#include "utils.h"
#include "message_handler.h"
#include "git/git_client.h"
#include "worker.h"


#if defined (Q_OS_UNIX) || defined (Q_OS_LINUX)
#include "unistd.h"
#endif

#include <QObject>
#include <QFile>
#include <QTextStream>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QRegularExpression>
#include <QProcess>
#include <QJsonDocument>
#include <QJsonValue>
#include <QJsonObject>
#include <QJsonArray>
#include <QStringList>
#include <QDirIterator>

#include <fstream>

QVector<QPair<QString, QString>> Utils::installedJsonFiles(Worker const *worker, QDir const &customerDir) {
    QVector<QPair<QString, QString>> vec;
    QStringList fileList;

    QDirIterator it(QDir::cleanPath(customerDir.absolutePath() + QDir::separator() + "etc/psa_config"));
    while (it.hasNext()) {
        QFileInfo const fi(it.next());
        if (fi.fileName().startsWith("DC2C") && fi.fileName().endsWith(".json")) {
            fileList << fi.absoluteFilePath();
        }
    }

    fileList.sort();

    QString const &current = QDir::current().absolutePath();

    if (!QDir::setCurrent(customerDir.absolutePath())) {
        qCritical() << __func__ << ":" << __LINE__ << ": ERROR: can not set"
                    << "working directory to" << customerDir.absolutePath();
    } else {

        for (int i = 0; i < fileList.size(); ++i) {

            QProcess p;

            // connect(&p, SIGNAL(finished(int,QProcess::ExitStatus)), , SLOT(finished(int,QProcess::ExitStatus)));

            QStringList params;
            params << "log" << "-n" << "1" << "--pretty=format:%H" << "--" << fileList[i];

            //QObject::connect(&p, SIGNAL(readyReadStandardOutput()),
            //        worker, SLOT(Worker::readyReadStandardOutput()), Qt::DirectConnection);

            p.start("git", params);
            p.waitForReadyRead();
            p.write("exit");
            p.waitForFinished();

            QString r = p.readAll().left(8);

            //QObject::disconnect(&p, SIGNAL(readyReadStandardOutput()), worker, SLOT(Worker::readyReadStandardError()));

            qCritical() << QDir::current().absolutePath()
                        << "JS git log -n 1 --pretty=format:%H -- " << fileList[i] << r;

            vec.push_back(QPair<QString, QString>(QFileInfo(fileList[i]).fileName(), r));
        }

        if (!QDir::setCurrent(current)) {
            qCritical() << __func__ << ":" << __LINE__ << ": ERROR: can not set"
                        << "working directory to" << current;
        }
    }

    return vec;
}

QVector<QPair<QString, QString>> Utils::installedTariffFiles(Worker const *worker, QDir const &customerDir) {
    QVector<QPair<QString, QString>> vec;
    QStringList fileList;

    QDirIterator it(QDir::cleanPath(customerDir.absolutePath() + QDir::separator() + "etc/psa_tariff"));
    while (it.hasNext()) {
        QFileInfo const fi(it.next());
        if (fi.fileName().startsWith("tariff") && fi.fileName().endsWith(".json")) {
            fileList << fi.absoluteFilePath();
        }
    }

    fileList.sort();

    QString const &current = QDir::current().absolutePath();

    if (!QDir::setCurrent(customerDir.absolutePath())) {
        qCritical() << __func__ << ":" << __LINE__ << ": ERROR: can not set"
                    << "working directory to" << customerDir.absolutePath();
    } else {

        for (int i = 0; i < fileList.size(); ++i) {

            QProcess p;
            QStringList params;
            params << "log" << "-n" << "1" << "--pretty=format:%H" << "--" << fileList[i];

            //QObject::connect(&p, SIGNAL(readyReadStandardOutput()),
            //        worker, SLOT(Worker::readyReadStandardOutput()), Qt::DirectConnection);

            p.start("git", params);
            p.waitForReadyRead();
            p.write("exit");
            p.waitForFinished();

            QString r = p.readAll().left(8);
            // QObject::disconnect(&p, SIGNAL(readyReadStandardOutput()), worker, SLOT(Worker::readyReadStandardError()));

            qCritical() << QDir::current().absolutePath()
                        << "git log -n 1 --pretty=format:%H -- " << fileList[i] << r;

            vec.push_back(QPair<QString, QString>(QFileInfo(fileList[i]).fileName(), r));
        }

        if (!QDir::setCurrent(current)) {
            qCritical() << __func__ << ":" << __LINE__ << ": ERROR: can not set"
                        << "working directory to" << current;
        }
    }

    return vec;
}

