// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "toolchainkitaspect.h"

#include "devicesupport/devicekitaspects.h"
#include "devicesupport/devicemanager.h"
#include "devicesupport/idevice.h"
#include "kit.h"
#include "kitaspect.h"
#include "kitmanager.h"
#include "projectexplorerconstants.h"
#include "projectexplorertr.h"
#include "toolchainmanager.h"
#include "toolchainoptionspage.h"

#include <utils/async.h>
#include <utils/layoutbuilder.h>
#include <utils/macroexpander.h>
#include <utils/stringutils.h>

#include <QAbstractListModel>
#include <QComboBox>
#include <QGridLayout>
#include <QJsonValue>
#include <QJsonObject>

using namespace Utils;

namespace ProjectExplorer {
namespace Internal {

class ToolchainListModel final : public QAbstractListModel
{
public:
    ToolchainListModel(const Kit &kit, const LanguageCategory &category, QObject *parent)
        : QAbstractListModel(parent)
        , m_kit(kit)
        , m_category(category)
    {
        reset();
    }

    void reset()
    {
        beginResetModel();
        m_bundles.clear();
        if (const IDeviceConstPtr device = BuildDeviceKitAspect::device(&m_kit)) {
            const Toolchains ltcList = ToolchainManager::toolchains(
                [this](const Toolchain *tc) { return m_category.contains(tc->language()); });
            const Toolchains toolchainsForBuildDevice
                = Utils::filtered(ltcList, [device](Toolchain *tc) {
                      return tc->compilerCommand().isSameDevice(device->rootPath());
                  });
            const QList<ToolchainBundle> bundlesForBuildDevice = ToolchainBundle::collectBundles(
                toolchainsForBuildDevice, ToolchainBundle::HandleMissing::CreateAndRegister);
            for (const ToolchainBundle &b : bundlesForBuildDevice)
                m_bundles.append(b);
        }
        m_bundles.append(std::nullopt);
        endResetModel();
    }

    int rowCount(const QModelIndex &parent = {}) const final
    {
        return parent.isValid() ? 0 : m_bundles.size();
    }

    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const final
    {
        if (!index.isValid() || index.row() >= m_bundles.size())
            return {};
        return toolchainBundleData(m_bundles.at(index.row()), 0, role);
    }

private:
    const Kit &m_kit;
    const LanguageCategory m_category;
    QList<std::optional<ToolchainBundle>> m_bundles;
};

class ToolchainKitAspectImpl final : public KitAspect
{
public:
    ToolchainKitAspectImpl(Kit *k, const KitAspectFactory *factory) : KitAspect(k, factory)
    {
        m_sortedLanguageCategories = sorted(
            ToolchainManager::languageCategories(),
            [](const LanguageCategory &l1, const LanguageCategory &l2) {
                return ToolchainManager::displayNameOfLanguageCategory(l1)
                < ToolchainManager::displayNameOfLanguageCategory(l2);
            });
        QTC_ASSERT(!m_sortedLanguageCategories.isEmpty(), return);
        for (const LanguageCategory &lc : std::as_const(m_sortedLanguageCategories)) {
            const auto model = new ToolchainListModel(*kit(), lc, this);
            auto getter = [lc](const Kit &k) {
                for (const Id lang : lc) {
                    if (Toolchain * const currentTc = ToolchainKitAspect::toolchain(&k, lang))
                        return currentTc->bundleId().toSetting();
                }
                return QVariant();
            };
            auto setter = [lc](Kit &k, const QVariant &v) {
                const Id bundleId = Id::fromSetting(v);
                const Toolchains bundleTcs = ToolchainManager::toolchains(
                    [bundleId](const Toolchain *tc) { return tc->bundleId() == bundleId; });
                for (const Id lang : lc) {
                    Toolchain * const tc = Utils::findOrDefault(bundleTcs, [lang](const Toolchain *tc) {
                        return tc->language() == lang;
                    });
                    if (tc)
                        ToolchainKitAspect::setToolchain(&k, tc);
                    else
                        ToolchainKitAspect::clearToolchain(&k, lang);
                }
            };
            auto resetModel = [model] { model->reset(); };
            addListAspectSpec({model, std::move(getter), std::move(setter), std::move(resetModel)});
        }

        connect(ToolchainManager::instance(), &ToolchainManager::toolchainUpdated,
                this, &KitAspect::refresh);

        setManagingPage(Constants::TOOLCHAIN_SETTINGS_PAGE_ID);
    }

private:
    void addToInnerLayout(Layouting::Layout &layout) override
    {
        const auto mainWidget = createSubWidget<QWidget>();
        mainWidget->setContentsMargins(0, 0, 0, 0);

        const auto grid = new QGridLayout(mainWidget);
        grid->setContentsMargins(0, 0, 0, 0);
        grid->setColumnStretch(1, 2);

        int row = 0;
        const QList<QComboBox *> cbList = comboBoxes();
        QTC_ASSERT(cbList.size() == m_sortedLanguageCategories.size(), return);
        for (const LanguageCategory &lc : std::as_const(m_sortedLanguageCategories)) {
            grid->addWidget(
                new QLabel(ToolchainManager::displayNameOfLanguageCategory(lc) + ':'), row, 0);
            grid->addWidget(cbList.at(row), row, 1);
            ++row;
        }
        addMutableAction(mainWidget);
        layout.addItem(mainWidget);
    }

