#include <QCoreApplication>
#include <QApplication>
#include <QDebug>
#include <QTimer>
#include <QFileInfo>

#ifdef __linux__
#include <stdlib.h> // system()
#endif

#include "message_handler.h"
#include "plugins/interfaces.h"

#include <unistd.h>
#include <memory>
#include <QSharedMemory>
#include <QRunnable>
#include <QThreadPool>
#include <QDir>
#include <QProcess>
#include <QCommandLineParser>
#include <QStandardPaths>
#include <QMainWindow>

#include "update.h"
#include "git/git_client.h"
#include "ismas/ismas_client.h"
#include "worker_thread.h"
#include "worker.h"
#include "mainwindow.h"
#include "utils.h"

#include <QThread>
#include <QtWidgets>
#include <QScopedPointer>

#if defined (Q_OS_UNIX) || defined (Q_OS_LINUX)
#include <unistd.h>
#include <errno.h>
#endif

#ifdef PTU5
#define SERIAL_PORT "ttymxc2"
#else
#define SERIAL_PORT "ttyUSB0"
#endif

// argv[1]: file to send to dc
int main(int argc, char *argv[]) {
    QByteArray const value = qgetenv("LC_ALL");
    if (value != "C") {
        qputenv("LC_ALL", "C");
    }
    // qputenv("XDG_RUNTIME_DIR", "/run/user/0");

    openlog("ATB-UPDATE", LOG_PERROR | LOG_PID | LOG_CONS, LOG_USER);

    QApplication a(argc, argv);
    QApplication::setApplicationName("ATBUpdateTool");
    QApplication::setApplicationVersion(APP_VERSION);

    if (!messageHandlerInstalled()) { // change internal qt-QDebug-handling
        atbInstallMessageHandler(atbDebugOutput);
        setDebugLevel(LOG_NOTICE);
    }

//#if defined (Q_OS_UNIX) || defined (Q_OS_LINUX)
//#ifdef _POSIX_THREAD_PROCESS_SHARED
//    errno = 0;
//    int res = 0;
//    if ((res = sysconf(_SC_THREAD_PROCESS_SHARED)) < 0) {
//        if (errno != 0) {
//            qCritical() << "_POSIX_THREAD_PROCESS_SHARED NOT SUPPORTED"
//                        << strerror(errno);
//            exit(-1);
//        }
//    } else {
//        if (res == _POSIX_THREAD_PROCESS_SHARED) {
//            Utils::printInfoMsg("_POSIX_THREAD_PROCESS_SHARED SUPPORTED");
//        }
//    }
//#endif
//#endif

    QCommandLineParser parser;
    parser.setApplicationDescription("Download tool for downloading device controller firmware, printer json-files and executing opkg-commands.");
    parser.addHelpOption();
    parser.addVersionOption();

    QCommandLineOption pluginDirectoryOption(QStringList() << "plugin-directory" << "plugin-directory",
        QCoreApplication::translate("main", "Where to find dc-plugin."),
        QCoreApplication::translate("main", "directory"));
    QString const pluginDefault = "/usr/lib";
    pluginDirectoryOption.setDefaultValue(pluginDefault);
    parser.addOption(pluginDirectoryOption);

    QCommandLineOption pluginNameOption(QStringList() << "plugin-name" << "plugin-name",
        QCoreApplication::translate("main", "Name of dc-plugin."),
        QCoreApplication::translate("main", "directory"));
    QString const pluginNameDefault = "libCAslave.so";
    pluginNameOption.setDefaultValue(pluginNameDefault);
    parser.addOption(pluginNameOption);

    QCommandLineOption workingDirectoryOption(QStringList() << "working-directory" << "working-directory",
        QCoreApplication::translate("main", "working directory of update-script."),
        QCoreApplication::translate("main", "directory"));
    QString const workingDirectoryDefault = "/opt/app/tools/atbupdate";
    workingDirectoryOption.setDefaultValue(workingDirectoryDefault);
    parser.addOption(workingDirectoryOption);

    QCommandLineOption dryRunOption(QStringList() << "d" << "dry-run",
        QCoreApplication::translate("main", "Start ATBUpdateTool in dry-run-mode. No actual actions."));
    parser.addOption(dryRunOption);

    // Process the actual command line arguments given by the user
    parser.process(a);
    QString plugInDir = parser.value(pluginDirectoryOption);
    QString plugInName = parser.value(pluginNameOption);
    QString workingDir = parser.value(workingDirectoryOption);
    bool dryRun = parser.isSet(dryRunOption);
    QString const rtPath = QCoreApplication::applicationDirPath();

    if (!QDir(plugInDir).exists()) {
        qCritical() << plugInDir
                    << "does not exists, but has to contain dc-library";
        exit(-1);
    }

    qInfo() << "pwd ..............." << rtPath;
    qInfo() << "plugInDir ........." << plugInDir;
    qInfo() << "plugInName ........" << plugInName;
    qInfo() << "workingDir ........" << workingDir;
    qInfo() << "dryRun ............" << dryRun;

    // before loading the library, delete all possible shared memory segments
#if defined Q_OS_LINUX || defined Q_OS_UNIX
    // system("rm -rf /tmp/qipc*");
#else
#error "Only tested under UNIX/LINUX"
#endif

    hwinf *hw = Update::loadDCPlugin(QDir(plugInDir), plugInName);
    hw->dc_autoRequest(true);
    // hw->dc_openSerial(5, "115200", "ttymxc2", 1);

    int machineNr = Utils::read1stLineOfFile("/etc/machine_nr");
    int customerNr = Utils::read1stLineOfFile("/etc/cust_nr");
    int zoneNr = Utils::read1stLineOfFile("/etc/zone_nr");
    QString const branchName = (zoneNr != 0)
            ? QString("zg1/zone%1").arg(zoneNr) : "master";

    QThread::currentThread()->setObjectName("main thread");
    qInfo() << "Main thread" << QThread::currentThreadId();

    Worker worker(customerNr,
                  machineNr,
                  zoneNr,
                  branchName,
                  plugInName,
                  workingDir,
                  dryRun);

    QString const customerNrStr(
        QString("customer_") + QString::number(customerNr).rightJustified(3, '0'));

    QScopedPointer<Update> update(
        new Update(hw,
                   &worker,
                   QDir::cleanPath(workingDir + QDir::separator() + customerNrStr),
                   customerNrStr,
                   branchName,
                   plugInName,
                   workingDir,
                   dryRun,
                   nullptr,
                   SERIAL_PORT,
                   "115200"));

    MainWindow mw(hw, &worker, update.get());
    worker.setMainWindow(&mw);

    mw.setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
    mw.showFullScreen();

    return a.exec();
}
