#pragma once

#include "ui/gadgets/gadget.h"

#include <memory>
#include <functional>
#include <atomic>

namespace Xybrid::NodeLib { class ADSR; }
namespace Xybrid::UI {
    class LayoutGadget;
    class KnobGadget : public Gadget {
        Q_OBJECT

        bool highlighted = false;
        double startVal;
        double lastVal = 0.0;
        bool dirty = true;

        QGraphicsSimpleTextItem* label;
        QGraphicsSimpleTextItem* valLabel;

        double get();
    public:
        enum Step : int {
            NoStep = 1,
            SmallStep = 3,
            BigStep = 15,
        };

        std::function<double()> fGet = [] { return 0.0; };
        std::function<void(double)> fSet = [](double) { };
        std::function<QString(double)> fText = [](double d) { return QString::number(d); };

        double min = 0.0;
        double max = 1.0;
        double step = 0.01;
        int stepPx = SmallStep;
        double defaultVal = 0.0;

        qreal size = 32;

        KnobGadget(QGraphicsItem* parent = nullptr);
        ~KnobGadget() override = default;

        QRectF boundingRect() const override;
        QRectF layoutBoundingRect() const override;
        QPainterPath shape() const override;
        void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;

        void mousePressEvent(QGraphicsSceneMouseEvent*) override;
        void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override;
        void mouseMoveEvent(QGraphicsSceneMouseEvent*) override;
        void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override;

        inline KnobGadget* setLabel(const QString& s) { label->setText(s); dirty = true; update(); return this; }
        inline KnobGadget* setTextFunc(const std::function<QString(double)>& f) { fText = f; dirty = true; update(); return this; }
        inline KnobGadget* setRange(double min, double max, double step = -1, int px = -1) {
            this->min = min;
            this->max = max;
            if (step > 0) this->step = step;
            if (px > 0) stepPx = px;
            update();
            return this;
        }
        inline KnobGadget* setDefault(double d) { this->defaultVal = d; return this; }

        // and binding templates
        template<typename T> KnobGadget* bind(T& ref) {
            auto p = &ref; // pointerize
            fGet = [p] { return static_cast<double>(*p); };
            fSet = [p](double d) { *p = static_cast<T>(d); };
            update();
            return this;
        }
        template<typename T> KnobGadget* bind(std::atomic<T>& atm) {
            auto p = &atm;
            fGet = [p] { return p->load(); };
            fSet = [p](double d) { p->store(static_cast<T>(d)); };
            update();
            return this;
        }

        static void autoCreate(LayoutGadget*, NodeLib::ADSR&);
    };

}
