#include "fileops.h"

#include "uisocket.h"

#include "config/audioconfig.h"
#include "config/uistate.h"
#include "config/uiconfig.h"

#include <QDebug>

#include <QFile>
#include <QCborMap>
#include <QCborArray>
#include <QCborStreamReader>
#include <QCborStreamWriter>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>

#include <QUndoStack>
#include <QFileDialog>

#define qs QStringLiteral

using Xybrid::Data::Project;
using Xybrid::Data::Pattern;
using Xybrid::Data::Graph;
using Xybrid::Data::Node;

namespace FileOps = Xybrid::FileOps;

using namespace Xybrid::Config;

namespace { // utilities
    struct _MapSaver {
        QCborMap& root;
        QString name;
        QCborMap map;

        _MapSaver(QCborMap& root, const QString& name) : root(root), name(name) { }
        ~_MapSaver() {
            root[name] = map;
        }
        inline auto operator[](const QString& s) { return map[s]; }
    };

    inline void load(QCborValueRef m, QString& v) { v = m.toString(v); }
    inline void load(QCborValueRef m, bool& v) { v = m.toBool(v); }
    inline void load(QCborValueRef m, int& v) { v = static_cast<int>(m.toInteger(v)); }
}

#define lsection(NAME) if (auto _sec = root[qs(#NAME)].toMap(); !_sec.isEmpty())
#define lvar(NS, NAME) load(_sec[qs(#NAME)], NS::NAME)

#define ssection(NAME) if (_MapSaver _sec(root, qs(#NAME)) ; true)
#define svar(NS, NAME) _sec[qs(#NAME)] = NS::NAME

void FileOps::loadConfig() {
    QFile file(Config::Directories::configFile);
    if (file.open({QFile::ReadOnly})) { // file exists! read in
        QCborStreamReader read(&file);
        auto root = QCborValue::fromCbor(read).toMap();
        file.close();

        lsection(directories) {
            lvar(Directories, projects);
            lvar(Directories, presets);
        }

        lsection(ui) {
            lvar(UIConfig, verticalKnobs);
            lvar(UIConfig, invertScrollWheel);
        }

        lsection(audio) {
            lvar(AudioConfig, playbackSampleRate);
            lvar(AudioConfig, playbackBufferMs);
            lvar(AudioConfig, previewSampleRate);
            lvar(AudioConfig, previewBufferMs);
            lvar(AudioConfig, renderSampleRate);
        }

    }

    // make sure directories exist
    if (auto d = QDir(Directories::projects); !d.exists()) d.mkpath(".");
    if (auto d = QDir(Directories::presets); !d.exists()) d.mkpath(".");
}

void FileOps::saveConfig() {
    QFileInfo fi(Directories::configFile);
    fi.dir().mkpath("."); // make sure directory exists

    QFile file(fi.filePath());
    if (!file.open({QFile::WriteOnly})) return;

    QCborMap root;

    ssection(directories) {
        svar(Directories, projects);
        svar(Directories, presets);
    }

    ssection(ui) {
        svar(UIConfig, verticalKnobs);
        svar(UIConfig, invertScrollWheel);
    }

    ssection(audio) {
        svar(AudioConfig, playbackSampleRate);
        svar(AudioConfig, playbackBufferMs);
        svar(AudioConfig, previewSampleRate);
        svar(AudioConfig, previewBufferMs);
        svar(AudioConfig, renderSampleRate);
    }

    // write out
    QCborStreamWriter w(&file);
    root.toCborValue().toCbor(w);
    file.close();
}

void FileOps::loadUIState() {
    QFile file(Directories::stateFile);
    if (file.open({QFile::ReadOnly})) { // file exists! read in
        QCborStreamReader read(&file);
        auto root = QCborValue::fromCbor(read).toMap();
        file.close();

        if (auto recent = root[qs("recent")].toArray(); !recent.isEmpty()) {
            UIState::recentFiles.clear();
            for (auto r : recent) UIState::recentFiles.push_back(r.toString());
        }

    }
}

void FileOps::saveUIState() {
    QFileInfo fi(Directories::stateFile);
    fi.dir().mkpath("."); // make sure directory exists

    QFile file(fi.filePath());
    if (!file.open({QFile::WriteOnly})) return;

    QCborMap root;

    {
        QCborArray recent;
        for (auto& r : UIState::recentFiles) recent.append(r);
        root[qs("recent")] = recent;
    }

    // write out
    QCborStreamWriter w(&file);
    root.toCborValue().toCbor(w);
    file.close();
}
