#include <QApplication>
#include <QMessageBox>
#include <QStyleFactory>

#include <cstdlib>
#include <iostream>
using namespace std;

#include "command.h"
#include "debuginfo.h"
#include "findresultwidget.h"
#include "findwidget.h"
#include "flagrow.h"
#include "flagrowobj.h"
#include "headingeditor.h"
#include "macros.h"
#include "mainwindow.h"
#include "noteeditor.h"
#include "options.h"
#include "scripteditor.h"
#include "scriptoutput.h"
#include "settings.h"
#include "shortcuts.h"
#include "taskeditor.h"
#include "taskmodel.h"
#include "version.h"
#include "warningdialog.h"

#if defined(VYM_DBUS)
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusError>
#endif

QString vymName;
QString vymVersion;
QString vymHome;
QString vymBuildDate;
QString vymCodeName;
QString vymCodeQuality;
QString vymInstanceName;
QString vymPlatform;
QString localeName;

QTextStream vout(stdout); // vymout - Testing for now. Flush after writing...

// Accessing JIRA and Confluence is done using agents
// Credentials may be stored in settings, but only on request
QString jiraPassword;   
QString confluencePassword;

TaskModel *taskModel;
TaskEditor *taskEditor;
ScriptEditor *scriptEditor;
ScriptOutput *scriptOutput;
HeadingEditor *headingEditor;
NoteEditor *noteEditor; // used in Constr. of LinkableMapObj
BranchPropertyEditor *branchPropertyEditor;

// initialized in mainwindow
Main *mainWindow;
FindWidget *findWidget;
FindResultWidget *findResultWidget;

FlagRowMaster *systemFlagsMaster;
FlagRowMaster *standardFlagsMaster;
FlagRowMaster *userFlagsMaster;

Macros macros;

ulong itemLastID = 0;  // Unique ID for all items in all models
ulong imageLastID = 0; // Unique ID for caching images, also flags not in tree

QDir tmpVymDir;          // All temp files go there, created in mainwindow
QDir cacheDir;            // tmp dir with cached svg files in tmpVymDir
QString clipboardDir;    // Clipboard used in all mapEditors
QString clipboardFile;   // Clipboard used in all mapEditors

QDir vymBaseDir;            // Containing all styles, scripts, images, ...

QDir vymTranslationsDir;    // Translation files (*.qm)
QTranslator vymTranslator;

QDir lastImageDir;
QDir lastMapDir;
QDir lastExportDir;
#if defined(Q_OS_WINDOWS)
QDir vymInstallDir;
#endif
QString iconPath;  // Pointing to icons used for toolbars
QString flagsPath; // Pointing to flags

bool debug;                // global debugging flag
bool testmode;             // Used to disable saving of autosave setting
bool restoreMode = false;  // During restore, existing lockfiles are ignored

QStringList ignoredLockedFiles;
QStringList lastSessionFiles;   //! Will be overwritten in setting after load, so read initially

Switchboard switchboard;

Settings settings("InSilmaril", "vym"); // Organization, Application name

bool zipToolAvailable = false;
bool unzipToolAvailable = false;
QString zipToolPath;   // Platform dependant zip tool
QString unzipToolPath; // For windows same as zipToolPath

QList<Command *> modelCommands;
QList<Command *> vymCommands;

Options options;
ImageIO imageIO;

int statusbarTime = 10000;

bool usingDarkTheme;
QColor vymBlue;

int warningCount = 0;
int criticalCount = 0;
int fatalCount = 0;

QString editorFocusStyle =
    QString(" border-color: #3daee9; border-style:outset; border-width:3px; "
            "color:black;");

#include <QScriptEngine>
QScriptValue scriptPrint(QScriptContext *ctx, QScriptEngine *eng);