QVector<QPair<QString, QString>> Utils::installedPackages() {
    QVector<QPair<QString, QString>> vec;
    if (QFile::exists("/usr/bin/")) {
        QProcess p;
        QStringList params;
        params << "-c" << R"(/usr/bin/ptuPackageVersions -i -o json)";

        p.start("bash", params);
        p.waitForFinished();

        QString r = p.readAllStandardOutput();

        // ptuPackageVersions returns a json-array
        QJsonArray const &ja = QJsonDocument::fromJson(r.remove(QRegExp("\\n")).toUtf8()).array();
        if (!ja.empty()) {
            qCritical() << __LINE__;
            // transform the array into an object, containing the objects
            // of the array (christian needs it this way)
            foreach (QJsonValue const &value, ja) {
                if (value.isObject()) {
                    QJsonObject obj = value.toObject();
                    QStringList keys = obj.keys();
                    if (!keys.isEmpty()) {
                        QString const &k = keys.first();
                        QJsonValue const &v = obj.value(k);
                        if (v.isObject()) {
                            obj = v.toObject();
                            if (obj.keys().contains("Version")) {
                                QJsonValue const &w = obj.value("Version");
                                if (w.isString()) {
                                    QString s = w.toString();
                                    QPair<QString, QString> p(k, s);
                                    vec.push_back(p);
                                }
                            }
                        }
                    }
                }
            }
        } else {
            qCritical() << __func__ << ":" << __LINE__
                        << "ERROR array return by ptuPackageVersions empty";
        }
    } else {
        qCritical() << __func__ << ":" << __LINE__
                    << "ERROR executing ptuPackageVersions";
    }

    return vec;
}

int Utils::read1stLineOfFile(QString fileName) {
    QFile f(fileName);
    if (f.exists()) {
        if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QTextStream in(&f);
            in.setCodec("UTF-8");
            while(!in.atEnd()) {
                return in.readLine().toInt();
            }
        }
    }
    return -1;
}

QString Utils::getLocation(QString fileName) {
    QString location("N/A");
    QFile f(fileName);
    if (f.exists()) {
        if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QTextStream in(&f);
            in.setCodec("UTF-8");
            while(!in.atEnd()) {
                QString const &line = in.readLine();
                if (line.indexOf("Project", Qt::CaseInsensitive) != -1) {
                    int const c = line.indexOf(":");
                    if (c != -1) {
                        location = line.mid(c+1);
                        if (!location.isEmpty()) {
                            location = location.replace(QChar(','), QString(""));
                            return location.replace(QChar('"'), QString("")).trimmed();
                        }
                    }
                }
            }
        }
    }

    return location;
}

QString Utils::getTariffVersion(QString fileName) {
    QString version("N/A");
    QFile f(fileName);
    if (f.exists()) {
        if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QTextStream in(&f);
            in.setCodec("UTF-8");
            while(!in.atEnd()) {
                QString const &line = in.readLine();
                if (line.indexOf("Version", Qt::CaseInsensitive) != -1) {
                    int const c = line.indexOf(":");
                    if (c != -1) {
                        version = line.mid(c+1);
                        if (!version.isEmpty()) {
                            version = version.replace(QChar(','), QString(""));
                            return version.replace(QChar('"'), QString("")).trimmed();
                        }
                    }
                }
            }
        }
    }

    return version;
}

QString Utils::getTariffInfo(QString fileName) {
    QString info("N/A");
    QFile f(fileName);
    if (f.exists()) {
        if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QTextStream in(&f);
            in.setCodec("UTF-8");
            while(!in.atEnd()) {
                QString const &line = in.readLine();
                if (line.indexOf("Info", Qt::CaseInsensitive) != -1) {
                    int const c = line.indexOf(":");
                    if (c != -1) {
                        info = line.mid(c+1);
                        if (!info.isEmpty()) {
                            info = info.replace(QChar(','), QString(""));
                            return info.replace(QChar('"'), QString("")).trimmed();
                        }
                    }
                }
            }
        }
    }

    return info;

}

QString Utils::zoneName(quint8 /* i */) {
    //static constexpr char const *zName[] = {
    //    "",
    //    "purple",
    //    "blue",
    //    "yellow",
    //    "green",
    //    "yellow (mars)",
    //    "green (mars)"
    //};
    //if (i < (sizeof(zName)/sizeof(char const *))) {
    //    return zName[i];
    //}
    return "---";
}

