/****************************************************************************
**
** Copyright (C) 2012 - 2017 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 "nxtdevicedebugsupport.h"

#include <nxtconstants.h>
#include <nxtsettings.h>
#include <steps/run/nxtrunconfiguration.h>
#include <steps/run/nxtrunrestconfiguration.h>
#include <qtc/nxtsdkkitinformation.h>
#include <qtc/nxtsdkmanager.h>
#include <qtc/nxttoolchain.h>
#include <vm/nxtvirtualboxmanager.h>

#include <debugger/debuggerkitinformation.h>
#include <debugger/debuggerplugin.h>
#include <projectexplorer/target.h>
#include <qmakeprojectmanager/qmakeproject.h>
#include <qtsupport/qtkitinformation.h>
#include <remotelinux/remotelinuxdebugsupport.h>
#include <utils/portlist.h>
#include <utils/qtcassert.h>

#include <device/hardware/nxthardwaredevicerest.h>

#include <projectexplorer/buildconfiguration.h> // todo

using namespace Debugger;
using namespace ProjectExplorer;
using namespace QmakeProjectManager;
using namespace Nxt;
using namespace Nxt::Internal;
using namespace RemoteLinux;

namespace {
const int GDB_SERVER_READY_TIMEOUT_MS = 10000;
}

class GdbServerReadyWatcher : public ProjectExplorer::RunWorker
{
    Q_OBJECT

public:
    explicit GdbServerReadyWatcher(ProjectExplorer::RunControl *runControl, GdbServerPortsGatherer *gdbServerPortsGatherer)
        : RunWorker(runControl)
        , m_gdbServerPortsGatherer(gdbServerPortsGatherer)
    {
        setDisplayName("GdbServerReadyWatcher");

        connect(&m_usedPortsGatherer, &DeviceUsedPortsGatherer::error, this, &RunWorker::reportFailure);
        connect(&m_usedPortsGatherer, &DeviceUsedPortsGatherer::portListReady, this, &GdbServerReadyWatcher::handlePortListReady);
    }
    ~GdbServerReadyWatcher()
    {
    }

private:
    void start() override
    {
        appendMessage(tr("Waiting for gdbserver..."), Utils::NormalMessageFormat);
        m_usedPortsGatherer.start(device());
        m_startTime.restart();
    }

    void handlePortListReady()
    {
        if (!m_usedPortsGatherer.usedPorts().contains(m_gdbServerPortsGatherer->gdbServerPort()))
        {
            if (m_startTime.elapsed() > GDB_SERVER_READY_TIMEOUT_MS)
            {
                reportFailure(tr("Timeout waiting for gdbserver to become ready."));
                return;
            }
            m_usedPortsGatherer.start(device());
        }
        reportDone();
    }

    GdbServerPortsGatherer *m_gdbServerPortsGatherer;
    DeviceUsedPortsGatherer m_usedPortsGatherer;
    QTime m_startTime;
};

#ifdef HACK_BYPASS_PRIVATE_ENVIRONMENT_ACCESS /* bypass the private access to debugger */
Debugger::Internal::DebuggerRunParameters& Debugger::DebuggerRunTool::runParametersHack()
{
    return m_runParameters;
}
#endif /* bypass the private access to debugger */

