#include "VivyApplication.hh"
#include "UI/MainWindow.hh"
#include "UI/DockWidgetTitleBar.hh"
#include "UI/Theme/Theme.hh"
#include "Lib/Document/VivyDocumentStore.hh"
#include "Lib/Document/VivyDocument.hh"
#include "Lib/Script/ScriptStore.hh"
#include "Lib/Script/ScriptDocument.hh"

using namespace Vivy;

VivyApplication::VivyApplication(int &argc, char **argv)
    : QApplication(argc, argv)
{
    documentStore = std::make_shared<VivyDocumentStore>();
    scriptStore   = std::make_shared<ScriptStore>();
    VIVY_LOG_CTOR() << "Construction is OK";

    if (argc >= 2) {
        for (int i = 1; i < argc; ++i) {
            const QString passedFileName(QString::fromUtf8(argv[1]));
            const QFileInfo passedArgumentFile(passedFileName);
            Utils::DocumentType passedFileType;
            if (Utils::detectDocumentType(passedArgumentFile, &passedFileType)) {
                if (passedFileType == Utils::DocumentType::Vivy) {
                    selectedVivyDoc = documentStore->loadDocument(passedFileName);
                    logInfo() << "Select vivy document " << passedFileName;
                } else if (passedFileType == Utils::DocumentType::VivyScript) {
                    selectedScriptDoc = scriptStore->loadDocument(passedFileName);
                    logInfo() << "Select script document " << passedFileName;
                } else {
                    logError() << "Not handled file type for file " << passedFileName;
                }
            } else {
                logError() << "Unrecognized file type for file " << passedFileName;
            }
        }

        selectedType = ApplicationType::CLI;
        VIVY_LOG_CTOR() << "Select the ApplicationType::CLI";
    }
}

VivyApplication::~VivyApplication() noexcept {}

void
VivyApplication::setTheme(Theme theme) noexcept
{
    if (theme == Theme::System) {
        logInfo() << "Set theme to system theme";
        return;
    }

    else if (theme == Theme::QtCreator) {
        qtCreatorThemePtr.reset(new Vivy::Theme("QtCreator"));
        QSettings settings(QStringLiteral(":theme/design.creatortheme"), QSettings::IniFormat);
        qtCreatorThemePtr->readSettings(&settings);
        qtCreatorThemePtr->applyToApplication();
        return;
    }

    const QString sheet = theme == Theme::Dark ? QStringLiteral(":qdarkstyle/dark/style.qss")
                                               : QStringLiteral(":qdarkstyle/light/style.qss");

    QFile stylesheet(sheet);
    if (!stylesheet.exists()) {
        logFatal() << "Missing stylesheet!";
        exit(EXIT_FAILURE);
    } else {
        stylesheet.open(QFile::ReadOnly | QFile::Text);
        QTextStream stylesheetStream(&stylesheet);
        setStyleSheet(stylesheetStream.readAll());
        logInfo() << "Theme set using " << VIVY_LOG_QUOTED(sheet);
    }
}

int
VivyApplication::exec() noexcept
{
    // For MPV & Qt
    QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
    std::setlocale(LC_NUMERIC, "C");

    // Add fonts
    fontIdMonospace     = QFontDatabase::addApplicationFont(":/fonts/FiraCode-Regular.ttf");
    fontIdMonospaceBold = QFontDatabase::addApplicationFont(":/fonts/FiraCode-Bold.ttf");
    fontIdRegular       = QFontDatabase::addApplicationFont(":/fonts/NotoSans-Regular.ttf");
    fontIdItalic        = QFontDatabase::addApplicationFont(":/fonts/NotoSans-Italic.ttf");
    fontIdBold          = QFontDatabase::addApplicationFont(":/fonts/NotoSans-Bold.ttf");
    fontIdBoldItalic    = QFontDatabase::addApplicationFont(":/fonts/NotoSans-BoldItalic.ttf");

    // Setup some things
    setAttribute(Qt::AA_DontShowIconsInMenus, false);
    setAttribute(Qt::AA_DontShowShortcutsInContextMenus, false);
    setFont(getApplicationFont(Font::Default));
    setTheme(Theme::QtCreator); // TODO: Set system theme for now (because we
                                // don't have a central theme provider).

    // Cursor blinking
    setCursorFlashTime(0);

    switch (selectedType) {
    case ApplicationType::CLI: {
        if ((selectedVivyDoc == nullptr) || (selectedScriptDoc == nullptr))
            return 1;

        if (!scriptStore->executeScript(selectedScriptDoc->getUuid(), selectedVivyDoc))
            return 2;

        for (const auto &str : scriptStore->getLoadedModules()) {
            std::cout << "Module " << str << " was loaded!\n";
            [[maybe_unused]] const auto *mod = scriptStore->getModule(str);
        }
        return 0;
    }

    case ApplicationType::GUI: {
        // Show the main window, also set up the log console
        mainWindowPtr               = std::make_unique<MainWindow>();
        QDockWidget *logConsoleDock = new QDockWidget("Console", mainWindowPtr.get());
        LogConsole *logConsole      = new LogConsole(mainWindowPtr.get());
        std::shared_ptr<ConsoleLogSinkDispatcher> consoleLogSinkDispatcher =
            logSink->newDispatcher<ConsoleLogSinkDispatcher>();

        DockWidgetTitleBar::addToDock(logConsoleDock);
        consoleLogSinkDispatcher->attachLogConsole(logConsole);
        logConsoleDock->setAllowedAreas(Qt::BottomDockWidgetArea);
        logConsoleDock->setFeatures(QDockWidget::DockWidgetMovable);
        logConsoleDock->setWidget(logConsole);
        mainWindowPtr->addDockWidget(Qt::BottomDockWidgetArea, logConsoleDock);
        mainWindowPtr->show();

        logInfo() << "Entering the main event loop";

        // Main loop
        return QApplication::exec();
    }
    }

    logFatal() << "Unreachable: undefined ApplicationType";
    return 1;
}

QFont
VivyApplication::getApplicationFont(Font id) const noexcept
{
    const auto getFontFromId = [](int fid) noexcept -> QFont {
        return QFont(QFontDatabase::applicationFontFamilies(fid).at(0));
    };

    switch (id) {
    case Font::Monospace: return getFontFromId(fontIdMonospace);
    case Font::MonospaceBold: return getFontFromId(fontIdMonospaceBold);
    case Font::DefaultItalic: return getFontFromId(fontIdItalic);
    case Font::DefaultBoldItalic: return getFontFromId(fontIdBoldItalic);
    case Font::DefaultBold: return getFontFromId(fontIdBold);
    case Font::Default: return getFontFromId(fontIdRegular);
    }

    // Let the program crash
    logError() << "Unreachable: undefined font id";
    return getFontFromId(fontIdRegular);
}

MainWindow *
VivyApplication::getMainWindow() const
{
    if (!mainWindowPtr)
        throw std::logic_error("No main window in the graphic VivyApplication");
    return mainWindowPtr.get();
}

AbstractDocument *
VivyApplication::getCurrentDocument() const
{
    if (!mainWindowPtr)
        throw std::logic_error("No main window in the graphic VivyApplication");
    return mainWindowPtr.get()->getCurrentDocument();
}

bool
VivyApplication::getUseFakeVimEditor() const noexcept
{
    return useFakeVim;
}

void
VivyApplication::setUseFakeVimEditor(bool ok) noexcept
{
    useFakeVim = ok;
}