/**
  *  \file ui/widgets/icongrid.hpp
  *  \brief Class ui::widgets::IconGrid
  */
#ifndef C2NG_UI_WIDGETS_ICONGRID_HPP
#define C2NG_UI_WIDGETS_ICONGRID_HPP

#include <vector>
#include "afl/base/signal.hpp"
#include "gfx/engine.hpp"
#include "gfx/point.hpp"
#include "gfx/timer.hpp"
#include "ui/icons/icon.hpp"
#include "ui/scrollablewidget.hpp"

namespace ui { namespace widgets {

    /** Icon Grid widget.
        This widget displays a set of icons of identical size.

        Icons are identified by X/Y coordinates which are 0-based integers.
        For simplicity, icons can also be identified by a number generated by counting row-wise.

        The widget allows the user to move around on the displayed list.
        The width (number of icons displayed next to each other) is fixed.
        The height (number of lines) is fixed for display;
        if more icons are added to the IconGrid, it can scroll and therefore implements the ScrollableWidget interface.

        An icon is drawn with the FocusedButton flag when it is selected.

        Icons can be marked accessible (default) or inaccessible.
        Inaccessible icons cannot be selected using setCurrentItem(), and are skipped using keyboard/scrollbar navigation.

        Signals:
        - ScrollableWidget::sig_change: raised when the widget scrolls or the cursor moves in vertical direction,
          or an icon is added causing the vertical size to grow.
        - IconGrid::sig_itemSelected: raised when the cursor moves */
    class IconGrid : public ScrollableWidget {
     public:
        /** Constructor.
            \param engine        Engine (for timer)
            \param cellSize      Size of each icon (the icon's getSize() is ignored)
            \param widthInCells  Number of icons per line
            \param heightInCells Number of lines */
        IconGrid(gfx::Engine& engine, gfx::Point cellSize, int widthInCells, int heightInCells);
        ~IconGrid();

        /** Set extra padding.
            There is a 1-pixel gap between icons which will contain the focus frame for selection.
            Use setPadding() to add additional padding around the icon.

            This method should be called before the widget is layouted.

            \param pad Pixels to add on the left, right, top, bottom of the icon */
        void setPadding(int pad);

        /** Set icon at position.
            Calling this method with coordinates outside the currently-used range will grow the widget accordingly.
            \param x Column [0,widthInCells)
            \param y Line (starting at 0)
            \param pIcon Icon; owned by called, must out-live the IconGrid. Can be 0. */
        void setIcon(int x, int y, ui::icons::Icon* pIcon);

        /** Add icon.
            Adds an icon after the highest currently-used position.
            This method is intended for simple static initialisation.
            \param pIcon Icon; owned by called, must out-live the IconGrid. Can be 0. */
        void addIcon(ui::icons::Icon* pIcon);

        /** Set current linear position.
            \param index New index (starting at 0) */
        void setCurrentItem(size_t index);

        /** Set current position.
            \param x Column [0,widthInCells)
            \param y Line (starting at 0) */
        void setCurrentItem(int x, int y);

        /** Set item accessibility, linear position.
            \param index Index (starting at 0)
            \param flag  true if item is accessible (default), false if not */
        void setItemAccessible(size_t index, bool flag);

        /** Set item accessibility.
            \param x     Column [0,widthInCells)
            \param y     Line (starting at 0)
            \param flag  true if item is accessible (default), false if not */
        void setItemAccessible(int x, int y, bool flag);

        /** Get current linear position.
            \return Index (starting at 0) */
        size_t getCurrentItem() const;

        /** Get current line.
            \return line (starting at 0) */
        int getCurrentLine() const;

        /** Get current column.
            \return column [0,widthInCells) */
        int getCurrentColumn() const;

        // ScrollableWidget:
        virtual int getPageTop() const;
        virtual int getPageSize() const;
        virtual int getCursorTop() const;
        virtual int getCursorSize() const;
        virtual int getTotalSize() const;
        virtual void setPageTop(int top);
        virtual void scroll(Operation op);

        // SimpleWidget:
        virtual void draw(gfx::Canvas& can);
        virtual void handleStateChange(State st, bool enable);
        virtual void handlePositionChange();
        virtual ui::layout::Info getLayoutInfo() const;
        virtual bool handleKey(util::Key_t key, int prefix);
        virtual bool handleMouse(gfx::Point pt, MouseButtons_t pressedButtons);

        /** Signal: item selected.
            Raised when the cursor moves in any direction, that is, the return value of
            - getCurrentItem()
            - getCurrentColumn()
            - getCurrentLine()
            changes. */
        afl::base::Signal<void()> sig_itemSelected;

        /** Signal: item double-clicked.
            Raised when the user double-clicks within the widget. */
        afl::base::Signal<void()> sig_doubleClick;

     private:
        const gfx::Point m_cellSize;
        const int m_widthInCells;
        const int m_heightInCells;
        int m_currentLine;
        int m_currentColumn;
        int m_pageTop;
        int m_padding;
        bool m_cursorOn;
        std::vector<ui::icons::Icon*> m_icons;
        std::vector<uint8_t> m_itemInaccessible;
        const afl::base::Ref<gfx::Timer> m_timer;

        gfx::Rectangle getCellPosition(int x, int y) const;
        bool handleVerticalScroll(int delta, int adjust);
        bool isItemAccessible(size_t pos) const;
        bool isItemAccessible(int x, int y) const;

        void onTimer();
        void resetCursorBlink();
    };

} }

#endif