void msgHandler(QtMsgType type, const QMessageLogContext &context,
                const QString &msg)
{
    QByteArray localMsg = msg.toLocal8Bit();
    switch (type) {
    case QtDebugMsg:
        fprintf(stderr, "%s (%s:%u, %s)\n", localMsg.constData(), context.file,
                context.line, context.function);
        break;
    case QtWarningMsg:
        fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(),
                context.file, context.line, context.function);
        warningCount++;
        break;
    case QtCriticalMsg:
        fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(),
                context.file, context.line, context.function);
        criticalCount++;
        break;
    case QtFatalMsg:
        fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(),
                context.file, context.line, context.function);
        fatalCount++;
        break;
    default:
        fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(),
                context.file, context.line, context.function);
    }
}

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    // Define some constants shared in various places
    vymName = __VYM_NAME;
    vymVersion = __VYM_VERSION;
    vymBuildDate = __VYM_BUILD_DATE;
    vymCodeName = __VYM_CODENAME;
    vymCodeQuality = __VYM_CODE_QUALITY;
    vymHome = __VYM_HOME;

    // Install our own handler for messages
    qInstallMessageHandler(msgHandler);

    // Testing for now
    vout.setCodec("UTF-8");

    // Reading and initializing options commandline options
    options.add("batch", Option::Switch, "b", "batch");
    options.add("commands", Option::Switch, "c", "commands");
    options.add("commandslatex", Option::Switch, "cl", "commandslatex");
    options.add("debug", Option::Switch, "d", "debug");
    options.add("help", Option::Switch, "h", "help");
    options.add("load", Option::String, "L", "load");
    options.add("local", Option::Switch, "l", "local");
    options.add("locale", Option::String, "locale", "locale");
    options.add("name", Option::String, "n", "name");
    options.add("quit", Option::Switch, "q", "quit");
    options.add("run", Option::String, "R", "run");
    options.add("recover", Option::Switch, "recover", "recover");
    options.add("restore", Option::Switch, "r", "restore");
    options.add("shortcuts", Option::Switch, "s", "shortcuts");
    options.add("testmode", Option::Switch, "t", "testmode");
    options.add("version", Option::Switch, "v", "version");
    options.setHelpText(
        "VYM - View Your Mind\n"
        "--------------------\n\n"
        "Information about vym can be found in vym.pdf,\n"
        "which should be part of the vym package.\n"
        "It is also available at the project homepage:\n\n"
        "http://www.InSilmaril.de/vym\n\n"
        "Usage: vym [OPTION]... [FILE]... \n"
        "Open FILEs with vym\n\n"
        "-b           batch         batch mode: hide windows\n"
        "-c           commands	    List all available commands\n"
        "-cl          commandslatex List commands in LaTeX format\n"
        "-d           debug         Show debugging output\n"
        "-h           help          Show this help text\n"
        "-L           load          Load script\n"
        "-l           local         Run with ressources in current directory\n"
        "--locale     locale        Override system locale setting to select\n"
        "                           language\n"
        "-n  STRING   name          Set name of instance for DBus access\n"
        "-q           quit          Quit immediatly after start for benchmarking\n"
        "-R  FILE     run           Run script\n"
        "-r           restore       Restore last session\n"
        "--recover    recover       Delete lockfiles during initial loading of\n"
        "                           files\n"
        "-s           shortcuts     Show Keyboard shortcuts on start\n"
        "-t           testmode      Test mode, e.g. no autosave and changing\n"
        "                           of its setting\n"
        "-v           version       Show vym version\n");

    if (options.parse()) {
        cout << endl << qPrintable(options.getHelpText()) << endl;
        return 1;
    }

    if (options.isOn("version")) {
        QString s = QString("VYM - View Your Mind (c) 2004-%1").arg(QDate::currentDate().year());
        s += " Uwe Drechsel\n";
        s += "   Version: " + vymVersion;
        if (!vymCodeName.isEmpty())
            s += QString(" - \"%1\"").arg(vymCodeName);
        s += "\n";
        s += "   Quality: " + vymCodeQuality + "\n";
        s += "Build date: " + vymBuildDate + "\n";
        cout << s.toStdString();

        return 0;
    }

    taskModel = new TaskModel();

    debug = options.isOn("debug");

    testmode = options.isOn("testmode");

    QString pidString = QString::number(QCoreApplication::applicationPid());

#if defined(VYM_DBUS)
    // Register for DBUS
    QDBusConnection dbusConnection = QDBusConnection::sessionBus();
    if (!dbusConnection.registerService("org.insilmaril.vym-" + pidString)) {
        fprintf(
            stderr, "%s\n",
            qPrintable(QDBusConnection::sessionBus().lastError().message()));
        exit(1);
    }
#endif

    if (options.isOn("name"))
        vymInstanceName = options.getArg("name");
    else
        vymInstanceName = pidString;

#ifdef QT_DEBUG
    qDebug() << "QT_DEBUG is set";
    debug = true;
#endif

    // Use /usr/share/vym or /usr/local/share/vym or . ?
    // First try options
    if (options.isOn("local")) {
        vymBaseDir.setPath(vymBaseDir.currentPath());
    }
    else
        // then look for environment variable
        if (getenv("VYMHOME") != 0) {
        vymBaseDir.setPath(getenv("VYMHOME"));
    }
    else
    // ok, let's find my way on my own
    {
#if defined(Q_OS_MACX)
        // Executable is in vym.app/Contents/MacOS, so go up first:
        vymBaseDir = QCoreApplication::applicationDirPath();
        vymBaseDir.cdUp();
        vymBaseDir.cd("Resources");
#elif defined(Q_OS_WINDOWS)
        vymBaseDir.setPath(QCoreApplication::applicationDirPath());
#else
        vymBaseDir.setPath(VYMBASEDIR);
#endif
    }

    // Platform specific settings
    vymPlatform = QSysInfo::prettyProductName();

