#ifndef SLIDER_HPP_
#define SLIDER_HPP_

#include <algorithm>
#include <string>

#include <Widget/Widget.hpp>
#include <Utilities/Utilities.hpp>
#include <Color/Color.hpp>

/**
 * @brief A slider widget which allows the user to select values from a range.
 * 
 * @tparam TValue The type of the value.
 */
template<typename TValue, typename TDisplayValue = TValue>
class Slider : public Widget
{
private:
    static constexpr int sliderWidth = 4;

    TValue &m_value;
    TValue m_minValue;
    TValue m_maxValue;
    std::string m_format;
    bool m_down;
    std::function<void()> m_onUpdated;
    int naturalWidth = 100;

    void updateValue()
    {
        float frac = std::max(0.0f, std::min(1.0f, (float) m_mousePos[0] / (float) m_size[0]));
        m_value = m_minValue + frac * (m_maxValue - m_minValue);
        m_onUpdated();
    }

public:
    /**
     * @brief Construct a slider.
     * 
     * @param value The value reference which this slider will update.
     * @param minValue The minimum value.
     * @param maxValue The maximum value.
     * @param format The format string.
     * @param onUpdated Function to call when value changed.
     */
    Slider(
        TValue &value,
        TValue minValue,
        TValue maxValue,
        std::string format,
        std::function<void()> onUpdated = [](){}
    ) :
        m_value(value),
        m_minValue(minValue),
        m_maxValue(maxValue),
        m_format(format),
        m_down(false),
        m_onUpdated(onUpdated)
    {}
    
    virtual void draw(GraphicsContext &context) override
    {
        context.color = glm::vec4(1.0f);
        context.drawRect(0, 0, m_size[0], m_size[1]);

        context.color = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
        context.drawRect(1, 1, m_size[0]-2, m_size[1]-2);

        context.color = Color::action;
        float frac = (float) (m_value - m_minValue) / (float) (m_maxValue - m_minValue);
        int offset = frac * (m_size[0] - sliderWidth - 2);
        context.drawRect(offset+1, 1, sliderWidth, m_size[1]-2);

        std::string text = formatString(m_format.c_str(), static_cast<TDisplayValue>(m_value));
        glm::ivec2 textSize = context.measureText(Font::generic, text);
        glm::ivec2 base = (m_size-textSize)/2;

        context.color = glm::vec4(1.0f);
        context.drawText(Font::generic, base[0], base[1], text);
    }

    virtual glm::ivec2 getMinSize() override
    {
        return glm::ivec2(naturalWidth, 12);
    }

    virtual void onMouseDown(Uint8 button) override
    {
        Widget::onMouseDown(button);
        if (button == SDL_BUTTON_LEFT && isMouseInside())
        {
            m_down = true;
            updateValue();
        }
    }

    virtual void onMouseUp(Uint8 button) override
    {
        Widget::onMouseUp(button);
        if (button == SDL_BUTTON_LEFT)
        {
            m_down = false;
        }
    }

    virtual void onMouseMoved(glm::ivec2 pos) override
    {
        Widget::onMouseMoved(pos);

        if (m_down)
        {
            updateValue();
        }
    }

    /**
     * @brief Set the natural width and return 'this'.
     */
    Slider<TValue>* withNaturalWidth(int width)
    {
        naturalWidth = width;
        return this;
    }
};

#endif