NxtDeviceDebugSupport::NxtDeviceDebugSupport(RunControl *runControl, ProjectExplorer::Kit *kit)
    : DebuggerRunTool(runControl, kit)
{
    setDisplayName("NxtDeviceDebugSupport");
    StandardRunnable inferior;

    auto rc = qobject_cast<NxtRunRestConfiguration*>(runControl->runConfiguration());
    auto nxtsdk = NxtSdkKitInformation::sdk(rc->target()->kit());
    QString symbolFile = rc->localExecutableFilePath();
#if 0
    auto const targetName = rc->property("targetName").toString();
    auto targetFilePath = rc->target()->applicationTargets().targetFilePath(targetName).toString();
    if (targetFilePath.isEmpty()) {
        targetFilePath = runControl->runConfiguration()->target()->activeBuildConfiguration()->buildDirectory().toString() + "/" + targetName;
    }
#endif
    auto const nativeSharedHomePath = QDir::fromNativeSeparators(nxtsdk->sharedHomePath());
    auto const nativeSharedSrcPath = QDir::fromNativeSeparators(nxtsdk->sharedSrcPath());

    auto const symbolFileIsValid = [&](QString const& symbolFile) -> bool {
        QFileInfo fi(symbolFile);
        return (fi.exists() && fi.isFile())
            && !symbolFile.isEmpty()
            && (symbolFile.contains(nativeSharedHomePath) || symbolFile.contains(nativeSharedHomePath));
    };

    if (!symbolFileIsValid(symbolFile)) {
        appendMessage(ProjectExplorer::RunControl::tr("Symbol file '%1' does not exist.")
                      .arg(symbolFile), Utils::ErrorMessageFormat);
#if 0
        symbolFile = targetFilePath;
        if (!symbolFileIsValid(symbolFile)) {
            appendMessage(ProjectExplorer::RunControl::tr("Symbol file '%1' does not exist.")
                          .arg(symbolFile), Utils::ErrorMessageFormat);
        }
#endif
    }

    symbolFile = symbolFile.replace(QDir::fromNativeSeparators(nxtsdk->sharedHomePath()), "/home/nxt/share/");
    symbolFile = symbolFile.replace(QDir::fromNativeSeparators(nxtsdk->sharedSrcPath()), "/home/nxt/src1/");

    setSymbolFile(symbolFile);
    setSysRoot(Nxt::Constants::NXT_TOOLCHAIN_SYSROOT_TARGET);

#ifdef HACK_BYPASS_PRIVATE_ENVIRONMENT_ACCESS /* bypass the private access to debugger */
    runParametersHack().debugInfoLocation = Nxt::Constants::NXT_TOOLCHAIN_DEBUG_INFO_LOCATION;
#endif /* bypass the private access to debugger */
    setDebugInfoLocation(Nxt::Constants::NXT_TOOLCHAIN_DEBUG_INFO_LOCATION);

    setRemoteChannel(runControl->device()->sshParameters().host(), 2222);
    setRunControlName("NXT remote debugger");
    setBreakOnMain(true);
    setInferior(inferior);

    setStartMode(AttachToRemoteServer);
    setCloseMode(KillAtClose);
    setUseContinueInsteadOfRun(true);
}