    QList<LanguageCategory> m_sortedLanguageCategories;
};

class ToolchainKitAspectFactory : public KitAspectFactory
{
public:
    ToolchainKitAspectFactory();

private:
    Tasks validate(const Kit *k) const override;
    void fix(Kit *k) override;
    void setup(Kit *k) override;

    KitAspect *createKitAspect(Kit *k) const override;

    ItemList toUserOutput(const Kit *k) const override;

    void addToBuildEnvironment(const Kit *k, Environment &env) const override;
    void addToRunEnvironment(const Kit *, Environment &) const override {}

    void addToMacroExpander(Kit *kit, MacroExpander *expander) const override;
    QList<OutputLineParser *> createOutputParsers(const Kit *k) const override;
    QSet<Id> availableFeatures(const Kit *k) const override;

    void onKitsLoaded() override;

    void toolChainUpdated(Toolchain *tc);
    void toolChainsDeregistered();

    std::optional<QtTaskTree::ExecutableItem> autoDetect(
        Kit *kit,
        const Utils::FilePaths &searchPaths,
        const DetectionSource &detectionSource,
        const LogCallback &logCallback) const override;

    std::optional<QtTaskTree::ExecutableItem> removeAutoDetected(
        const QString &detectionSource, const LogCallback &logCallback) const override;

    void listAutoDetected(
        const QString &detectionSource, const LogCallback &logCallback) const override;

