#include "processutils.h"

#include <QProcess>
#include <QScopedPointer>
#include <QDebug>

#include "utils.h"

using namespace vnotex;

ProcessUtils::State ProcessUtils::start(const QString &p_program,
                                        const QStringList &p_args,
                                        const QByteArray &p_stdIn,
                                        int &p_exitCodeOnSuccess,
                                        QByteArray &p_stdOut,
                                        QByteArray &p_stdErr)
{
    QScopedPointer<QProcess> proc(new QProcess());
    proc->start(p_program, p_args);
    return handleProcess(proc.data(), p_stdIn, p_exitCodeOnSuccess, p_stdOut, p_stdErr);
}

ProcessUtils::State ProcessUtils::handleProcess(QProcess *p_process,
                                                const QByteArray &p_stdIn,
                                                int &p_exitCodeOnSuccess,
                                                QByteArray &p_stdOut,
                                                QByteArray &p_stdErr)
{
    if (!p_process->waitForStarted()) {
        return State::FailedToStart;
    }

    if (!p_stdIn.isEmpty()) {
        if (p_process->write(p_stdIn) == -1) {
            p_process->closeWriteChannel();
            qWarning() << "failed to write to stdin of QProcess" << p_process->errorString();
            return State::FailedToWrite;
        } else {
            p_process->closeWriteChannel();
        }
    }

    p_process->waitForFinished();

    State state = State::Succeeded;
    if (p_process->exitStatus() == QProcess::CrashExit) {
        state = State::Crashed;
    } else {
        p_exitCodeOnSuccess = p_process->exitCode();
    }

    p_stdOut = p_process->readAllStandardOutput();
    p_stdErr = p_process->readAllStandardError();
    return state;
}

QStringList ProcessUtils::parseCombinedArgString(const QString &p_args)
{
    QStringList args;
    QString tmp;
    int quoteCount = 0;
    bool inQuote = false;

    // Handle quoting.
    // Tokens can be surrounded by double quotes "hello world".
    // Three consecutive double quotes represent the quote character itself.
    for (int i = 0; i < p_args.size(); ++i) {
        if (p_args.at(i) == QLatin1Char('"')) {
            ++quoteCount;
            if (quoteCount == 3) {
                // Third consecutive quote.
                quoteCount = 0;
                tmp += p_args.at(i);
            }

            continue;
        }

        if (quoteCount) {
            if (quoteCount == 1) {
                inQuote = !inQuote;
            }

            quoteCount = 0;
        }

        if (!inQuote && p_args.at(i).isSpace()) {
            if (!tmp.isEmpty()) {
                args += tmp;
                tmp.clear();
            }
        } else {
            tmp += p_args.at(i);
        }
    }

    if (!tmp.isEmpty()) {
        args += tmp;
    }

    return args;
}

QString ProcessUtils::combineArgString(const QStringList &p_args)
{
    QString argStr;
    for (const auto &arg : p_args) {
        QString tmp(arg);
        tmp.replace("\"", "\"\"\"");
        if (tmp.contains(' ')) {
            tmp = '"' + tmp + '"';
        }

        if (argStr.isEmpty()) {
            argStr = tmp;
        } else {
            argStr = argStr + ' ' + tmp;
        }
    }

    return argStr;
}

ProcessUtils::State ProcessUtils::start(const QString &p_program,
                                        const QStringList &p_args,
                                        const std::function<void(const QString &)> &p_logger,
                                        const bool &p_askedToStop)
{
    QProcess proc;
    proc.start(p_program, p_args);
    return handleProcess(&proc, p_logger, p_askedToStop);
}

ProcessUtils::State ProcessUtils::start(const QString &p_command,
                                        const std::function<void(const QString &)> &p_logger,
                                        const bool &p_askedToStop)
{
    QProcess proc;
    proc.start(p_command);
    return handleProcess(&proc, p_logger, p_askedToStop);
}

ProcessUtils::State ProcessUtils::handleProcess(QProcess *p_process,
                                                const std::function<void(const QString &)> &p_logger,
                                                const bool &p_askedToStop)
{
    if (!p_process->waitForStarted()) {
        return State::FailedToStart;
    }

    while (p_process->state() != QProcess::NotRunning) {
        Utils::sleepWait(100);

        auto outBa = p_process->readAllStandardOutput();
        auto errBa = p_process->readAllStandardError();
        QString msg;
        if (!outBa.isEmpty()) {
            msg += QString::fromLocal8Bit(outBa);
        }
        if (!errBa.isEmpty()) {
            msg += QString::fromLocal8Bit(errBa);
        }
        if (!msg.isEmpty()) {
            p_logger(msg);
        }

        if (p_askedToStop) {
            break;
        }
    }

    return p_process->exitStatus() == QProcess::NormalExit ? State::Succeeded : State::Crashed;
}

void ProcessUtils::startDetached(const QString &p_command)
{
    Q_ASSERT(!p_command.isEmpty());
    auto process = new QProcess();
    QObject::connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
                     process, &QProcess::deleteLater);
    process->start(p_command);
}
