/****************************************************************************
**
** Copyright (C) 2012 - 2014 Jolla Ltd.
** Contact: http://jolla.com/
**
** This file is part of Qt Creator.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Digia.
**
****************************************************************************/

#include "nxtremoteprocess.h"

#include "../nxtplugin/src/nxtconstants.h"
#include <ssh/sshremoteprocessrunner.h>
#include <utils/qtcprocess.h>

#include <QCoreApplication>
#include <QDir>
#include <QStandardPaths>
#include <QFile>
#include <QProcessEnvironment>
#include <QRegularExpression>
#include <QSettings>
#include <QTextStream>
#include <iostream>
#include <QTimer>
#include <QTextStream>
#include <QDataStream>

NxtRemoteProcess::NxtRemoteProcess(QObject *parent)
    : QSsh::SshRemoteProcessRunner(parent)
    , m_cache(false)
    , m_interactive(false)
{
#ifdef NXT_DEBUG_REMOTE_IO
    fdx = new QFile(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).append("/nxtssh_io_log.txt"));
    if (fdx) {
        fdx->open(QIODevice::WriteOnly | QIODevice::Append);
    }
    tsx = new QTextStream(fdx);
    if (tsx) {
        *tsx << QString("\r\n").repeated(5)
             << QString("x").repeated(80)
             << QString("\r\n").repeated(1);
        tsx->flush();
    }
#endif /* NXT_DEBUG_REMOTE_IO */
    connect(this, &NxtRemoteProcess::processStarted, this, &NxtRemoteProcess::onProcessStarted);
    connect(this, &NxtRemoteProcess::readyReadStandardOutput, this, &NxtRemoteProcess::onStandardOutput);
    connect(this, &NxtRemoteProcess::readyReadStandardError, this, &NxtRemoteProcess::onStandardError);
    connect(this, &NxtRemoteProcess::connectionError, this, &NxtRemoteProcess::onConnectionError);
}

NxtRemoteProcess::~NxtRemoteProcess()
{
    if (m_console) {
        delete m_console;
    }
#ifdef NXT_DEBUG_REMOTE_IO
    if (fdx) {
        fdx->close();
        delete fdx;
    }
    if (tsx) {
        delete tsx;
    }
#endif /* NXT_DEBUG_REMOTE_IO */
}

void NxtRemoteProcess::setCommand(const QString& command)
{
#ifdef NXT_DEBUG_REMOTE_IO
    if (tsx) {
        *tsx << QString("\r\n").repeated(5)
             << "COMMAND: " << command
             << QString("\r\n").repeated(1);
        tsx->flush();
    }
#endif /* NXT_DEBUG_REMOTE_IO */
    m_command = command;
}

void NxtRemoteProcess::setSshParameters(const QSsh::SshConnectionParameters& params)
{
    m_sshConnectionParams = params;
}

void NxtRemoteProcess::setIntercative(bool enabled)
{
    m_interactive = enabled;
}

void NxtRemoteProcess::setCacheOutput(bool enabled)
{
    m_cache = enabled;
}

int NxtRemoteProcess::executeAndWait()
{
    QEventLoop loop;
    connect(this, &NxtRemoteProcess::connectionError, &loop, &QEventLoop::quit);
    connect(this, &NxtRemoteProcess::processClosed, &loop, &QEventLoop::quit);
    QSsh::SshRemoteProcessRunner::run(forwardEnvironment(m_command).toUtf8(), m_sshConnectionParams);
    loop.exec();
    if (processExitStatus() == QSsh::SshRemoteProcess::NormalExit) {
        return processExitCode();
    } else {
        return 1;
    }
}