    Utils::Result<QtTaskTree::ExecutableItem> createAspectFromJson(
        const DetectionSource &detectionSource,
        const Utils::FilePath &rootPath,
        Kit *kit,
        const QJsonValue &json,
        const LogCallback &logCallback) const override;
};

ToolchainKitAspectFactory::ToolchainKitAspectFactory()
{
    setId(ToolchainKitAspect::id());
    setJsonKeys({"compiler"});
    setDisplayName(Tr::tr("Compiler"));
    setDescription(Tr::tr("The compiler to use for building.<br>"
                          "Make sure the compiler will produce binaries compatible "
                          "with the target device, Qt version and other libraries used."));
    setPriority(30000);
}

Tasks ToolchainKitAspectFactory::validate(const Kit *k) const
{
    Tasks result;

    const QList<Toolchain*> tcList = ToolchainKitAspect::toolChains(k);
    if (tcList.isEmpty()) {
        result << BuildSystemTask(Task::Warning, ToolchainKitAspect::msgNoToolchainInTarget());
    } else {
        QSet<Abi> targetAbis;
        for (const Toolchain *tc : tcList) {
            targetAbis.insert(tc->targetAbi());
            result << tc->validateKit(k);
        }
        if (targetAbis.count() != 1) {
            result << BuildSystemTask(Task::Error,
                                      Tr::tr("Compilers produce code for different ABIs: %1")
                                          .arg(Utils::transform<QList>(targetAbis, &Abi::toString).join(", ")));
        }
    }
    return result;
}

void ToolchainKitAspectFactory::fix(Kit *k)
{
    QTC_ASSERT(ToolchainManager::isLoaded(), return);
    const QList<Id> languages = ToolchainManager::allLanguages();
    const IDeviceConstPtr dev = BuildDeviceKitAspect::device(k);
    for (const Id l : languages) {
        const QByteArray tcId = ToolchainKitAspect::toolchainId(k, l);
        if (tcId.isEmpty())
            continue;
        Toolchain * const tc = ToolchainManager::findToolchain(tcId);
        if (!tc)  {
            qWarning("Tool chain set up in kit \"%s\" for \"%s\" not found.",
                     qPrintable(k->displayName()),
                     qPrintable(ToolchainManager::displayNameOfLanguageId(l)));
            ToolchainKitAspect::clearToolchain(k, l); // make sure to clear out no longer known tool chains
        } else if (!dev || !dev->rootPath().isSameDevice(tc->compilerCommand())) {
            ToolchainKitAspect::clearToolchain(k, l);
        }
    }
}

static Id findLanguage(const QString &ls)
{
    QString lsUpper = ls.toUpper();
    return Utils::findOrDefault(ToolchainManager::allLanguages(),
                                [lsUpper](Id l) { return lsUpper == l.toString().toUpper(); });
}

using LanguageAndAbi = std::pair<Id, Abi>;
using LanguagesAndAbis = QList<LanguageAndAbi>;

static void setToolchainsFromAbis(Kit *k, const LanguagesAndAbis &abisByLanguage)
{
    if (abisByLanguage.isEmpty())
        return;

    // First transform languages into categories, so we can work on the bundle level.
    // Obviously, we assume that the caller does not specify different ABIs for
    // languages from the same category.
    const QList<LanguageCategory> allCategories = ToolchainManager::languageCategories();
    QHash<LanguageCategory, Abi> abisByCategory;
    for (const LanguageAndAbi &langAndAbi : abisByLanguage) {
        const auto category
            = Utils::findOrDefault(allCategories, [&langAndAbi](const LanguageCategory &cat) {
                  return cat.contains(langAndAbi.first);
              });
        QTC_ASSERT(!category.isEmpty(), continue);
        abisByCategory.insert(category, langAndAbi.second);
    }

    const IDeviceConstPtr dev = BuildDeviceKitAspect::device(k);

    // Get bundles.
    const QList<ToolchainBundle> bundles = ToolchainBundle::collectBundles(
        ToolchainBundle::HandleMissing::CreateAndRegister);

    // Set a matching bundle for each LanguageCategory/Abi pair, if possible.
    for (auto it = abisByCategory.cbegin(); it != abisByCategory.cend(); ++it) {
        const QList<ToolchainBundle> matchingBundles
            = Utils::filtered(bundles, [&](const ToolchainBundle &b) {
                  if (!dev)
                      return false;
                  for (const Toolchain * const tc : b.toolchains()) {
                      if (!dev->rootPath().isSameDevice(tc->compilerCommand()))
                          return false;
                  }
                  return b.factory() && b.factory()->languageCategory() == it.key()
                         && b.targetAbi() == it.value();
              });

        if (matchingBundles.isEmpty()) {
            for (const Id language : it.key())
                ToolchainKitAspect::clearToolchain(k, language);
            continue;
        }

        const ToolchainBundle bestBundle = Utils::minElementOr(
            matchingBundles, &ToolchainManager::isBetterToolchain, matchingBundles.first());
        ToolchainKitAspect::setBundle(k, bestBundle);
    }
}

static void setMissingToolchainsToHostAbi(Kit *k, const QList<Id> &languageBlacklist)
{
    LanguagesAndAbis abisByLanguage;
    for (const Id lang : ToolchainManager::allLanguages()) {
        if (languageBlacklist.contains(lang) || ToolchainKitAspect::toolchain(k, lang))
            continue;
        abisByLanguage.emplaceBack(lang, Abi::hostAbi());
    }
    setToolchainsFromAbis(k, abisByLanguage);
}

static void setupForSdkKit(Kit *k)
{
    const Store value = storeFromVariant(k->value(ToolchainKitAspect::id()));
    bool lockToolchains = !value.isEmpty();

    // The installer provides two kinds of entries for toolchains:
    //   a) An actual toolchain id, for e.g. Boot2Qt where the installer ships the toolchains.
    //   b) An ABI string, for Desktop Qt. In this case, it is our responsibility to find
    //      a matching toolchain on the host system.
    LanguagesAndAbis abisByLanguage;
    for (auto i = value.constBegin(); i != value.constEnd(); ++i) {
        const Id lang = findLanguage(stringFromKey(i.key()));

        if (!lang.isValid()) {
            lockToolchains = false;
            continue;
        }

        const QByteArray id = i.value().toByteArray();
        if (ToolchainManager::findToolchain(id))
            continue;

        // No toolchain with this id exists. Check whether it's an ABI string.
        lockToolchains = false;
        const Abi abi = Abi::fromString(QString::fromUtf8(id));
        if (!abi.isValid())
            continue;

        abisByLanguage.emplaceBack(lang, abi);
    }
    setToolchainsFromAbis(k, abisByLanguage);
    setMissingToolchainsToHostAbi(k, Utils::transform(abisByLanguage, &LanguageAndAbi::first));

    k->setSticky(ToolchainKitAspect::id(), lockToolchains);
}

static void setupForNonSdkKit(Kit *k)
{
    setMissingToolchainsToHostAbi(k, {});
    k->setSticky(ToolchainKitAspect::id(), false);
}

void ToolchainKitAspectFactory::setup(Kit *k)
{
    QTC_ASSERT(ToolchainManager::isLoaded(), return);
    QTC_ASSERT(k, return);

    // Already set up?
    for (const Id lang : ToolchainManager::allLanguages()) {
        if (ToolchainKitAspect::toolchain(k, lang))
            return;
    }

    if (k->detectionSource().isSdkProvided())
        setupForSdkKit(k);
    else
        setupForNonSdkKit(k);
}

KitAspect *ToolchainKitAspectFactory::createKitAspect(Kit *k) const
{
    QTC_ASSERT(k, return nullptr);
    return new ToolchainKitAspectImpl(k, this);
}

KitAspectFactory::ItemList ToolchainKitAspectFactory::toUserOutput(const Kit *k) const
{
    Toolchain *tc = ToolchainKitAspect::cxxToolchain(k);
    return {{Tr::tr("Compiler"), tc ? tc->displayName() : Tr::tr("None", "No compiler")}};
}

void ToolchainKitAspectFactory::addToBuildEnvironment(const Kit *k, Environment &env) const
{
    Toolchain *tc = ToolchainKitAspect::cxxToolchain(k);
    if (tc)
        tc->addToEnvironment(env);
}

void ToolchainKitAspectFactory::addToMacroExpander(Kit *kit, MacroExpander *expander) const
{
    QTC_ASSERT(kit, return);

    // Compatibility with Qt Creator < 4.2:
    expander->registerVariable("Compiler:Name", Tr::tr("Compiler"),
                               [kit] {
                                   const Toolchain *tc = ToolchainKitAspect::cxxToolchain(kit);
                                   return tc ? tc->displayName() : Tr::tr("None", "No compiler");
                               });

    expander->registerVariable("Compiler:Executable", Tr::tr("Path to the compiler executable"),
                               [kit] {
                                   const Toolchain *tc = ToolchainKitAspect::cxxToolchain(kit);
                                   return tc ? tc->compilerCommand().path() : QString();
                               });

    // After 4.2
    expander->registerPrefix(
        "Compiler:Name", "C", Tr::tr("Compiler for different languages"), [kit](const QString &ls) {
            const Toolchain *tc = ToolchainKitAspect::toolchain(kit, findLanguage(ls));
            return tc ? tc->displayName() : Tr::tr("None", "No compiler");
        });
    expander->registerPrefix(
        "Compiler:Executable",
        "C",
        Tr::tr("Compiler executable for different languages"),
        [kit](const QString &ls) {
            const Toolchain *tc = ToolchainKitAspect::toolchain(kit, findLanguage(ls));
            return tc ? tc->compilerCommand().path() : QString();
        });
}

QList<OutputLineParser *> ToolchainKitAspectFactory::createOutputParsers(const Kit *k) const
{
    for (const Id langId : {Id(Constants::CXX_LANGUAGE_ID), Id(Constants::C_LANGUAGE_ID)}) {
        if (const Toolchain * const tc = ToolchainKitAspect::toolchain(k, langId))
            return tc->createOutputParsers();
    }
    return {};
}

QSet<Id> ToolchainKitAspectFactory::availableFeatures(const Kit *k) const
{
    QSet<Id> result;
    for (Toolchain *tc : ToolchainKitAspect::toolChains(k))
        result.insert(tc->typeId().withPrefix("ToolChain."));
    return result;
}

void ToolchainKitAspectFactory::onKitsLoaded()
{
    for (Kit *k : KitManager::kits())
        fix(k);

    connect(ToolchainManager::instance(), &ToolchainManager::toolchainsDeregistered,
            this, &ToolchainKitAspectFactory::toolChainsDeregistered);
    connect(ToolchainManager::instance(), &ToolchainManager::toolchainUpdated,
            this, &ToolchainKitAspectFactory::toolChainUpdated);
}

void ToolchainKitAspectFactory::toolChainUpdated(Toolchain *tc)
{
    for (Kit *k : KitManager::kits()) {
        if (ToolchainKitAspect::toolchain(k, tc->language()) == tc)
            notifyAboutUpdate(k);
    }
}

void ToolchainKitAspectFactory::toolChainsDeregistered()
{
    for (Kit *k : KitManager::kits())
        fix(k);
}

std::optional<QtTaskTree::ExecutableItem> ToolchainKitAspectFactory::autoDetect(
    Kit *kit,
    const FilePaths &searchPaths,
    const DetectionSource &detectionSource,
    const LogCallback &logCallback) const
{
    const auto searchToolchains = [searchPaths](Async<Toolchain *> &async) {
        async.setConcurrentCallData(
            [](QPromise<Toolchain *> &promise,
               IDevice::ConstPtr device,
               const FilePaths &searchPaths) {
                ToolchainDetector detector({}, device, searchPaths);

                for (ToolchainFactory *factory : ToolchainFactory::allToolchainFactories()) {
                    const Toolchains detectedToolchains = factory->autoDetect(detector);

                    for (Toolchain *toolchain : detectedToolchains) {
                        if (toolchain->isValid())
                            promise.addResult(toolchain);
                    }
                }
            },
            DeviceManager::deviceForPath(searchPaths.first()),
            searchPaths);
    };

    const auto toolchainsDone =
        [kit, detectionSource, logCallback](const Async<Toolchain *> &async) {
            const Toolchains toolchains = async.results();

            for (Toolchain *toolchain : toolchains) {
                toolchain->setDetectionSource(detectionSource);
                logCallback(Tr::tr("Found toolchain: %1.").arg(toolchain->displayName()));
            }

            ToolchainManager::registerToolchains(toolchains);

            const QList<ToolchainBundle> bundles = ToolchainBundle::collectBundles(
                toolchains, ToolchainBundle::HandleMissing::CreateAndRegister);

            if (!bundles.isEmpty())
                ToolchainKitAspect::setBundle(kit, bundles.first());
        };

    return AsyncTask<Toolchain *>(searchToolchains, toolchainsDone);
}

std::optional<QtTaskTree::ExecutableItem> ToolchainKitAspectFactory::removeAutoDetected(
    const QString &detectionSource, const LogCallback &logCallback) const
{
    return QtTaskTree::QSyncTask([=] {
        const auto toolchains
            = filtered(ToolchainManager::toolchains(), [detectionSource](Toolchain *tc) {
                  return tc->detectionSource().id == detectionSource;
              });

        for (Toolchain *tc : toolchains)
            logCallback(Tr::tr("Removing toolchain: %1.").arg(tc->displayName()));

        ToolchainManager::deregisterToolchains(toolchains);
    });
}

void ToolchainKitAspectFactory::listAutoDetected(
    const QString &detectionSource, const LogCallback &logCallback) const
{
    for (const Toolchain *tc : ToolchainManager::toolchains()) {
        if (tc->detectionSource().isAutoDetected() && tc->detectionSource().id == detectionSource)
            logCallback(Tr::tr("Toolchain: %1.").arg(tc->displayName()));
    }
}

Result<QtTaskTree::ExecutableItem> ToolchainKitAspectFactory::createAspectFromJson(
    const DetectionSource &detectionSource,
    const Utils::FilePath &rootPath,
    Kit *kit,
    const QJsonValue &json,
    const LogCallback &logCallback) const
{
    Q_UNUSED(logCallback);

    if (!json.isObject()) {
        return ResultError(
            Tr::tr("Expected a JSON object for toolchain detection, got: %1.").arg(json.type()));
    }

    QJsonObject jsonObject = json.toObject();
    if (jsonObject.isEmpty()) {
        return ResultError(Tr::tr("No toolchains found in JSON object for detection."));
    }

    QList<QPair<Utils::Id, Utils::FilePath>> toolchains;
    for (auto it = jsonObject.constBegin(); it != jsonObject.constEnd(); ++it) {
        const Id language = findLanguage(it.key());
        if (!language.isValid())
            return ResultError(Tr::tr("Unknown language in toolchain detection: %1.").arg(it.key()));

        const FilePath path = rootPath.withNewPath(it.value().toString());
        toolchains.append(qMakePair(language, path));
    }

    const auto detect = [](QPromise<Toolchains> &promise,
                           const QList<ToolchainDescription> &tcds,
                           const DetectionSource &detectionSource) {
        Toolchains result;
        for (const ToolchainDescription &tcd : tcds) {
            for (ToolchainFactory *factory : ToolchainFactory::allToolchainFactories()) {
                const Toolchains detected = factory->detectForImport(tcd);
                for (Toolchain *detected : detected) {
                    if (detected->isValid())
                        detected->setDetectionSource(detectionSource);
                }
                result.append(detected);
            }
        }
        promise.addResult(result);
    };

    const auto setup = [toolchains, detectionSource, detect](Async<Toolchains> &async) {
        QList<ToolchainDescription> toolchainDescriptions;

        for (const auto &toolchain : toolchains) {
            ToolchainDescription tcd;
            tcd.compilerPath = toolchain.second;
            tcd.language = toolchain.first;
            toolchainDescriptions.append(tcd);
        }

        async.setConcurrentCallData(detect, toolchainDescriptions, detectionSource);
    };

    const auto bundle = [kit](const Async<Toolchains> &async) {
        const Toolchains allDetected = async.result();
        ToolchainManager::registerToolchains(allDetected);

        const QList<ToolchainBundle> bundles = ToolchainBundle::collectBundles(
            allDetected, ToolchainBundle::HandleMissing::CreateAndRegister);

        if (!bundles.isEmpty())
            ToolchainKitAspect::setBundle(kit, bundles.first());
    };

    return AsyncTask<Toolchains>(setup, bundle);
}

const ToolchainKitAspectFactory thsToolChainKitAspectFactory;
} // namespace Internal

Id ToolchainKitAspect::id()
{
    // "PE.Profile.ToolChain" until 4.2
    // "PE.Profile.ToolChains" temporarily before 4.3 (May 2017)
    return "PE.Profile.ToolChainsV3";
}

QByteArray ToolchainKitAspect::toolchainId(const Kit *k, Id language)
{
    QTC_ASSERT(ToolchainManager::isLoaded(), return nullptr);
    if (!k)
        return {};
    Store value = storeFromVariant(k->value(ToolchainKitAspect::id()));
    return value.value(language.toKey(), QByteArray()).toByteArray();
}

Toolchain *ToolchainKitAspect::toolchain(const Kit *k, Id language)
{
    return ToolchainManager::findToolchain(toolchainId(k, language));
}

Toolchain *ToolchainKitAspect::cToolchain(const Kit *k)
{
    return ToolchainManager::findToolchain(toolchainId(k, ProjectExplorer::Constants::C_LANGUAGE_ID));
}

Toolchain *ToolchainKitAspect::cxxToolchain(const Kit *k)
{
    return ToolchainManager::findToolchain(toolchainId(k, ProjectExplorer::Constants::CXX_LANGUAGE_ID));
}

QList<Toolchain *> ToolchainKitAspect::toolChains(const Kit *k)
{
    QTC_ASSERT(k, return {});

    const Store value = storeFromVariant(k->value(ToolchainKitAspect::id()));
    const QList<Toolchain *> tcList
            = transform<QList>(ToolchainManager::allLanguages(), [&value](Id l) {
                return ToolchainManager::findToolchain(value.value(l.toKey()).toByteArray());
            });
    return filtered(tcList, [](Toolchain *tc) { return tc; });
}

void ToolchainKitAspect::setToolchain(Kit *k, Toolchain *tc)
{
    QTC_ASSERT(tc, return);
    QTC_ASSERT(k, return);
    Store result = storeFromVariant(k->value(ToolchainKitAspect::id()));
    result.insert(tc->language().toKey(), tc->id());

    k->setValue(id(), variantFromStore(result));
}

void ToolchainKitAspect::setBundle(Kit *k, const ToolchainBundle &bundle)
{
    bundle.forEach<Toolchain>([k](Toolchain &tc) {
        setToolchain(k, &tc);
    });
}

void ToolchainKitAspect::clearToolchain(Kit *k, Id language)
{
    QTC_ASSERT(language.isValid(), return);
    QTC_ASSERT(k, return);

    Store result = storeFromVariant(k->value(ToolchainKitAspect::id()));
    result.insert(language.toKey(), QByteArray());
    k->setValue(id(), variantFromStore(result));
}

Abi ToolchainKitAspect::targetAbi(const Kit *k)
{
    const QList<Toolchain *> tcList = toolChains(k);
    // Find the best possible ABI for all the tool chains...
    Abi cxxAbi;
    QHash<Abi, int> abiCount;
    for (Toolchain *tc : tcList) {
        Abi ta = tc->targetAbi();
        if (tc->language() == Id(Constants::CXX_LANGUAGE_ID))
            cxxAbi = tc->targetAbi();
        abiCount[ta] = (abiCount.contains(ta) ? abiCount[ta] + 1 : 1);
    }
    QList<Abi> candidates;
    int count = -1;
    candidates.reserve(tcList.count());
    for (auto i = abiCount.begin(); i != abiCount.end(); ++i) {
        if (i.value() > count) {
            candidates.clear();
            candidates.append(i.key());
            count = i.value();
        } else if (i.value() == count) {
            candidates.append(i.key());
        }
    }

    // Found a good candidate:
    if (candidates.isEmpty())
        return Abi::hostAbi();
    if (candidates.contains(cxxAbi)) // Use Cxx compiler as a tie breaker
        return cxxAbi;
    return candidates.at(0); // Use basically a random Abi...
}

QString ToolchainKitAspect::msgNoToolchainInTarget()
{
    return Tr::tr("No compiler set in kit.");
}

} // namespace ProjectExplorer