void NxtDeviceDebugSupport::start()
{
    auto rc = qobject_cast<NxtRunRestConfiguration*>(runControl()->runConfiguration());
    auto nxtsdk = NxtSdkKitInformation::sdk(rc->target()->kit());
    //auto project = qobject_cast<QmakeProject *>(rc->target()->project());
    auto project = qobject_cast<QmakeProject*>(rc->target()->project());
    auto buildDirectory = project->activeTarget()->activeBuildConfiguration()->buildDirectory().toString();

    if (isCppDebugging()) {
        buildDirectory = buildDirectory.replace(QDir::fromNativeSeparators(nxtsdk->sharedHomePath()), "/home/nxt/share");
        buildDirectory = buildDirectory.replace(QDir::fromNativeSeparators(nxtsdk->sharedSrcPath()), "/home/nxt/src1");
        addSolibSearchDir(buildDirectory);
    }

    runParametersHack().sharedHomePath = QDir::fromNativeSeparators(nxtsdk->sharedHomePath());
    runParametersHack().sharedSrcPath = QDir::fromNativeSeparators(nxtsdk->sharedSrcPath());

    runParametersHack().sysRoot = QLatin1String(Nxt::Constants::NXT_TOOLCHAIN_SYSROOT_TARGET);

#ifdef HACK_BYPASS_PRIVATE_ENVIRONMENT_ACCESS /* bypass the private access to debugger */
    Utils::Environment& env = runParametersHack().debugger.environment;
#else
    Utils::Environment& env = m_runParameters.debugger.environment;
#endif /* bypass the private access to debugger */

    env.appendOrSet(QLatin1String(Constants::NXT_SSH_USERNAME), QLatin1String(Constants::NXT_VM_DEFAULTUSER));
    env.appendOrSet(QLatin1String(Constants::NXT_SSH_PORT), QString::number(nxtsdk->sshPort()));
    env.appendOrSet(QLatin1String(Constants::NXT_SSH_PRIVATE_KEY), nxtsdk->privateKeyFile());
    env.appendOrSet(QLatin1String(Constants::NXT_SSH_SHARED_HOME), QDir::fromNativeSeparators(nxtsdk->sharedHomePath()));
    env.appendOrSet(QLatin1String(Constants::NXT_SSH_SHARED_TARGET), QDir::fromNativeSeparators(nxtsdk->sharedTargetsPath()));

    env.appendOrSet(QLatin1String(Constants::NXT_SSH_SHARED_SRC), QDir::fromNativeSeparators(nxtsdk->sharedSrcPath()));
    env.appendOrSet(QLatin1String(Constants::NXT_SDK_ENVIRONMENT_FILTER), NxtSettings::environmentFilter());

    const auto tmp = rc->project()->activeTarget()->displayName().split(" (in");
    auto targetName = tmp.size() > 0 ? tmp.at(0) : "this should not happen";
    const auto sdkTools = QDir(NxtSdkManager::sdkToolsDirectory() + QDir::separator() + QLatin1String(Constants::NXT_BUILD_ENGINE_NAME) + QDir::separator() + targetName).absolutePath();
    env.appendOrSet(QLatin1String(Constants::NXT_SSH_TARGET_NAME), targetName);
    env.appendOrSet(QLatin1String(Constants::NXT_SSH_SDK_TOOLS), sdkTools);
    env.appendOrSet(QLatin1String(Constants::NXT_SSH_PROJECT_PATH), rc->project()->projectDirectory().toString());

    {
        auto vappName = rc->property("vappName").toString();
        appendMessage(ProjectExplorer::RunControl::tr("Starting VApp in gdbserver '%1' on NXT device '%2'...")
                      .arg(vappName, runControl()->device().data()->displayName()), Utils::NormalMessageFormat);

        auto ssh = runControl()->device()->sshParameters();
        NxtHardwareDeviceREST nxt(ssh.host(), ssh.userName(), ssh.password());
        if (!nxt.available()) {
            appendMessage(ProjectExplorer::RunControl::tr("NXT device '%1' is not available.")
                          .arg(runControl()->device().data()->displayName()), Utils::ErrorMessageFormat);
            return;
        }
        if (nxt.authenticate() != 200) {
            appendMessage(ProjectExplorer::RunControl::tr("Authentication on NXT device '%1' failed.")
                          .arg(runControl()->device().data()->displayName()), Utils::ErrorMessageFormat);
            return;
        }
        if (!nxt.startVAppInGdbServer(vappName)) {
            appendMessage(ProjectExplorer::RunControl::tr("Failed to start VApp in gdbserver '%1' on NXT device '%2'.")
                          .arg(vappName, runControl()->device().data()->displayName()), Utils::ErrorMessageFormat);
            return;
        }
        appendMessage(ProjectExplorer::RunControl::tr("VApp '%1' has been started in gdbserver on NXT device '%2'.")
                      .arg(vappName, runControl()->device().data()->displayName()), Utils::NormalMessageFormat);
    }

    // show environment
    if (0) {
        QDebug debug = qDebug();
        debug.noquote();
        debug.nospace();
        debug << "NxtDeviceDebugSupport Environment:" << "\n"
              << QString("-").repeated(40) << "\n"
              << env.toStringList().join("\n") << "\n"
              << QString("-").repeated(40) << "\n";
    }

    DebuggerRunTool::start();

}

#include "nxtdevicedebugsupport.moc"
