#include "command.h"
#include "worker.h"

#include <QProcess>
#include <QDebug>
#include <QDir>
#include <QRegularExpression>
#include <QDateTime>
#include <QMutexLocker>


Command::Command(QString const &command, int start_timeout, int finish_timeout)
    : m_command(command.trimmed())
    , m_commandResult("")
    , m_waitForStartTimeout(start_timeout)
    , m_waitForFinishTimeout(finish_timeout)
    , m_exitCode(-1)
    , m_p(nullptr)
    , m_worker(nullptr) {
}

QString Command::getCommandResult(bool reset) const {
    QMutexLocker locker(&m_mtx);

    if (reset == false) {
        return m_commandResult;
    }

    QString commandResult = m_commandResult;
    m_commandResult.clear();

    return commandResult;
}

void Command::readyReadStandardOutput() {
    QMutexLocker locker(&m_mtx);
    QProcess *p = (QProcess *)sender();
    if (p) {
        QString s = p->readAllStandardOutput();

        // qCritical() << __func__ << ":" << __LINE__ << s;

        if (m_worker) {
            int i = -1;
            if ((i = s.indexOf("<DC-VERSION>")) != -1) {
                s = s.mid(i+12).trimmed();
                if ((i = s.indexOf("\"")) != -1) {
                    s = s.mid(i+1);
                    if ((i = s.indexOf("\"")) != -1) {
                        s = s.mid(0, i).trimmed();
                    }
                }
                emit m_worker->showDcDownload(s);
            } else
            if ((i = s.indexOf("<DC-PROGRESS>")) != -1) {
                bool ok;
                int v = s.mid(i+13).trimmed().toInt(&ok);
                if (ok) {
                    emit m_worker->setDcDownloadProgress(v);
                    emit m_worker->insertText(s.mid(0,i) + "\n");
                }
            } else
            if ((i = s.indexOf("<DC-UPDATE-FINISH>")) != -1) {
                m_worker->summary();
                // qApp->exit(0);
            } else
            if ((i = s.indexOf("<DC-UPDATE-SUCCESS>")) != -1) {
                m_worker->summary();
            } else
            if ((i = s.indexOf("<DC-UPDATE-FAILURE>")) != -1) {
                m_worker->summary();
                //qApp->exit(-1);
            } else
            if ((i = s.indexOf("<JS-PROGRESS>")) != -1) {
                bool ok;
                int v = s.mid(i+13).trimmed().toInt(&ok);
                if (ok) {
                    emit m_worker->setDcDownloadProgress(v);
                    emit m_worker->insertText(s.mid(0,i) + "\n");
                }
            } else {
                emit m_worker->insertText(s);
            }
        }
        m_commandResult += s;
    }
}

void Command::readyReadStandardError() {
    QProcess *p = (QProcess *)sender();
    if (p) {
        QByteArray buf = p->readAllStandardError();
        qCritical() << buf;
    }
}

// TODO: nach UpdateCommands ziehen
void Command::finished(int /*exitCode*/, QProcess::ExitStatus /*exitStatus*/) {
    QProcess *p = (QProcess *)sender();
    // read all remaining data sent to the process, just in case
    QString d = p->readAllStandardOutput();
    if (!d.isEmpty()) {
        QMutexLocker locker(&m_mtx);
        m_commandResult += d;
    }
    disconnect(p, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(readyReadStandardOutput()));
    disconnect(p, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(readyReadStandardError()));
    disconnect(p, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(finished(int,QProcess::ExitStatus)));

    //if (m_command.contains("ATBDownloadDCJsonFiles")) {
    //    m_worker->dcUpdate();
    //}
}

// TODO: nach UpdateCommand ziehen
bool Command::start(QString workingDirectory, QStringList args) {
    if (!QDir::setCurrent(workingDirectory)) {
        qCritical() << "SET WORKING_DIRECTORY" << workingDirectory
                    << "FAILED FOR" << m_command;
        return false;
    }
    if (m_p != nullptr) {
        delete m_p;
    }

    qCritical() << "COMMAND" << m_command << workingDirectory << args;

    m_p = new QProcess(this);
    m_p->setWorkingDirectory(workingDirectory);
    m_p->setProcessChannelMode(QProcess::MergedChannels);

    connect(m_p, SIGNAL(readyReadStandardOutput()), this, SLOT(readyReadStandardOutput()));
    connect(m_p, SIGNAL(readyReadStandardError()), this, SLOT(readyReadStandardError()));
    connect(m_p, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(finished(int,QProcess::ExitStatus)));

    if (!args.isEmpty()) {
        m_p->start(m_command, args);
    } else {
        m_p->start(m_command);
    }

    return m_p->waitForStarted(m_waitForStartTimeout);
}

