#pragma once

#include "ui/gadgets/gadget.h"
#include "nodelib/param.h"

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

#include "util/strings.h"

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

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

        QGraphicsSimpleTextItem* label;
        QGraphicsSimpleTextItem* valLabel;

        double get();
    public:
        enum Step : int {
            NoStep = 1,
            SmallStep = 3,
            MedStep = 7,
            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;
        double subStep = -1;
        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 hoverEnterEvent(QGraphicsSceneHoverEvent*) override;
        void mousePressEvent(QGraphicsSceneMouseEvent*) override;
        void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override;
        void mouseMoveEvent(QGraphicsSceneMouseEvent*) override;
        void wheelEvent(QGraphicsSceneWheelEvent*) 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* setSize(decltype(size) s) { this->size = s; return this; }
        inline KnobGadget* setRange(double min, double max, double step = -1, int px = -1, double sub = -1) {
            this->min = min;
            this->max = max;
            if (step > 0) this->step = step;
            if (px > 0) stepPx = px;
            if (sub > 0) subStep = sub;
            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;
        }
        KnobGadget* bind(NodeLib::Param& par) {
            auto p = &par;
            fGet = [p] { return p->value; };
            fSet = [p](double d) {
                p->value = d;
                p->vt = std::numeric_limits<double>::quiet_NaN();
            };

            // binding to one of these populates metadata
            min = p->min;
            max = p->max;
            defaultVal = p->def;
            label->setText(p->name);

            dirty = true;
            update();
            return this;
        }

        static QString textPercent(double);
        static QString textOffset(double);
        static QString textFrequency(double);

        static QString textGain(double);
        static QString textBalance(double);

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

        // macro to vastly reduce boilerplate
#define preset(NAME) \
    template<typename T> static inline KnobGadget* auto##NAME (QGraphicsItem* p, T& v) { return auto##NAME (p)->bind(v); } \
    static inline KnobGadget* auto##NAME (QGraphicsItem* p)

        // common presets
        preset(Percent) { return (new KnobGadget(p))->setRange(0.0, 1.0, .01)->setTextFunc(KnobGadget::textPercent); }

        preset(Gain) { return (new KnobGadget(p))->setRange(-60, 12, .1)->setLabel(qs("Gain"))->setTextFunc(KnobGadget::textGain); }
        preset(Balance) { return (new KnobGadget(p))->setRange(-1.0, 1.0, .01)->setLabel(qs("Balance"))->setTextFunc(KnobGadget::textBalance); }
        preset(Cutoff) { return (new KnobGadget(p))->setRange(0, 16000, 10, NoStep, 1)->setLabel(qs("Cutoff"))->setTextFunc(KnobGadget::textFrequency); }

    };
}

// keep this local
#undef preset