QString NxtRemoteProcess::forwardEnvironment(const QString &command)
{
    const QProcessEnvironment systemEnvironment = QProcessEnvironment::systemEnvironment();

    const QStringList patterns = systemEnvironment.value(Nxt::Constants::NXT_SDK_ENVIRONMENT_FILTER).split(QRegularExpression("[[:space:]]+"), QString::SkipEmptyParts);
    if (patterns.isEmpty())
        return command;

    QStringList regExps;
    for (const QString &pattern : patterns) {
        const QString asRegExp = QRegularExpression::escape(pattern).replace("\\*", ".*");
        regExps.append(asRegExp);
    }
    const QRegularExpression filter("^(" + regExps.join("|") + ")$");

    QStringList keys = systemEnvironment.keys();
    keys.sort();

    QStringList environmentToForward;
    for (const QString& key : keys) {
        if (key == Nxt::Constants::NXT_SDK_ENVIRONMENT_FILTER)
            continue;
        if (filter.match(key).hasMatch()) {
            const QString value = Utils::QtcProcess::quoteArgUnix(systemEnvironment.value(key));
            environmentToForward.append(key + "=" + value);
        }
    }
    if (environmentToForward.isEmpty())
        return command;

    QTextStream qout(stdout);
    QLatin1String delim("\n    ");
    qout << tr("Adding to environment:") << delim << environmentToForward.join(delim) << endl;

    return "export " + environmentToForward.join(" ") + "; " + command;
}

void NxtRemoteProcess::onProcessStarted()
{
    if (m_interactive) {
        m_console = new ConsoleListener(this);
        if (m_console) {
            QObject::connect(m_console, &ConsoleListener::newline, [&](QString const& str) {
                if (str.size() > 0) {
                    QString const line = str + "\n";
                    writeDataToProcess(line.toLatin1().constData());
        #ifdef NXT_DEBUG_REMOTE_IO
                    if (tsx) {
                        *tsx << QString("\r\n").repeated(1)
                             << QString(">in ")
                             << QString(".").repeated(80)
                             << QString("\r\n").repeated(1)
                             << line;
                        tsx->flush();
                    }
        #endif /* NXT_DEBUG_REMOTE_IO */

                    // kill the nxtssh process immediately if the debugger was stopped by the user
                    // todo: this is not a nice way to stop the process. find a better solution!
                    if (line.contains("python theDumper.exitGdb(")) {
                        cancel();
                        exit(0);
                    }
                }
            });
        } else {
            std::cerr << "Error: Unable to read from stdin\n";
        }
    }
}

void NxtRemoteProcess::onStandardOutput()
{
    if (!m_cache) {
        const QByteArray output(readAllStandardOutput());
#if 0
        fprintf(stderr, "%s", output.constData());
#else
        QTextStream ts(stdout);
        ts << QString::fromLocal8Bit(output).toLocal8Bit();
#endif
        fflush(stdout);
#ifdef NXT_DEBUG_REMOTE_IO
        if (tsx) {
            *tsx << QString("\r\n").repeated(1)
                 << QString(">out ")
                 << QString(".").repeated(80)
                 << QString("\r\n").repeated(1)
                 << QString::fromLocal8Bit(output);
            tsx->flush();
        }
#endif /* NXT_DEBUG_REMOTE_IO */
    }
}

void NxtRemoteProcess::onStandardError()
{
    if (!m_cache) {
        const QByteArray output(readAllStandardError());
#if 0
        fprintf(stderr, "%s", output.constData());
#else
        QTextStream ts(stderr);
        ts << QString::fromLocal8Bit(output).toLocal8Bit();
#endif
        fflush(stderr);
#ifdef NXT_DEBUG_REMOTE_IO
        if (tsx) {
            *tsx << QString("\r\n").repeated(1)
                 << QString(">err ")
                 << QString(".").repeated(80)
                 << QString("\r\n").repeated(1)
                 << QString::fromLocal8Bit(output);
            tsx->flush();
        }
#endif /* NXT_DEBUG_REMOTE_IO */
    }
}

void NxtRemoteProcess::onConnectionError()
{
    if (!m_cache) {
        fprintf(stderr, "%s", qPrintable(QString::fromLatin1("Project ERROR: Could not connect to \"%1\" virtual machine. (%2)\n").arg(Nxt::Constants::NXT_BUILD_ENGINE_NAME, lastConnectionErrorString())));
        fflush(stderr);
    }
}
