#include "graph.h"
using namespace Xybrid::Data;

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

#include "data/project.h"
#include "uisocket.h"
#include "mainwindow.h"

#include "util/strings.h"

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

#define qs QStringLiteral

namespace {
    std::shared_ptr<PluginInfo> inf;
    bool c = PluginRegistry::enqueueRegistration([] {
        auto i = std::make_shared<PluginInfo>();
        i->id = "graph";
        i->displayName = "Subgraph";
        i->createInstance = []{ return std::make_shared<Graph>(); };
        PluginRegistry::registerPlugin(i);
        inf = i;
    });
}

//std::string Graph::pluginName() const { return "Subgraph"; }

Graph::Graph() {
    plugin = inf; // harder bind
}

// propagate
void Graph::reset() { for (auto c : children) c->reset(); }
void Graph::release() { for (auto c : children) c->release(); }

void Graph::saveData(QCborMap& m) const {
    // graph properties
    // ... maybe there will be some at some point

    std::unordered_map<Node*, int> indices;
    { /* children */ } {
        QCborArray c;

        int idx = 0;
        for (auto ch : children) {
            if (!ch->plugin) continue;
            indices[ch.get()] = idx++;
            c << ch->toCbor();
        }

        m[qs("children")] = c;
    }

    { /* connections */ } {
        // mapped from output to input
        // array { oIdx, dataType, pIdx, iIdx, dataType, pIdx }
        QCborArray cn;

        for (auto ch : children) {
            if (!ch->plugin) continue; // already skipped over
            int idx = indices[ch.get()];
            for (auto dt : ch->outputs) {
                for (auto op : dt.second) {
                    auto o = op.second;
                    o->cleanConnections(); // let's just do some groundskeeping here
                    for (auto iw : o->connections) {
                        auto i = iw.lock();
                        QCborArray c;
                        c << idx;
                        c << Util::enumName(o->dataType());
                        c << o->index;
                        c << indices[i->owner.lock().get()];
                        c << Util::enumName(i->dataType());
                        c << i->index;
                        cn << c;
                    }
                }
            }

        }

        m[qs("connections")] = cn;
    }

}

void Graph::loadData(const QCborMap& m) {
    auto g = std::static_pointer_cast<Graph>(shared_from_this());
    // graph properties (none)

    { /* children */ } {
        QCborArray c = m.value("children").toArray();
        children.reserve(static_cast<size_t>(c.size()));
        for (auto chmv : c) Node::fromCbor(chmv, g); // oh, that's it?
    }

    { /* connections */ } {
        QCborArray cn = m.value("connections").toArray();

        auto pmt = QMetaType::metaObjectForType(QMetaType::type("Xybrid::Data::Port"));
        auto dtm = pmt->enumerator(pmt->indexOfEnumerator("DataType"));

        for (auto cv : cn) {
            auto c = cv.toArray();
            if (c.empty()) continue;
            auto on = children[static_cast<size_t>(c[0].toInteger())];
            auto in = children[static_cast<size_t>(c[3].toInteger())];
            auto op = on->port(Port::Output, static_cast<Port::DataType>(dtm.keyToValue(c[1].toString().toStdString().c_str())), static_cast<uint8_t>(c[2].toInteger()));
            auto ip = in->port(Port::Input, static_cast<Port::DataType>(dtm.keyToValue(c[4].toString().toStdString().c_str())), static_cast<uint8_t>(c[5].toInteger()));
            if (op && ip) op->connect(ip);
        }
    }
}

void Graph::onParent(std::shared_ptr<Graph>) {
    // propagate project pointer
    for (auto c : children) {
        c->project = project;
        // let this handle the recursion for us, since this is all this function does
        if (c->plugin == inf) c->onParent(c->parent.lock());
    }
}

void Graph::onDoubleClick() {
    emit project->socket->openGraph(this);
}