void Utils::printCriticalErrorMsg(QString const &errorMsg, bool upper, bool lower) {
    if (upper) qCritical() << QString(80, 'E');

    qCritical() << errorMsg;

    if (lower) qCritical() << QString(80, 'E');
}

void Utils::printCriticalErrorMsg(QStringList const &errorMsg) {
    qCritical() << QString(80, 'E');
    for (int i = 0; i < errorMsg.size(); ++i) {
        qCritical() << errorMsg.at(i);
    }
    qCritical() << QString(80, 'E');
}

void Utils::printUpdateStatusMsg(QDebug debug, QStringList const &updateMsg) {
    //if (updateMsg.size() > 1) {
    //    qCritical() << QString(80, 'U');
    //}

    Q_UNUSED(debug);

    for (int i = 0; i < updateMsg.size(); ++i) {
        qInfo() << updateMsg.at(i);
    }

    //if (updateMsg.size() > 1) {
    //    qCritical() << QString(80, 'U');
    //}
}

void Utils::printUpdateStatusMsg(QStringList const &updateMsg) {
    //if (updateMsg.size() > 1) {
    //    qCritical() << QString(80, 'U');
    //}

    for (int i = 0; i < updateMsg.size(); ++i) {
        qCritical() << updateMsg.at(i);
    }

    //if (updateMsg.size() > 1) {
    //    qCritical() << QString(80, 'U');
    //}
}

void Utils::printUpdateStatusMsg(QString const &updateMsg, bool upper, bool lower) {
    if (upper) qCritical() << QString(80, 'U');

    qCritical() << updateMsg;

    if (lower) qCritical() << QString(80, 'U');
}

void Utils::printUpdateStatusMsg(QDebug debug, QString const &updateMsg,
                                 bool upper, bool lower) {
    if (upper) debug << QString(80, 'U');

    qInfo() << updateMsg;

    if (lower) debug << QString(80, 'U');

}

void Utils::printInfoMsg(QString const &infoMsg, bool upper, bool lower) {
    if (upper) qCritical() << QString(80, 'I');

    qCritical() << infoMsg;

    if (lower) qCritical() << QString(80, 'I');
}

void Utils::printInfoMsg(QStringList const &infoMsg) {
    //if (infoMsg.size() > 1) {
    //    qCritical() << QString(80, 'I');
    //}

    for (int i = 0; i < infoMsg.size(); ++i) {
        qCritical() << infoMsg.at(i);
    }

    //if (infoMsg.size() > 1) {
    //    qCritical() << QString(80, 'I');
    //}
}

void Utils::printLineEditInfo(QStringList const &lines) {
    if (getDebugLevel() == LOG_DEBUG) {
        for (int i=0; i<lines.size(); ++i) {
            qInfo() << lines.at(i);
        } qInfo() << ""; qInfo() << "";
    }
}

QString Utils::getTariffLoadTime(QString fileName) {
    QFileInfo fInfo(fileName);
    if (fInfo.exists()) {
        QDateTime lastModifiedTime = fInfo.lastModified();
        if (lastModifiedTime.isValid()) {
            return lastModifiedTime.toString(Qt::ISODateWithMs);
        } else {
            printCriticalErrorMsg(fileName + " HAS INVALID MODIFIED-TIME");
            QDateTime birthTime = fInfo.birthTime();
            if (birthTime.isValid()) {
                return birthTime.toString(Qt::ISODateWithMs);
            } else {
                printCriticalErrorMsg(fileName + " HAS INVALID BIRTH-TIME");
            }
        }
    } else {
        printCriticalErrorMsg(fileName + " DOES NOT EXIST");
    }
    return "N/A";
}

QString Utils::rstrip(QString const &str) {
    int n = str.size() - 1;
    for (; n >= 0; --n) {
        if (!str.at(n).isSpace()) {
            return str.left(n + 1);
        }
    }
    return "";
}