bool Command::execute(QString workingDirectory, QStringList args) {

    if (!QDir::setCurrent(workingDirectory)) {
        qCritical() << "SET WORKING_DIRECTORY" << workingDirectory
                    << "FAILED FOR" << m_command;
        return false;
    }

    QScopedPointer<QProcess> p(new QProcess(this));
    p->setWorkingDirectory(workingDirectory);
    p->setProcessChannelMode(QProcess::MergedChannels);

    connect(&(*p), SIGNAL(readyReadStandardOutput()), this, SLOT(readyReadStandardOutput()));
    connect(&(*p), SIGNAL(readyReadStandardError()), this, SLOT(readyReadStandardError()));

    if (!args.isEmpty()) {
        //qDebug() << "START COMMAND" << m_command << "WITH ARGS" << args
        //         << "IN" << p->workingDirectory();
        p->start(m_command, args);
    } else {
        //qDebug() << "START COMMAND" << m_command
        //         << "IN" << p->workingDirectory();
        p->start(m_command);
    }

    qint64 const start = QDateTime::currentDateTime().toMSecsSinceEpoch();

    if (p->waitForStarted(m_waitForStartTimeout)) {
        // qDebug() << "PROCESS" << m_command << "STARTED IN" << p->workingDirectory();
        if (p->state() == QProcess::ProcessState::Running) {
            // qDebug() << "PROCESS" << m_command << "RUNNING IN" << p->workingDirectory();
            // wait forever for git/opkg-commands to finish
            int wait = m_waitForFinishTimeout;
            if (m_command.trimmed().startsWith("git", Qt::CaseInsensitive) ||
                m_command.trimmed().startsWith("opkg", Qt::CaseInsensitive)) {
                wait = -1;
            }
            bool const no_timeout = p->waitForFinished(wait);
            if (no_timeout) {
                // qDebug() << "PROCESS" << m_command << "FINISHED IN" << p->workingDirectory();
                if (p->exitStatus() == QProcess::NormalExit) {
                    if ((m_exitCode = p->exitCode()) == 0) {
                        qint64 const end = QDateTime::currentDateTime().toMSecsSinceEpoch();
                        qDebug() << "EXECUTED" << m_command
                                 << QString("(runtime %1ms)").arg(end-start)
                                 << "with code" << m_exitCode
                                 << "IN" << p->workingDirectory();
                        return true;
                    } else {
                        qint64 const end = QDateTime::currentDateTime().toMSecsSinceEpoch();
                        qCritical() << "EXECUTED" << m_command
                                    << QString("(runtime %1ms)").arg(end-start)
                                    << "with code" << m_exitCode
                                    << "IN" << p->workingDirectory();
                    }
                } else {
                    qint64 const end = QDateTime::currentDateTime().toMSecsSinceEpoch();
                    qCritical() << "PROCESS" << m_command << "CRASHED with code"
                                << p->exitCode()
                                << QString("(after %1ms)").arg(end-start)
                                << "IN" << p->workingDirectory();
                }
            } else {
                qint64 const end = QDateTime::currentDateTime().toMSecsSinceEpoch();
                qCritical() << "PROCESS" << m_command
                            << "DID NOT FINISH WITH" << wait
                            << "MS IN" << p->workingDirectory()
                            << QString("(runtime %1ms)").arg(end-start);
            }
        } else {
            qCritical() << "WRONG PROCESS STATE" << p->state()
                        << "IN" << p->workingDirectory();
        }
    } else {
        qint64 const end = QDateTime::currentDateTime().toMSecsSinceEpoch();
        qCritical() << "PROCESS" << m_command << "TIMEOUT AT START"
                    << QString("(runtime %1ms)").arg(end-start)
                    << "IN" << p->workingDirectory();
    }
    return false;
}