#pragma once

#include <cmath>
#include <limits>
#include <QCborMap>

#include "data/node.h"
#include "data/porttypes.h"

//namespace Xybrid::Data { class ParameterPort; }
namespace Xybrid::NodeLib {
    /// Plugin parameter with associated metadata and automation faculties
    class Param {
        //
        Param() = default;
    public:
        class Reader {
            friend class Param;
            Param* p;
            size_t pos = 0;
            bool r;
            Reader(Param* p, bool r) : p(p), r(r) { }
        public:
            double next() {
                if (r) {
                    auto v = p->port->data[pos++];
                    if (!std::isnan(v)) {
                        if (p->min == -p->max) // "signed" logic if symmetrical around zero
                            v = std::clamp(v, -1.0, 1.0) * p->max;
                        else // extents 0.0 .. 1.0 scaled across parameter range
                            v = std::clamp(p->min + v * (p->max - p->min), p->min, p->max);

                        p->vt = v;
                    }
                }
                if (!std::isnan(p->vt)) return p->vt;
                return p->value;
            }
        };

        enum Flags : uint8_t {
            ResetOnTick = 0b00000001,
        };

        QString name;
        double min, max, def;

        double value;
        double vt = std::numeric_limits<double>::quiet_NaN();

        Flags flags;

        Data::ParameterPort* port = nullptr;

        Param(const QString& name, double min, double max, double def, Flags flags = {}) : Param() {
            this->name = name;
            this->min = min;
            this->max = max;
            this->def = def;
            this->value = def;
            this->flags = flags;
        }

        Data::ParameterPort* makePort(Data::Node*, const QString& name = { });

        Reader start() {
            bool r = port && port->isConnected();
            if (r) port->pull();
            if (flags & ResetOnTick) vt = std::numeric_limits<double>::quiet_NaN();
            return Reader(this, r);
        }

        inline QString saveName() const {
            return name.toLower().remove(' ');
        }

        inline void save(QCborMap& m, QString id = { }) const {
            if (id.isEmpty()) id = saveName();
            m[id] = value;
        }

        inline void load(const QCborMap& m, QString id = { }) {
            if (id.isEmpty()) id = saveName();
            value = m.value(id).toDouble(value);
            vt = std::numeric_limits<double>::quiet_NaN();
        }
    };
}
