#include "mainwindow.h"
#include "audio/audioengine.h"
#include "config/pluginregistry.h"
#include "data/graph.h"
#include "fileops.h"

#include "util/mem.h"

#include <vector>

#include <QDebug>
#include <QApplication>
#include <QCommandLineParser>
#include <QLocalServer>
#include <QLocalSocket>
#include <QSurfaceFormat>
#include <QFontDatabase>

#include <QCborMap>
#include <QCborArray>
#include <QCborStreamReader>
#include <QCborStreamWriter>

#define qs QStringLiteral

int main(int argc, char *argv[]) {
    qRegisterMetaType<Xybrid::Data::Port>();

    // enable antialiasing on accelerated graphicsview
    QSurfaceFormat fmt;
    fmt.setSamples(10);

    QSurfaceFormat::setDefaultFormat(fmt);
    QApplication a(argc, argv);

    QCommandLineParser cl;
    cl.addHelpOption();
    cl.addVersionOption();
    cl.addPositionalArgument("[project...]", QApplication::translate("main", "Project file(s) to open."));

    cl.process(a);
    auto args = cl.positionalArguments();

    QString userName = qEnvironmentVariable("USER");
    if (userName.isEmpty()) userName = qEnvironmentVariable("USERNAME");

    QString socketName = qs("xybrid-ipc-%1").arg(userName);

    QLocalSocket tryc;
    tryc.connectToServer(socketName);
    tryc.waitForConnected(1000); // wait for connection attempt (can't hang on local)
    if (tryc.isOpen()) { // if server already exists, give it the signal and exit
        QCborArray root;
        root << "open";

        QCborArray lst;
        for (auto& fn : args) {
            QFileInfo fi(fn);
            if (!fi.exists()) continue;
            lst << fi.absoluteFilePath();
        }
        root << lst;

        QCborStreamWriter csw(&tryc);
        root.toCborValue().toCbor(csw);

        tryc.waitForBytesWritten();
        tryc.close();
        return 0;
    }

    QLocalServer srv;
    srv.setSocketOptions(QLocalServer::UserAccessOption);
    srv.removeServer(socketName); // if it exists and we're here, previous instance probably crashed or was killed
    srv.listen(socketName);
    QObject::connect(&srv, &QLocalServer::newConnection, &srv, [&]() {
        auto s = srv.nextPendingConnection();
        s->waitForDisconnected();

        QCborStreamReader csr(s);
        auto root = QCborValue::fromCbor(csr).toArray();
        s->deleteLater();

        auto cmd = root.at(0).toString();

        if (cmd == "open") {
            auto lst = root.at(1).toArray();
            if (lst.isEmpty()) {
                auto w = new Xybrid::MainWindow(nullptr);
                w->show();
            }
            for (auto e : lst) {
                QFileInfo fi(e.toString());
                if (!fi.exists()) continue;
                auto fileName = fi.absoluteFilePath();
                if (auto w = Xybrid::MainWindow::projectWindow(fileName); w) w->tryFocus();
                else {
                    w = new Xybrid::MainWindow(nullptr, fileName);
                    if (w->getProject()) w->show();
                    else (w->deleteLater());
                }
            }
        }
    });

    // make sure bundled fonts are loaded
    QFontDatabase::addApplicationFont(":/fonts/iosevka-term-light.ttf");
    QFontDatabase::addApplicationFont(":/fonts/Arcon-Rounded-Regular.otf");

    Xybrid::FileOps::loadConfig();
    Xybrid::FileOps::loadUIState();

    Xybrid::Config::PluginRegistry::init();
    Xybrid::Audio::AudioEngine::init();

    Xybrid::Util::reserveInitialPool(); // reserve arena pool ahead of time

    bool opn = false;

    for (auto& fn : args) {
        QFileInfo fi(fn);
        if (!fi.exists()) continue;
        auto fileName = fi.absoluteFilePath();
        auto w = new Xybrid::MainWindow(nullptr, fileName);
        if (w->getProject()) { w->show(); opn = true; }
        else (w->deleteLater());
    }

    if (!opn) { // always show one window on launch
        auto w = new Xybrid::MainWindow(nullptr);
        w->show();
    }

    // hook up exit event
    QObject::connect(&a, &QCoreApplication::aboutToQuit, [] {
        Xybrid::FileOps::saveConfig();
    });

    return a.exec();
}
