#include "mixboard.h"
using Xybrid::Gadgets::MixBoard;
using namespace Xybrid::Data;

#include "nodelib/basics.h"
using namespace Xybrid::NodeLib;

#include "data/audioframe.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/buttongadget.h"
#include "ui/gadgets/togglegadget.h"
#include "ui/gadgets/knobgadget.h"
using namespace Xybrid::UI;

#include <cmath>

#include <QPen>
#include <QCborMap>
#include <QCborArray>

#define qs QStringLiteral

namespace {
    bool _ = PluginRegistry::enqueueRegistration([] {
        auto i = std::make_shared<PluginInfo>();
        i->id = "gadget:mixboard";
        i->displayName = "Mixer Board";
        i->category = "Gadget";
        i->createInstance = []{ return std::make_shared<MixBoard>(); };
        PluginRegistry::registerPlugin(i);
    });
}

MixBoard::MixBoard() {

}

void MixBoard::init() {
    addPort(Port::Output, Port::Audio, 0);
    if (sections.empty()) insertSection(0);
}

void MixBoard::process() {
    auto out = std::static_pointer_cast<AudioPort>(port(Port::Output, Port::Audio, 0));
    out->pull();

    bool solo = false;
    for (auto& s : sections) if (s.solo) { solo = true; break; }

    size_t ts = audioEngine->curTickSize();

    for (size_t i = 0; i < sections.size(); i++) {
        auto& s = sections[i];
        if (s.mute || (solo && !s.solo)) continue; // muted

        auto m = AudioFrame::gainBalanceMult(s.gain);
        auto in = std::static_pointer_cast<AudioPort>(port(Port::Input, Port::Audio, static_cast<uint8_t>(i)));
        in->pull();

        for (size_t f = 0; f < ts; f++) (*out)[f] += (*in)[f] * m;
    }
}

void MixBoard::saveData(QCborMap& m) const {
    QCborArray sa;
    for (auto& sec : sections) {
        QCborMap s;
        s[qs("gain")] = QCborValue(sec.gain);
        s[qs("mute")] = sec.mute;
        s[qs("solo")] = sec.solo;
        sa << s;
    }

    m[qs("sections")] = sa;
}

void MixBoard::loadData(const QCborMap& m) {
    auto sa = m.value("sections").toArray();

    sections.clear();
    sections.resize(static_cast<size_t>(sa.size()));
    for (size_t i = 0; i < sections.size(); i++) {
        addPort(Port::Input, Port::Audio, static_cast<uint8_t>(i), false);
        auto& s = sections[i];
        auto sm = sa[static_cast<int>(i)].toMap();
        s.gain = sm.value("gain").toDouble(s.gain);
        s.mute = sm.value("mute").toBool(s.mute);
        s.solo = sm.value("solo").toBool(s.solo);
    }
    if (obj) {
        obj->updatePorts();
        onGadgetCreated();
        emit obj->finalized();
    }
}

void MixBoard::insertSection(uint8_t p) {
    p = std::min(p, static_cast<uint8_t>(sections.size()));
    sections.emplace(sections.begin()+p);
    for (int16_t ii = 254; ii >= p; ii--) {
        auto i = static_cast<uint8_t>(ii);
        movePort(Port::Input, Port::Audio, i, i+1, false);
    }
    addPort(Port::Input, Port::Audio, p, false);
    if (obj) {
        obj->updatePorts();
        onGadgetCreated();
        emit obj->finalized();
    }
}

void MixBoard::removeSection(uint8_t p) {
    if (p >= sections.size()) return;
    sections.erase(sections.begin()+p);
    removePort(Port::Input, Port::Audio, p, false);
    for (int16_t ii = p+1; ii < 256; ii++) {
        auto i = static_cast<uint8_t>(ii);
        movePort(Port::Input, Port::Audio, i, i-1, false);
    }
    if (obj) {
        obj->updatePorts();
        onGadgetCreated();
        emit obj->finalized();
    }
}

void MixBoard::onGadgetCreated() {
    if (!obj) return;
    {
        auto k = new Gadget();
        for (auto c : obj->contents->childItems()) c->setParentItem(k);
        k->deleteLater();
    }
    qDeleteAll(obj->contents->childItems()); // clear out anything already there
    auto l = new LayoutGadget(obj, true);

    QObject::connect(obj, &NodeObject::postGeometryUpdate, l, [this] {
        obj->inputPortContainer->setY(obj->contents->y());
        auto c = static_cast<QGraphicsLineItem*>(obj->inputPortContainer.get());
        c->setPen(Qt::NoPen); // hide line
    });

    auto c = obj->inputPortContainer->childItems();
    auto count = sections.size();
    for (size_t i = 0; i < count; i++) {
        constexpr qreal spc = 7;
        auto ln = (new LayoutGadget(l))->setMetrics(3);

        auto tl = (new LayoutGadget(ln, true))->setMetrics(-1, spc);
        /*auto mute = */(new ToggleGadget(tl))->bind(sections[i].mute)->setColor({255, 0, 0})->setToolTip("Mute", {-1, 0});
        /*auto solo = */(new ToggleGadget(tl))->bind(sections[i].solo)->setColor({191, 191, 0})->setToolTip("Solo", {-1, 0});

        /*auto gain = */(new KnobGadget(ln))->bind(sections[i].gain)->setRange(-60, 6, .1)->setLabel("Gain")->setTextFunc([](double d) { return QString("%1dB").arg(d); });

        auto end = (new LayoutGadget(ln, true))->setMetrics(-1, spc);
        auto bIns = (new ButtonGadget(end))->setSize(16, 16)->setText("+");
        auto bDel = (new ButtonGadget(end))->setSize(16, 16)->setText("-");
        QObject::connect(bIns, &ButtonGadget::clicked, [this, i] { insertSection(static_cast<uint8_t>(i)); });
        QObject::connect(bDel, &ButtonGadget::clicked, [this, i] { removeSection(static_cast<uint8_t>(i)); });

        if (count <= 1) delete bDel; // no dropping to zero
    }

    l->updateGeometry();
    auto lc = l->childItems();
    for (auto i = 0; i < static_cast<int>(count); i++) c[i]->setY(lc[i]->y() + lc[i]->boundingRect().center().y());

    auto btn = (new ButtonGadget(l))->setSize(16, 16)->setText("+");
    QObject::connect(btn, &ButtonGadget::clicked, [this, count] { insertSection(static_cast<uint8_t>(count)); });
}
