#include "ioport.h"
using Xybrid::Gadgets::IOPort;
using namespace Xybrid::Data;

#include <QPainter>
#include <QGraphicsScene>
#include <QStyleOptionGraphicsItem>

#include <QCborMap>
#include <QMetaType>
#include <QMetaEnum>

#define qs QStringLiteral

#include "config/pluginregistry.h"
using namespace Xybrid::Config;

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

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

#include "audio/audioengine.h"
using namespace Xybrid::Audio;

#include "util/strings.h"

namespace {
    bool _ = PluginRegistry::enqueueRegistration([] {
        auto i = std::make_shared<PluginInfo>();
        i->id = "ioport";
        i->displayName = "I/O Port";
        i->hidden = true;
        i->createInstance = []{ return std::make_shared<IOPort>(); };
        PluginRegistry::registerPlugin(i);
        //inf = i;
    });

    Port::Type opposite(Port::Type t) {
        if (t == Port::Input) return Port::Output;
        return Port::Input;
    }
}


IOPort::IOPort() {

}

void IOPort::remove() {
    auto g = parent.lock();
    if (!g) return;
    if (!portSet) return;
    g->removePort(type, dataType, index);
    removePort(opposite(type), dataType, index);
}

void IOPort::add() {
    auto g = parent.lock();
    if (!g) return;
    if (!portSet) return;
    auto gp = g->addPort(type, dataType, index);
    auto p = addPort(opposite(type), dataType, index);
    if (gp && p) {
        p->name = name;
        gp->name = name;
        p->passthroughTo = gp;
        gp->passthroughTo = p;
    }
}

void IOPort::setPort(Port::Type type, Port::DataType dataType, uint8_t index) {
    remove();
    this->type = type;
    this->dataType = dataType;
    this->index = index;
    portSet = true;
    add();
}

void IOPort::process() {
    if (type == Port::Input) { // thread safety for passthroughs?
        //port(opposite(type), dataType, index)->pull();
    }
}

void IOPort::onRename() {
    auto g = parent.lock();
    if (!g) return;
    if (!portSet) return;
    auto gp = g->port(type, dataType, index);
    auto p = port(opposite(type), dataType, index);
    if (gp && p) {
        p->name = name;
        gp->name = name;
    }
}

void IOPort::saveData(QCborMap& m) const {
    QCborMap pm;
    pm[qs("type")] = Util::enumName(type);
    pm[qs("dataType")] = Util::enumName(dataType);
    pm[qs("index")] = index;
    m[qs("port")] = pm;
}

void IOPort::loadData(const QCborMap& m) {
    auto pm = m.value("port").toMap();
    if (pm.empty()) return;
    auto pmt = QMetaType::metaObjectForType(QMetaType::type("Xybrid::Data::Port"));
    auto tm = pmt->enumerator(pmt->indexOfEnumerator("Type"));
    auto dtm = pmt->enumerator(pmt->indexOfEnumerator("DataType"));
    std::string st = pm.value("type").toString().toStdString();
    std::string sdt = pm.value("dataType").toString().toStdString();
    setPort(
        static_cast<Port::Type>(tm.keyToValue(st.c_str())),
        static_cast<Port::DataType>(dtm.keyToValue(sdt.c_str())),
        static_cast<uint8_t>(pm.value("index").toInteger())
    );
}

void IOPort::onUnparent(std::shared_ptr<Graph>) { remove(); }
void IOPort::onParent(std::shared_ptr<Graph>) { add(); }

void IOPort::onDoubleClick() {
    // if it's a command input on the root graph...
    if (type == Port::Input && dataType == Port::Command && !parent.lock()->parent.lock()) {
        audioEngine->preview(project->shared_from_this(), index, -127); // set preview port
    }
}

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

    obj->customChrome = true;
    obj->autoPositionPorts = false;
    qreal ps = (PortObject::portSize + PortObject::portSpacing) * 2;
    obj->setGadgetSize(QPointF(ps, ps));

    // do this after setting size
    auto r = obj->boundingRect();
    obj->inputPortContainer->setPos(r.center());
    obj->outputPortContainer->setPos(r.center());
}

void IOPort::drawCustomChrome(QPainter* painter, const QStyleOptionGraphicsItem* opt) {
    QColor outline = QColor(31, 31, 31);
    if (opt->state & QStyle::State_Selected) outline = QColor(127, 127, 255);

    auto r = obj->boundingRect();
    auto rf = r - QMarginsF(1, 1, 1, 1);

    painter->setPen(Qt::NoPen);
    int ro = 180*16 * (1-type);

    painter->setBrush(QColor(63, 63, 63));
    painter->drawPie(rf, (90+45)*16 + ro, -90*3*16);
    painter->setBrush(QColor(95, 95, 95));
    painter->drawPie(rf, (90+45)*16 + 180*16 + ro, 90*16);

    painter->setBrush(Qt::NoBrush);
    painter->setPen(QPen(outline, 2));
    painter->drawPie(r, (90+45)*16 + ro, -90*3*16);
}

