/****************************************************************************
**
** 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 "nxttarget.h"

#include <nxtconstants.h>
#include <nxtlogging.h>
#include <qtc/nxtqtversion.h>
#include <qtc/nxtsdk.h>
#include <qtc/nxtsdkkitinformation.h>
#include <qtc/nxtsdkmanager.h>
#include <qtc/nxttargetkitinformation.h>
#include <qtc/nxttoolchain.h>

#include <coreplugin/icore.h>
#include <debugger/debuggeritem.h>
#include <debugger/debuggeritemmanager.h>
#include <debugger/debuggerkitinformation.h>
#include <projectexplorer/toolchainmanager.h>
#include <qtsupport/qtkitinformation.h>
#include <qtsupport/qtversionfactory.h>
#include <qtsupport/qtversionmanager.h>
#include <utils/hostosinfo.h>
#include <utils/qtcassert.h>

#include <QDir>
#include <QStringList>

using namespace Debugger;
using namespace ProjectExplorer;
using namespace QtSupport;
using namespace Utils;

namespace Nxt {
namespace Internal {

using namespace Nxt::Constants;

static const char* wrapperScripts[] =
{
    NXT_WRAPPER_QMAKE,
    NXT_WRAPPER_MAKE,
    NXT_WRAPPER_GDB,
    NXT_WRAPPER_GCC,
    NXT_WRAPPER_NXTDEPLOY,
};

NxtTarget::NxtTarget(NxtSdk* nxtsdk):
    m_sdk(nxtsdk)
{
}

NxtTarget::~NxtTarget()
{
}

QString NxtTarget::name() const
{
    return m_name;
}

void NxtTarget::setName(const QString &name)
{
    m_name = name;
}

void NxtTarget::setQmakeQuery(const QString &qmakeQuery)
{
    m_qmakeQuery = qmakeQuery;
}

void NxtTarget::setGccDumpMachine(const QString &gccMachineDump)
{
    m_gccMachineDump = gccMachineDump;
    // the dump contains a linefeed
    m_gccMachineDump.remove(QRegExp(QLatin1String("\\n")));
}

void NxtTarget::setDefaultGdb(const QString &name)
{
    m_defaultGdb=name;
}

bool NxtTarget::fromMap(const QVariantMap &data)
{
    m_name = data.value(QLatin1String(Constants::NXT_TARGET_NAME)).toString();
    m_qmakeQuery = data.value(QLatin1String(Constants::NXT_TARGET_QMAKE_DUMP)).toString();
    m_gccMachineDump = data.value(QLatin1String(Constants::NXT_TARGET_GCC_DUMP)).toString();
    m_defaultGdb = data.value(QLatin1String(Constants::NXT_TARGET_DEFAULT_DEBUGGER)).toString();
    return isValid();
}

QVariantMap NxtTarget::toMap() const
{
    QVariantMap result;
    result.insert(QLatin1String(Constants::NXT_TARGET_NAME), m_name);
    result.insert(QLatin1String(Constants::NXT_TARGET_QMAKE_DUMP), m_qmakeQuery);
    result.insert(QLatin1String(Constants::NXT_TARGET_GCC_DUMP), m_gccMachineDump);
    result.insert(QLatin1String(Constants::NXT_TARGET_DEFAULT_DEBUGGER), m_defaultGdb);
    return result;
}

bool NxtTarget::isValid() const
{
    return !m_name.isEmpty() && !m_qmakeQuery.isEmpty() && !m_gccMachineDump.isEmpty();
}

QString NxtTarget::targetPath() const
{
    return NxtSdkManager::sdkToolsDirectory() + m_sdk->virtualMachineName() + QLatin1Char('/') + m_name;
}

bool NxtTarget::createScripts() const
{
    if (!isValid())
        return false;

    const QString targetPath(this->targetPath());

    QDir targetDir(targetPath);
    if (!targetDir.exists() && !targetDir.mkpath(targetPath)) {
        qWarning() << "IDS NXT" << "Could not create target directory." << targetDir;
        return false;
    }
    bool result = true;
    for (unsigned int i = 0; i < sizeof(wrapperScripts) / sizeof(wrapperScripts[0]); ++i)
         result &= createScript(targetPath, static_cast<int>(i));

    const QString qmakepath = targetPath + QLatin1Char('/') + QLatin1String(Constants::QMAKE_QUERY);
    const QString gccpath = targetPath + QLatin1Char('/') + QLatin1String(Constants::GCC_DUMPMACHINE);

    QString patchedQmakeQuery = m_qmakeQuery;
#ifndef __linux__
    patchedQmakeQuery.replace(QLatin1String(":/"), QString::fromLatin1(":%1/%2/").arg(m_sdk->sharedTargetsPath()).arg(m_name));
#endif

    result &= createCacheFile(qmakepath, patchedQmakeQuery);
    result &= createCacheFile(gccpath, m_gccMachineDump);

    return result;
}

void NxtTarget::deleteScripts() const
{
    FileUtils::removeRecursively(FileName::fromString(targetPath()));
}

Kit* NxtTarget::createKit() const
{
    if (!isValid())
        return nullptr;
    const QString sysroot(m_sdk->sharedTargetsPath() + QLatin1Char('/') + m_name);

    FileName path = FileName::fromString(sysroot);
    if (!path.toFileInfo().exists()) {
        qWarning() << "IDS NXT" << "Sysroot does not exist" << sysroot;
        return nullptr;
    }

    Kit *k = new Kit();
    k->setAutoDetected(true);
    k->setUnexpandedDisplayName(QString::fromLatin1("%1 (in %2)").arg(m_name, m_sdk->virtualMachineName()));
    k->setIconPath(FileName::fromString(QLatin1String(Constants::NXT_OPTIONS_CATEGORY_ICON)));
    SysRootKitInformation::setSysRoot(k, FileName::fromUserInput(sysroot));

    DeviceTypeKitInformation::setDeviceTypeId(k, Constants::NXT_DEVICE_TYPE);
    k->setMutable(DeviceKitInformation::id(), true);

    ensureDebuggerIsSet(k);

    NxtSdkKitInformation::setSdk(k,m_sdk);
    NxtTargetKitInformation::setTargetName(k,name());
    return k;
}

void NxtTarget::ensureDebuggerIsSet(Kit *k) const
{
    QTC_ASSERT(!m_defaultGdb.isEmpty(), return);

#if 0
    const QString gdb = HostOsInfo::withExecutableSuffix(m_defaultGdb);
    QString gdbDir = QCoreApplication::applicationDirPath();
    if (HostOsInfo::isMacHost()) {
        QDir dir = QDir(gdbDir);
        dir.cdUp();
        dir.cdUp();
        dir.cdUp();
        gdbDir = dir.path();
    }
    FileName gdbFileName = FileName::fromString(gdbDir + QLatin1Char('/') + gdb);
#else
    const QString gdb = QLatin1String(Constants::NXT_WRAPPER_GDB);
    QString gdbDir = FileName::fromString(targetPath()).toString();
    FileName gdbFileName = FileName::fromString(gdbDir + QLatin1Char('/') + gdb);
#endif

    if (gdbFileName.toFileInfo().exists()) {
        if (const DebuggerItem *existing = DebuggerItemManager::findByCommand(gdbFileName)) {
            DebuggerKitInformation::setDebugger(k, existing->id());
        } else {
            DebuggerItem debugger;
            debugger.setCommand(gdbFileName);
            debugger.setEngineType(GdbEngineType);
#if 0
            debugger.setUnexpandedDisplayName(QObject::tr("GDB (%1)").arg(m_defaultGdb));
#else
            debugger.setUnexpandedDisplayName(QObject::tr("GDB (%1 in %2)").arg(m_defaultGdb, m_sdk->virtualMachineName()));
#endif
            debugger.setAutoDetected(true);
#if 0
            const int prefixLength = QString(Constants::NXT_DEBUGGER_FILENAME_PREFIX).length();
            debugger.setAbi(Abi::abiFromTargetTriplet(m_defaultGdb.mid(prefixLength)));
#else
            debugger.setAbi(Abi::abiFromTargetTriplet(NXT_TOOLCHAIN_NAME_ARM));
            debugger.setVersion(NXT_DEBUGGER_ARM_VERSION);
#endif
            QVariant id = DebuggerItemManager::registerDebugger(debugger);
            DebuggerKitInformation::setDebugger(k, id);
        }
    } else {
        qCWarning(Log::sdks) << "Debugger binary" << gdb << "not found";
        k->setValue(DebuggerKitInformation::id(), QVariant(QString()));
    }
}

NxtQtVersion* NxtTarget::createQtVersion() const
{
    const FileName qmake = FileName::fromString(targetPath() + QLatin1Char('/') +
            QLatin1String(Constants::NXT_WRAPPER_QMAKE));
    // Is there a qtversion present for this qmake?
    BaseQtVersion *qtv = QtVersionManager::qtVersionForQMakeBinary(qmake);
    if (qtv && !qtv->isValid()) {
        QtVersionManager::removeVersion(qtv);
        qtv = nullptr;
    }
    if (!qtv)
        qtv = new NxtQtVersion(qmake, true, targetPath());

    //QtVersionFactory::createQtVersionFromQMakePath(qmake, true, targetPath());

    QTC_ASSERT(qtv && qtv->type() == QLatin1String(Constants::NXT_QT), return nullptr);

    NxtQtVersion *nxtqtv = static_cast<NxtQtVersion *>(qtv);
    const QString vmName = m_sdk->virtualMachineName();
    nxtqtv->setVirtualMachineName(vmName);
    nxtqtv->setTargetName(m_name);
    nxtqtv->setUnexpandedDisplayName(QString::fromLatin1("Qt %1 for %2 in %3").arg(qtv->qtVersionString(), m_name, vmName));
    return nxtqtv;
}

NxtToolChain* NxtTarget::createToolChain(Core::Id l) const
{
    const FileName gcc = FileName::fromString(targetPath() + QLatin1Char('/') + QLatin1String(Constants::NXT_WRAPPER_GCC));
    QList<ToolChain *> toolChains = ToolChainManager::toolChains();

    foreach (ToolChain *tc, toolChains) {
        if (tc->compilerCommand() == gcc && tc->isAutoDetected()) {
            QTC_ASSERT(tc->typeId() == Constants::NXT_TOOLCHAIN_ID, return nullptr);
        }
    }

    NxtToolChain* nxttoolchain = new NxtToolChain(ToolChain::AutoDetection);
    const QString vmName = m_sdk->virtualMachineName();
    nxttoolchain->setDisplayName(QString::fromLatin1("GCC (%1 in %2)").arg(m_name, vmName));
    nxttoolchain->setVirtualMachine(vmName);
    nxttoolchain->setTargetName(m_name);
    nxttoolchain->setLanguage(l);
    nxttoolchain->resetToolChain(gcc);
    return nxttoolchain;
}

bool NxtTarget::createScript(const QString &targetPath, int scriptIndex) const
{
    bool ok = false;
    const char* wrapperScriptCopy = wrapperScripts[scriptIndex];
    const QFile::Permissions rwrw = QFile::ReadOwner|QFile::ReadUser|QFile::ReadGroup|QFile::WriteOwner|QFile::WriteUser|QFile::WriteGroup;
    const QFile::Permissions rwxrwx = rwrw|QFile::ExeOwner|QFile::ExeUser|QFile::ExeGroup;

    QString wrapperScriptCommand = QLatin1String(wrapperScriptCopy);
    if (HostOsInfo::isWindowsHost())
        wrapperScriptCommand.chop(4); // remove the ".cmd"

    const QString wrapperBinaryPath = Core::ICore::libexecPath() + QLatin1String("/nxtssh") + QStringLiteral(QTC_HOST_EXE_SUFFIX);

    using namespace Utils;
    const QString scriptCopyPath = targetPath + QLatin1Char('/') + QLatin1String(wrapperScriptCopy);
    QDir targetDir(targetPath);
    const QString targetName = targetDir.dirName();
    targetDir.cdUp();
    const QString nxtDevToolsDir = QDir::toNativeSeparators(targetDir.canonicalPath());
    QFile script(scriptCopyPath);
    ok = script.open(QIODevice::WriteOnly);
    if (!ok) {
        qWarning() << "IDS NXT" << "Could not open script" << scriptCopyPath;
        return false;
    }

    QString scriptContent;

    if (HostOsInfo::isWindowsHost()) {
        scriptContent += QLatin1String("@echo off\nSetLocal EnableDelayedExpansion\n");
        scriptContent += QLatin1String("set ARGUMENTS=\nFOR %%a IN (%*) DO set ARGUMENTS=!ARGUMENTS! ^ '%%a'\n");
        scriptContent += QLatin1String("set ") + QLatin1String(NXT_SSH_TARGET_NAME) + QLatin1Char('=') + targetName + QLatin1Char('\n');
        scriptContent += QLatin1String("set ") + QLatin1String(NXT_SSH_SDK_TOOLS) + QLatin1Char('=') + nxtDevToolsDir + QDir::separator() + targetName + QLatin1Char('\n');
        scriptContent += QLatin1String("SetLocal DisableDelayedExpansion\n");
        scriptContent += QLatin1Char('"') + QDir::toNativeSeparators(wrapperBinaryPath) + QLatin1String("\" ") + wrapperScriptCommand + QLatin1Char(' ') + QLatin1String("%ARGUMENTS%\n");
    }

    if (HostOsInfo::isAnyUnixHost()) {
        scriptContent += QLatin1String("#!/bin/sh\n");
        scriptContent += QLatin1String("ARGUMENTS=\"\";for ARGUMENT in \"$@\"; do ARGUMENTS=\"${ARGUMENTS} '${ARGUMENT}'\" ; done;\n");
        scriptContent += QLatin1String("export  ") + QLatin1String(NXT_SSH_TARGET_NAME) + QLatin1Char('=') + targetName + QLatin1Char('\n');
        scriptContent += QLatin1String("export  ") + QLatin1String(NXT_SSH_SDK_TOOLS) + QLatin1String("=\"") + nxtDevToolsDir + QDir::separator() + targetName + QLatin1String("\"\n");
        scriptContent += QLatin1String("exec ");
        scriptContent += QLatin1Char('"') + QDir::toNativeSeparators(wrapperBinaryPath) + QLatin1String("\" ") + wrapperScriptCommand + QLatin1Char(' ') + QLatin1String("${ARGUMENTS}");
    }


    ok = script.write(scriptContent.toUtf8()) != -1;
    if (!ok) {
        qWarning() << "IDS NXT" << "Could not write script" << scriptCopyPath;
        return false;
    }

    ok = QFile::setPermissions(scriptCopyPath, rwxrwx);
    if (!ok) {
        qWarning() << "IDS NXT" << "Could not set file permissions on script" << scriptCopyPath;
        return false;
    }
    return ok;
}

bool NxtTarget::createCacheFile(const QString &fileName, const QString &content) const
{
    bool ok = false;

    QFile file(fileName);
    ok = file.open(QIODevice::WriteOnly);
    if (!ok) {
        qWarning() << "IDS NXT" << "Could not open cache file" << fileName;
        return false;
    }

    ok = file.write(content.toUtf8()) != -1;
    if (!ok) {
        qWarning() << "IDS NXT" << "Could not write cache file " << fileName;
        return false;
    }
    file.close();
    return ok;
}

bool NxtTarget::operator==(const NxtTarget &other) const
{
    return m_name == other.m_name && m_qmakeQuery == other.m_qmakeQuery && m_gccMachineDump == other.m_gccMachineDump;
}

} // namespace Internal
} // namespace Nxt
