#include "xriek.h"
using Xybrid::Instruments::Xriek;
using namespace Xybrid::NodeLib;
using Note = InstrumentCore::Note;
using namespace Xybrid::Data;

#include "nodelib/commandreader.h"

#include "data/porttypes.h"
#include "config/pluginregistry.h"
using namespace Xybrid::Config;
#include "audio/audioengine.h"
using namespace Xybrid::Audio;

#include "ui/patchboard/nodeobject.h"
#include "ui/gadgets/layoutgadget.h"
#include "ui/gadgets/knobgadget.h"
using namespace Xybrid::UI;

#include "util/strings.h"

#include <cmath>

#include <QDebug>
#include <QCborMap>
#include <QCborValue>
#include <QCborArray>

namespace {
    bool _ = PluginRegistry::enqueueRegistration([] {
        auto i = std::make_shared<PluginInfo>();
        i->id = "plug:xriek";
        i->displayName = "Xriek";
        i->category = "Instrument";
        i->createInstance = []{ return std::make_shared<Xriek>(); };
        PluginRegistry::registerPlugin(i);
    });

    [[maybe_unused]] inline double wrap(double d) {
        while (true) {
            if (d > 1.0) d = (d - 2.0) * -1; //d-=2.0;
            else if (d < -1.0) d = (d + 2.0) * -1;
            else return d;
        }
    }
    [[maybe_unused]] inline double lerp(double p, double a, double b) {
        return b * p + a * (1.0 - p);
    }
}

Xriek::Xriek() { }

void Xriek::init() {
    addPort(Port::Input, Port::Command, 0);
    addPort(Port::Output, Port::Audio, 0);

    core.onNoteOn = [this](Note& note) {
        //qDebug() << "note on";
        note.adsr = adsr.normalized();
    };

    core.globalParam['Q'] = [](const ParamReader& pr) {
        qDebug() << "global recieved" << pr.param() << pr.val();
        return true;
    };

    core.processNote = [this](Note& note, AudioPort* p) {
        double freq;

        double smpTime = core.sampleTime();
        size_t ts = p->size;
        for (size_t i = 0; i < ts; i++) {
            core.advanceNote(note);

            double n = note.effectiveNote();
            freq = 440.0 * std::pow(SEMI, n - (45+12*2));
            auto si = note.scratch[0];
            note.scratch[0] += smpTime * freq;

            double o = 0;
            double op = 0;
            double drv = 1.0+drive;
            for (int i = 1; i <= 5; i++) {
                double p = 1.0/i;
                o += wrap(std::sin(si*PI*i) * drv) * p;
                op += p;
            }
            o = wrap((o/op) * drv);

            o = std::pow(o, 1.0/(1.0+saturation));

            //o = note.scratch[1] + (smpTime / 1.0/20000) * (note.scratch[1] - o); // simple low pass

            note.scratch[3] += /*(smpTime / (1.0/20000.0))*/0.5 * (o - note.scratch[3]); // simple low pass;
            if (note.scratch[3] != note.scratch[3]) note.scratch[3] = 0; // nan!? WHY

            //qDebug() << note.scratch[3];

            //if (note.id % 2 != 0) o *= -1;

            AudioFrame out = note.scratch[3];

            (*p)[i] += out.gainBalance(0, note.pan) * note.ampMult();
        }
    };
}

void Xriek::reset() { core.reset(); }
void Xriek::release() { core.release(); }
void Xriek::process() { core.process(this); }

void Xriek::saveData(QCborMap& m) const {
    m[qs("adsr")] = adsr;

    m[qs("drive")] = drive;
    m[qs("saturation")] = saturation;
    //
}

void Xriek::loadData(const QCborMap& m) {
    adsr = m.value("adsr");

    drive = m.value("drive").toDouble(drive);
    saturation = m.value("saturation").toDouble(saturation);
    //
}

void Xriek::onGadgetCreated() {
    auto l = new LayoutGadget(obj);
    {
        auto k = new KnobGadget(l);
        k->min = 0.0;
        k->max = 5.0;
        k->step = .01;
        k->bind(drive);
        k->setLabel("Drive");
    }

    {
        auto k = new KnobGadget(l);
        k->min = 0.0;
        k->max = 5.0;
        k->step = .01;
        k->bind(saturation);
        k->setLabel("Saturate");
    }

    l->addSpacer();
    KnobGadget::autoCreate(l, adsr);
}
