#ifndef WIDGET_HPP_
#define WIDGET_HPP_

#include <GraphicsContext/GraphicsContext.hpp>
#include <Color/Color.hpp>

/**
 * @brief Base class for all widgets.
 */
class Widget
{
private:
    static std::vector<Widget*> cleanupQueue;

protected:
    /**
     * @brief Current size of the widget.
     */
    glm::ivec2 m_size;

    /**
     * @brief The mouse position (set by `onMouseMoved()` by default).
     */
    glm::ivec2 m_mousePos;

    /**
     * @brief Open a popup containing the specified content widget.
     * 
     * @param relPos Position to open at, relative to this widget.
     * @param content The widget to be displayed in the popup.
     */
    void openPopup(glm::ivec2 relPos, Widget *content);

    /**
     * @brief Returns true if this widget currently has an open popup.
     */
    bool isHostingPopup();

public:
    /**
     * @brief Current popup (if any).
     */
    static Widget* popup;

    /**
     * @brief The currently-focused widget, or `nullptr`.
     */
    static Widget* focused;

    /**
     * @brief The widget hosting the popup.
     */
    static Widget* popupHost;

    /**
     * @brief The currently-dragged widget, or `nullptr`.
     */
    static Widget* dragged;

    /**
     * @brief If true, then onMouseMoved() will be called on the next frame to refresh the mouse position.
     */
    static bool isRequestingMousePosUpdate;

    static Material* currentCursor;
    static glm::ivec2 currentHotPoint;

    /**
     * @brief The absolute position of this widget.
     * 
     * This is usually set by Layout's draw().
     */
    glm::ivec2 widgetAbsPos = glm::ivec2(0, 0);

    /**
     * @brief Perform post-update cleanup.
     */
    static void doPostUpdateCleanup();

    /**
     * @brief Construct the widget.
     */
    Widget();

    virtual ~Widget();

    /**
     * @brief Close the current popup.
     */
    static void closePopup();

    /**
     * @brief Set the cursor to use for the next frame.
     */
    static void setCursor(Material &cursor, glm::ivec2 hotPoint);

    /**
     * @brief Returns true if the mouse cursor is inside this widget.
     * 
     * This is based on `m_mousePos` which is set by `onMouseMoved()` by default.
     */
    bool isMouseInside();

    /**
     * @brief Focus this widget.
     */
    void focus();

    /**
     * @brief Return true if this widget is focused.
     */
    bool isFocused();

    /**
     * @brief Called when the size of this widget changes.
     * 
     * The default behavior is to update the protected field `m_size`. You should
     * call this supermethod from any overrides.
     * 
     * @param newSize The new size.
     */
    virtual void onResize(glm::ivec2 newSize);

    /**
     * @brief Draw this widget.
     * 
     * @param context The graphics context.
     */
    virtual void draw(GraphicsContext &context) = 0;

    /**
     * @brief Get the minimum size of this widget.
     * 
     * @return The minimum size in virtual pixels.
     */
    virtual glm::ivec2 getMinSize() = 0;

    /**
     * @brief Called when the mouse has moved.
     * 
     * You should call the supermethod to get the default behavior.
     * 
     * @param pos Position of the mouse relative to this widget.
     */
    virtual void onMouseMoved(glm::ivec2 pos);

    /**
     * @brief Called when the mouse is up.
     * 
     * @param button The SDL mouse button number.
     */
    virtual void onMouseUp(Uint8 button);

    /**
     * @brief Called when the mouse is down.
     * 
     * @param button The SDL mouse button number.
     */
    virtual void onMouseDown(Uint8 button);

    /**
     * @brief Called when a key is pressed.
     * 
     * @param keycode The key code.
     */
    virtual void onKeyDown(SDL_Keycode keycode);

    /**
     * @brief Called when a key is released.
     * 
     * @param keycode The key code.
     */
    virtual void onKeyUp(SDL_Keycode keycode);

    /**
     * @brief Called when the user scrolls the mouse wheel.
     * 
     * @param delta The distance moved.
     */
    virtual void onScroll(int delta);

    /**
     * @brief Return true if this widget takes text input when focused.
     * 
     * Return false by default.
     */
    virtual bool isTextInput();

    /**
     * @brief Called when text input is received.
     * 
     * @param text The text input.
     */
    virtual void onTextInput(std::string text);

    /**
     * @brief Called when the popup opened by this widget is closed.
     */
    virtual void onPopupClosed();

    /**
     * @brief Get the currently-allocated size of this widget.
     */
    glm::ivec2 getCurrentSize();

    /**
     * @brief Get the tab order of this widget and/or its descendents.
     * 
     * This `tabOrder` vector might have some widgets in it already. This method must push
     * widgets to the end of this vector, in the expected tab order. By default, nothing
     * is pushed, and thus nothing in this widget would be tabbed into.
     */
    virtual void getInternalTabOrder(std::vector<Widget*> &tabOrder);
    
    friend class PopupContainer;
};

#endif