bool Utils::sameFilesInDirs(QDir const &dir1, QDir const &dir2,
                            QStringList const &nameFilters) {
    if (!dir1.exists()) {
        printCriticalErrorMsg(dir1.dirName() + " DOES NOT EXIST");
        return false;
    }
    if (!dir2.exists()) {
        printCriticalErrorMsg(dir2.dirName() + " DOES NOT EXIST");
        return false;
    }
    if (dir1.absolutePath() == dir2.absolutePath()) {
        printCriticalErrorMsg(dir1.dirName() + " AND "+ dir2.dirName() + " HAVE SAME PATH");
        return false;
    }

    // files, sorted by name
    QFileInfoList const &lst1 = dir1.entryInfoList(nameFilters, QDir::Files, QDir::Name);
    QFileInfoList const &lst2 = dir2.entryInfoList(nameFilters, QDir::Files, QDir::Name);

    QStringList fileNameLst1{};
    QStringList fileNameLst2{};
    QListIterator<QFileInfo> i1(lst1);
    while (i1.hasNext()) {
        fileNameLst1 << i1.next().fileName();
    }
    QListIterator<QFileInfo> i2(lst2);
    while (i2.hasNext()) {
        fileNameLst2 << i2.next().fileName();
    }

    QString dirPath1 = dir1.absolutePath();
    QString dirPath2 = dir2.absolutePath();

    if (fileNameLst1.isEmpty()) {
        qCritical() << "DIR1" << dirPath1 << " DOES NOT CONTAIN EXPECTED FILES";
        return false;
    }
    if (fileNameLst2.isEmpty())  {
        qCritical() << "DIR1" << dirPath2 << " DOES NOT CONTAIN EXPECTED FILES";
        return false;
    }
    if (fileNameLst1 != fileNameLst2) {
        printCriticalErrorMsg(dirPath1 + " AND " + dirPath2
                            + " HAVE DIFFERENT FILES: [" + fileNameLst1.join(',') + "],["
                            + fileNameLst2.join(',') + "]");
        return false;
    } else {
        printInfoMsg(dirPath1 + " AND " + dirPath2
                    + " ARE EQUAL: [" + fileNameLst1.join(',') + "]");
    }

    QStringList gitBlobLst1{};
    QStringList gitBlobLst2{};
    QListIterator<QFileInfo> i3(lst1);
    while (i3.hasNext()) {
        gitBlobLst1 << GitClient::gitBlob(i3.next().fileName());
    }
    QListIterator<QFileInfo> i4(lst2);
    while (i4.hasNext()) {
        gitBlobLst2 << GitClient::gitBlob(i4.next().fileName());
    }

    if (gitBlobLst1.isEmpty()) {
        qCritical() << "DIR1" << dirPath1 << " DOES NOT CONTAIN EXPECTED FILES";
        return false;
    }
    if (gitBlobLst2.isEmpty())  {
        qCritical() << "DIR1" << dirPath2 << " DOES NOT CONTAIN EXPECTED FILES";
        return false;
    }

    if (gitBlobLst1 != gitBlobLst2) {
        printCriticalErrorMsg(dirPath1 + " AND " + dirPath2
                            + " HAVE DIFFERENT BLOBS: [" + gitBlobLst1.join(',') + "],["
                            + gitBlobLst2.join(',') + "]");
        return false;
    } else {
        printInfoMsg(dirPath1 + " AND " + dirPath2
                    + " CONTAIN SAME GIT-BLOBS FOR FILES: [" + fileNameLst1.join(',') + "]");

    }

    return true;
}


QString Utils::getParentName() { // get name of parent process
    QString ppid = QString("/proc/%1/status").arg(getppid());
    std::ifstream f(ppid.toStdString().c_str());
    if (f.is_open()) {
        std::string next;
        while (std::getline(f, next)) {
            QString line = QString(next.c_str()).simplified();
            if (line.startsWith("Name")) {
                int const idx = line.indexOf(QChar(':'));
                if (idx != -1) {
                    return line.mid(idx+1).trimmed();
                }
            }
        }
    }
    return "";
}

bool Utils::isATBQTRunning() {
    QDirIterator it("/proc",
                    QStringList() << "status",
                    QDir::Files,
                    QDirIterator::Subdirectories);
    while (it.hasNext()) {
        QString const &nextStatusFile = it.next();
        static const QRegularExpression re("^/proc/[0-9]{1,}/status");
        QRegularExpressionMatch match = re.match(nextStatusFile);
        if (match.hasMatch()) {
            std::ifstream f(nextStatusFile.toStdString().c_str());
            if (f.is_open()) {
                std::string next;
                while (std::getline(f, next)) {
                    QString line = QString(next.c_str()).simplified();
                    if (line.startsWith("Name")) {
                        int const idx = line.indexOf(QChar(':'));
                        if (idx != -1) {
                            QString const binary = line.mid(idx+1).trimmed();
                            if (binary == "ATBQT") {
                                return true;
                            }
                        }
                    }
                }
            }
        }
    }
    return false;
}