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

#include "nodelib/commandreader.h"
#include "nodelib/resampler.h"

#include "data/project.h"
#include "data/sample.h"

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

#include "ui/waveformpreviewwidget.h"

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

#include "ui/patchboard/nodeuiscene.h"
#include "uisocket.h"

#include "util/ext.h"
#include "util/strings.h"

#include <cmath>

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

#include <QMenu>
#include <QGraphicsProxyWidget>

// clazy:excludeall=non-pod-global-static
RegisterPlugin(Capaxitor, {
    i->id = "plug:capaxitor";
    i->displayName = "CapaXitor";
    i->category = "Sampler";
})

Capaxitor::Capaxitor() {

}

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

    core.onNoteOn = [this](Note& note) {
        auto& data = *hard_cast<NoteData*>(&note.scratch);
        new (&data) NoteData(); // construct in-place

        note.adsr = adsr;
    };

    core.onDeleteNote = [](Note& note) {
        auto& data = *hard_cast<NoteData*>(&note.scratch);
        data.~NoteData(); // destroy
    };

    core.processNote = [this](Note& note, AudioPort* p) {
        auto& data = *hard_cast<NoteData*>(&note.scratch);
        auto smp = this->smp.lock();
        if (!smp) return core.deleteNote(note);

        double rate = static_cast<double>(smp->sampleRate) / static_cast<double>(audioEngine->curSampleRate());
        double baseNote = smp->getNote();

        bool loop = smp->loopStart >= 0;
        auto len = static_cast<double>(smp->length());

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

            double n = note.effectiveNote();
            double fr = std::pow(SEMI, n - baseNote);

            // actual sample pos
            double sp = data.sampleTime * rate;
            if (!loop && sp >= len) return core.deleteNote(note);

            auto out = NodeLib::resamp(smp.get(), sp, rate*fr);

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

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

void Capaxitor::saveData(QCborMap& m) const {
    if (auto smp = this->smp.lock(); smp) {
        m[qs("sample")] = QCborValue(smp->uuid);
        smp->markForExport();
    }
    m[qs("adsr")] = adsr;
}

void Capaxitor::loadData(const QCborMap& m) {
    auto id = m.value("sample").toUuid();
    if (auto f = project->samples.find(id); f != project->samples.end()) {
        smp = f.value();
    }
    adsr = m.value("adsr");
}

void Capaxitor::onGadgetCreated() {
    if (!obj) return;

    auto l = new LayoutGadget(obj);
    auto sampleSelector = new SampleSelectorGadget(project, l);
    sampleSelector->setSize(128, 48);
    sampleSelector->setSample(smp.lock(), false);

    KnobGadget::autoCreate(l, adsr);

    QObject::connect(sampleSelector, &SampleSelectorGadget::sampleSelected, sampleSelector, [=](auto smp) { this->smp = smp; });
}

void Capaxitor::onDoubleClick() { emit project->socket->openNodeUI(this); }