#if defined(Q_OS_WINDOWS)
    // Only Windows 10 has tar. Older windows versions not supported.
    zipToolPath = "tar";
#else
    zipToolPath = "/usr/bin/zip";
    unzipToolPath = "/usr/bin/unzip";
#endif
    iconPath = vymBaseDir.path() + "/icons/";
    flagsPath = vymBaseDir.path() + "/flags/";

    // When running locally, use local macros. Otherwise settings are used
    if (options.isOn("local"))
        macros.setPath(vymBaseDir.path() + "/macros/macros.vys");
    else
        macros.setPath(
            settings
            .value("/macros/path", vymBaseDir.path() + "/macros/macros.vys")
            .toString());

    // Some directories
    QDir useDir;
    if (options.isOn("local"))
        useDir = QDir().current();
    else
        useDir = QDir().home();
    lastImageDir = useDir;
    lastMapDir = useDir;
    lastExportDir = useDir;

    if (options.isOn("help")) {
        cout << qPrintable(options.getHelpText()) << endl;
        return 0;
    }

    // Initialize translations
    if (options.isOn("locale"))
        localeName = options.getArg("locale");

    // Use dark theme depending on system appearance and preferences
    int text_hsv_value = app.palette().color(QPalette::WindowText).value();
    int bg_hsv_value = app.palette().color(QPalette::Base).value();
    bool systemSeemsDark = (text_hsv_value > bg_hsv_value);
    QString settingsDarkTheme = settings.value("/system/darkTheme", "system").toString();
    usingDarkTheme = false;
    if (settingsDarkTheme != "never") {
        if (settingsDarkTheme == "always" || (settingsDarkTheme == "system" && systemSeemsDark))
            usingDarkTheme = true;
    }

#if defined(Q_OS_WINDOWS)
    if (usingDarkTheme) {
        qApp->setStyle(QStyleFactory::create("fusion"));

        // On Windows, there is no dark palette predefined, let's do that on our own
        QPalette palette;
        palette.setColor(QPalette::Window, QColor(53,53,53));
        palette.setColor(QPalette::WindowText, Qt::white);
        palette.setColor(QPalette::Base, QColor(27, 30, 32));
        palette.setColor(QPalette::AlternateBase, QColor(53,53,53));
        palette.setColor(QPalette::ToolTipBase, Qt::white);
        palette.setColor(QPalette::ToolTipText, Qt::white);
        palette.setColor(QPalette::Text, Qt::white);
        palette.setColor(QPalette::Button, QColor(53,53,53));
        palette.setColor(QPalette::ButtonText, Qt::white);
        palette.setColor(QPalette::BrightText, Qt::red);
        palette.setColor(QPalette::Highlight, QColor(142,45,197).lighter());
        palette.setColor(QPalette::HighlightedText, Qt::black);
        qApp->setPalette(palette);
    }
#endif

    // Prepare and check translations
    vymTranslationsDir = QDir(vymBaseDir.path() + "/translations");
    vymTranslationsDir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);

    bool translationsMissing = false;
    if(!vymTranslationsDir.exists())
        translationsMissing = true;
    else if (vymTranslationsDir.isEmpty())
        translationsMissing = true;

    if (translationsMissing) {
        WarningDialog warn;
        warn.setMinimumWidth(800);
        warn.setMinimumHeight(350);
        warn.showCancelButton(false);
        warn.setShowAgainName("mainwindow/translations/qmFilesMissing");
        warn.setCaption("Translations not available");
        warn.setText(
                "vym has not been built correctly and only will be available in English: \n\n"
                "No translation files in\n" +
                vymTranslationsDir.path().toLatin1() + "\n\n" +
                "Please get vym from\n"
                " * https://sourceforge.net/projects/vym/  or \n"
                " * https://software.opensuse.org//download.html?project=home%3Ainsilmaril&package=vym");
        warn.exec();
    } else {
        bool ok;
        if (!localeName.isEmpty())
            // Use localeName to load specific language
            ok = vymTranslator.load(QString("vym.%1.qm").arg(localeName), vymTranslationsDir.path());
        else
            ok = vymTranslator.load(QLocale(), "vym", ".", vymTranslationsDir.path(), ".qm");

        if (!ok) {
            WarningDialog warn;
            warn.showCancelButton(false);
            warn.setText(
                QString("Couldn't load translation for locale \"%1\" from\n%2")
                    .arg(localeName)
                    .arg(vymTranslationsDir.path()));
            warn.setShowAgainName("mainwindow/translations/localeMissing");
            warn.exec();
        } else
            QCoreApplication::installTranslator(&vymTranslator);
    }

    // Initializing the master rows of flags
    systemFlagsMaster = new FlagRowMaster;
    systemFlagsMaster->setName("systemFlagsMaster");

    standardFlagsMaster = new FlagRowMaster;
    standardFlagsMaster->setName("standardFlagsMaster");
    standardFlagsMaster->setPrefix("standard/");

    userFlagsMaster = new FlagRowMaster;
    userFlagsMaster->setName("userFlagsMaster");
    userFlagsMaster->setPrefix("user/");

    // Initialize editors
    noteEditor = new NoteEditor("noteeditor");
    noteEditor->setWindowIcon(QPixmap(":/vym-editor.png"));
    headingEditor = new HeadingEditor("headingeditor");
    branchPropertyEditor = new BranchPropertyEditor();

    // Initially read filenames of last session, before settings are 
    // overwritten during loading of maps
    lastSessionFiles = settings.value("/mainwindow/sessionFileList", QStringList()).toStringList();


    Main m;

    // Check for zip tools
    checkZipTool();
    checkUnzipTool();

