/****************************************************************************
**
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Build Suite.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** 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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "probe.h"

#include "msvcprobe.h"
#include "osxprobe.h"
#include "../shared/logging/consolelogger.h"

#include <logging/translator.h>
#include <tools/hostosinfo.h>
#include <tools/profile.h>
#include <tools/settings.h>

#include <QCoreApplication>
#include <QDir>
#include <QDirIterator>
#include <QProcess>
#include <QStringList>

using namespace qbs;
using Internal::HostOsInfo;
using Internal::Tr;

static QString searchPath(const QString &path, const QString &me)
{
    foreach (const QString &ppath, path.split(HostOsInfo::pathListSeparator())) {
        const QString fullPath = ppath + QLatin1Char('/') + me;
        if (QFileInfo(fullPath).exists())
            return QDir::cleanPath(fullPath);
    }
    return QString();
}

static QString qsystem(const QString &exe, const QStringList &args = QStringList())
{
    QProcess p;
    p.setProcessChannelMode(QProcess::MergedChannels);
    p.start(exe, args);
    p.waitForStarted();
    p.waitForFinished();
    return QString::fromLocal8Bit(p.readAll());
}

static void specific_probe(Settings *settings, QList<Profile> &profiles, QString cc)
{
    qbsInfo() << Tr::tr("Trying to detect %1...").arg(cc);

    QString toolchainType;
    QStringList toolchainTypes;
    if (cc.contains("clang")) {
        toolchainType = "clang";
        toolchainTypes << "clang" << "llvm" << "gcc";
    } else if (cc.contains("llvm")) {
        toolchainType = "llvm-gcc";
        toolchainTypes << "llvm" << "gcc";
    } else if (cc.contains("gcc")) {
        toolchainType = "gcc";
        toolchainTypes << "gcc";
    }

    QString path       = QString::fromLocal8Bit(qgetenv("PATH"));
    QString cxx        = QString::fromLocal8Bit(qgetenv("CXX"));
    QString ld         = QString::fromLocal8Bit(qgetenv("LD"));
    QString cross      = QString::fromLocal8Bit(qgetenv("CROSS_COMPILE"));

    QString pathToGcc;
    QString architecture;
    QString endianness;

    if (ld.isEmpty())
        ld = "ld";
    if (cxx.isEmpty()) {
        if (toolchainType == "gcc")
            cxx = "g++";
        else if (toolchainType == "llvm-gcc")
            cxx = "llvm-g++";
        else if (toolchainType == "clang")
            cxx = "clang++";
    }
    if(!cross.isEmpty() && !cc.startsWith("/")) {
        pathToGcc = searchPath(path, cross + cc);
        if (QFileInfo(pathToGcc).exists()) {
            if (!cc.contains(cross))
                cc.prepend(cross);
            if (!cxx.contains(cross))
                cxx.prepend(cross);
            if (!ld.contains(cross))
                ld.prepend(cross);
        }
    }
    if (cc.startsWith("/"))
        pathToGcc = cc;
    else
        pathToGcc = searchPath(path, cc);

    if (!QFileInfo(pathToGcc).exists()) {
        qbsInfo() << Tr::tr("%1 not found.").arg(cc);
        return;
    }

    QString compilerTriplet = qsystem(pathToGcc, QStringList() << "-dumpmachine").simplified();
    QStringList compilerTripletl = compilerTriplet.split('-');
    if (compilerTripletl.count() < 2) {
        qbsError() << QString::fromLocal8Bit("Detected '%1', but I don't understand "
                "its architecture '%2'.").arg(pathToGcc, compilerTriplet);
        return;
    }

    architecture = compilerTripletl.at(0);
    if (architecture.contains("arm"))
        endianness = "big";
    else
        endianness = "little";

    QStringList pathToGccL = pathToGcc.split('/');
    QString compilerName = pathToGccL.takeLast().replace(cc, cxx);

    qbsInfo() << Tr::tr("Toolchain detected:\n"
                        "    binary: %1\n"
                        "    triplet: %2\n"
                        "    arch: %3\n"
                        "    cc: %4").arg(pathToGcc, compilerTriplet, architecture, cc);
    if (!cxx.isEmpty())
        qbsInfo() << Tr::tr("    cxx: %1").arg(cxx);
    if (!ld.isEmpty())
        qbsInfo() << Tr::tr("    ld: %1").arg(ld);

    Profile profile(toolchainType, settings);
    profile.removeProfile();

    profile.setValue("qbs.toolchain", toolchainTypes);
    profile.setValue("qbs.architecture", canonicalizeArchitecture(architecture));
    profile.setValue("qbs.endianness", endianness);

    if (compilerName.contains('-')) {
        QStringList nl = compilerName.split('-');
        profile.setValue("cpp.compilerName", nl.takeLast());
        profile.setValue("cpp.toolchainPrefix", nl.join("-") + '-');
    } else {
        profile.setValue("cpp.compilerName", compilerName);
    }
    profile.setValue("cpp.toolchainInstallPath", pathToGccL.join("/"));
    profiles << profile;
}

static void mingwProbe(Settings *settings, QList<Profile> &profiles)
{
    QString mingwPath;
    QString mingwBinPath;
    QString gccPath;
    QByteArray envPath = qgetenv("PATH");
    foreach (const QByteArray &dir, envPath.split(';')) {
        QFileInfo fi(dir + "/gcc.exe");
        if (fi.exists()) {
            mingwPath = QFileInfo(dir + "/..").canonicalFilePath();
            gccPath = fi.absoluteFilePath();
            mingwBinPath = fi.absolutePath();
            break;
        }
    }
    if (gccPath.isEmpty())
        return;
    QProcess process;
    process.start(gccPath, QStringList() << "-dumpmachine");
    if (!process.waitForStarted()) {
        qbsError() << "Could not start \"gcc -dumpmachine\".";
        return;
    }
    process.waitForFinished(-1);
    QByteArray gccMachineName = process.readAll().trimmed();
    QStringList validMinGWMachines;
    validMinGWMachines << "mingw32" << "mingw64" << "i686-w64-mingw32" << "x86_64-w64-mingw32";
    if (!validMinGWMachines.contains(gccMachineName)) {
        qbsError() << QString::fromLocal8Bit("Detected gcc platform '%1' is not supported.")
                      .arg(QString::fromLocal8Bit(gccMachineName));
        return;
    }

    QByteArray architecture = gccMachineName.split('-').first();
    if (architecture == "mingw32")
        architecture = "x86";
    else if (architecture == "mingw64")
        architecture = "x86_64";

    Profile profile(QString::fromLocal8Bit(gccMachineName), settings);
    qbsInfo() << Tr::tr("Platform '%1' detected in '%2'.").arg(profile.name(), mingwPath);
    profile.setValue("qbs.targetOS", QStringList("windows"));
    profile.setValue("cpp.toolchainInstallPath", mingwBinPath);
    profile.setValue("cpp.compilerName", QLatin1String("g++.exe"));
    profile.setValue("qbs.toolchain", QStringList() << "mingw" << "gcc");
    profile.setValue(QLatin1String("qbs.architecture"),
                     canonicalizeArchitecture(QString::fromLatin1(architecture)));
    profile.setValue(QLatin1String("qbs.endianness"),
                     QLatin1String("little"));
    profiles << profile;
}

int probe(Settings *settings)
{
    QList<Profile> profiles;
    if (HostOsInfo::isWindowsHost()) {
        msvcProbe(settings, profiles);
        mingwProbe(settings, profiles);
    } else if (HostOsInfo::isOsxHost()) {
        osxProbe(settings, profiles);
        specific_probe(settings, profiles, QLatin1String("gcc"));
        specific_probe(settings, profiles, QLatin1String("clang"));
    } else {
        specific_probe(settings, profiles, QLatin1String("gcc"));
        specific_probe(settings, profiles, QLatin1String("clang"));
    }

    if (profiles.isEmpty()) {
        qbsWarning() << Tr::tr("Could not detect any toolchains. No profile created.");
    } else if (profiles.count() == 1 && settings->defaultProfile().isEmpty()) {
        const QString profileName = profiles.first().name();
        qbsInfo() << Tr::tr("Making profile '%1' the default.").arg(profileName);
        settings->setValue(QLatin1String("defaultProfile"), profileName);
    }
    return 0;
}

QString canonicalizeArchitecture(const QString &arch)
{
    QMap<QString, QStringList> archMap;
    archMap.insert(QLatin1String("x86"), QStringList()
        << QLatin1String("i386")
        << QLatin1String("i486")
        << QLatin1String("i586")
        << QLatin1String("i686")
        << QLatin1String("ia32")
        << QLatin1String("ia-32")
        << QLatin1String("x86_32")
        << QLatin1String("x86-32")
        << QLatin1String("intel32"));

    archMap.insert(QLatin1String("x86_64"), QStringList()
        << QLatin1String("x86-64")
        << QLatin1String("x64")
        << QLatin1String("amd64")
        << QLatin1String("ia32e")
        << QLatin1String("em64t")
        << QLatin1String("intel64"));

    archMap.insert(QLatin1String("ia64"), QStringList()
        << QLatin1String("ia-64")
        << QLatin1String("itanium"));

    QMapIterator<QString, QStringList> i(archMap);
    while (i.hasNext())
    {
        i.next();
        if (i.value().contains(arch))
            return i.key();
    }

    return arch;
}