#if defined(Q_OS_WINDOWS)
    if (!zipToolAvailable || QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows10) {
        QMessageBox::critical(
            0, QObject::tr("Critical Error"),
            QObject::tr("Couldn't find tool to unzip data,"
                        "or your Windows version is older than Windows 10."));
        m.settingsZipTool();
    }
#else
    if (!zipToolAvailable || !unzipToolAvailable) {
        QMessageBox::critical(
            0, QObject::tr("Critical Error"),
            QObject::tr("Couldn't find tool to zip/unzip data. "
                        "Please install on your platform and set"
                        "path in Settings menu:\n ",
                        "zip tool missing on Linux/Mac platform"));
        m.settingsZipTool();
    }
#endif

    m.setWindowIcon(QPixmap(":/vym.png"));
    m.fileNew();

    if (debug)
        // Show debug info AFTER creating MainWindow
        cout << debugInfo().toStdString() << endl;

    if (options.isOn("commands")) {
        cout << "Available commands in map:\n";
        cout << "=========================:\n";
        foreach (Command *c, modelCommands)
            cout << c->getDescription().toStdString() << endl;

        cout << "Available commands in vym:\n";
        cout << "=========================:\n";
        foreach (Command *c, vymCommands)
            cout << c->getDescription().toStdString() << endl;
        return 0;
    }

    if (options.isOn("commandslatex")) {
        foreach (Command *c, modelCommands)
            cout << c->getDescriptionLaTeX().toStdString() << endl;
        foreach (Command *c, vymCommands)
            cout << c->getDescriptionLaTeX().toStdString() << endl;
        return 0;
    }

    if (options.isOn("batch"))
        m.hide();
    else {
        // Paint Mainwindow first time
        qApp->processEvents();
        m.show();
    }

    // Show release notes and afterwards updates
    m.checkReleaseNotesAndUpdates();

    if (options.isOn("shortcuts"))
        switchboard
            .printASCII(); // FIXME-3 global switchboard and exit after listing

    m.loadCmdLine();

    // Restore last session
    if (options.isOn("restore"))
        m.fileRestoreSession();

    // Load script
    if (options.isOn("load")) {
        QString fn = options.getArg("load");
        if (!scriptEditor->loadScript(fn)) {
            QString error(QObject::tr("Error"));
            QString msg(QObject::tr("Couldn't open \"%1\"\n.").arg(fn));
            if (options.isOn("batch"))
                qWarning() << error + ": " + msg;
            else
                QMessageBox::warning(0, error, msg);
            return 0;
        }
    }

    // Run script
    if (options.isOn("run")) {
        QString script;
        QString fn = options.getArg("run");
        if (!scriptEditor->loadScript(fn)) {
            QString error(QObject::tr("Error"));
            QString msg(QObject::tr("Couldn't open \"%1\"\n.").arg(fn));
            if (options.isOn("batch"))
                qWarning() << error + ": " + msg;
            else
                QMessageBox::warning(0, error, msg);
            return 0;
        }
        m.runScript(scriptEditor->getScriptFile());
    }

    // For benchmarking we may want to quit instead of entering event loop
    if (options.isOn("quit"))
        return 0;

    // Enable some last minute cleanup
    QObject::connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));

    app.exec();

    // Cleanup
    delete noteEditor;

    int s = warningCount + criticalCount + fatalCount;
    if (s > 0)
        qDebug() << "vym exiting with:\n"
                 << warningCount << " warning messages\n"
                 << criticalCount << " critical messages\n"
                 << fatalCount << " fatal messages";
    return s;
}
