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

#include "puppetenvironmentbuilder.h"
#include "designersettings.h"
#include "qmldesignerplugin.h"

#include <model.h>

#include <app/app_version.h>

#include <projectexplorer/kit.h>
#include <projectexplorer/target.h>
#include <utils/algorithm.h>
#include <utils/hostosinfo.h>
#include <qmlprojectmanager/qmlmultilanguageaspect.h>
#include <qmlprojectmanager/qmlproject.h>
#include <qtsupport/qtkitinformation.h>
#include <qtsupport/qtversions.h>

#include <QLibraryInfo>

namespace QmlDesigner {

namespace {
Q_LOGGING_CATEGORY(puppetEnvirmentBuild, "qtc.puppet.environmentBuild", QtWarningMsg)

void filterOutQtBaseImportPath(QStringList *stringList)
{
    Utils::erase(*stringList, [](const QString &string) {
        QDir dir(string);
        return dir.dirName() == "qml"
               && !dir.entryInfoList(QStringList("QtTest"), QDir::Dirs).isEmpty();
    });
}

Utils::FilePath pathForBinPuppet(ProjectExplorer::Target *target)
{
    if (!target || !target->kit())
        return {};

    QtSupport::QtVersion *currentQtVersion = QtSupport::QtKitAspect::qtVersion(target->kit());

    if (currentQtVersion)
        return currentQtVersion->binPath()
            .pathAppended(QString{"qml2puppet-"} + Core::Constants::IDE_VERSION_LONG)
            .withExecutableSuffix();

    return {};
}

} // namespace

QProcessEnvironment PuppetEnvironmentBuilder::processEnvironment() const
{
    qCInfo(puppetEnvirmentBuild) << Q_FUNC_INFO;
    m_availablePuppetType = determinePuppetType();
    m_environment = Utils::Environment::systemEnvironment();

    addKit();
    addRendering();
    addControls();
    addPixelRatio();
    addVirtualKeyboard();
    addQuick3D();
    addForceQApplication();
    addMultiLanguageDatatbase();
    addImportPaths();
    addCustomFileSelectors();

    qCInfo(puppetEnvirmentBuild) << "Puppet environment:" << m_environment.toStringList();

    return m_environment.toProcessEnvironment();
}

QProcessEnvironment PuppetEnvironmentBuilder::createEnvironment(
    ProjectExplorer::Target *target, const DesignerSettings &designerSettings, const Model &model)
{
    PuppetEnvironmentBuilder builder{target, designerSettings, model};
    return builder.processEnvironment();
}

bool PuppetEnvironmentBuilder::usesVirtualKeyboard() const
{
    if (m_target) {
        auto *qmlbuild = qobject_cast<QmlProjectManager::QmlBuildSystem *>(m_target->buildSystem());

        const Utils::EnvironmentItem virtualKeyboard("QT_IM_MODULE", "qtvirtualkeyboard");
        return qmlbuild && qmlbuild->environment().indexOf(virtualKeyboard);
    }

    return false;
}

QString PuppetEnvironmentBuilder::getStyleConfigFileName() const
{
    if (m_target) {
        for (const Utils::FilePath &fileName :
             m_target->project()->files(ProjectExplorer::Project::SourceFiles)) {
            if (fileName.fileName() == "qtquickcontrols2.conf")
                return fileName.toString();
        }
    }

    return {};
}

void PuppetEnvironmentBuilder::addKit() const
{
    if (m_target) {
        if (m_availablePuppetType == PuppetType::Kit) {
            m_target->kit()->addToBuildEnvironment(m_environment);
            const QtSupport::QtVersion *qt = QtSupport::QtKitAspect::qtVersion(m_target->kit());
            if (qt) { // Kits without a Qt version should not have a puppet!
                // Update PATH to include QT_HOST_BINS
                m_environment.prependOrSetPath(qt->hostBinPath());
            }
        }
    }
}

void PuppetEnvironmentBuilder::addRendering() const
{
    m_environment.set("QML_BAD_GUI_RENDER_LOOP", "true");
    m_environment.set("QML_PUPPET_MODE", "true");
    m_environment.set("QML_DISABLE_DISK_CACHE", "true");
    m_environment.set("QMLPUPPET_RENDER_EFFECTS", "true");
    if (!m_environment.hasKey("QT_SCREEN_SCALE_FACTORS") && !m_environment.hasKey("QT_SCALE_FACTOR"))
        m_environment.set("QT_AUTO_SCREEN_SCALE_FACTOR", "1");

    const bool smoothRendering = m_designerSettings.value(DesignerSettingsKey::SMOOTH_RENDERING).toBool();

    if (smoothRendering)
        m_environment.set("QMLPUPPET_SMOOTH_RENDERING", "true");
}

void PuppetEnvironmentBuilder::addControls() const
{
    const QString controlsStyle = m_designerSettings.value(DesignerSettingsKey::CONTROLS_STYLE).toString();

    if (!controlsStyle.isEmpty()) {
        m_environment.set("QT_QUICK_CONTROLS_STYLE", controlsStyle);
        m_environment.set("QT_LABS_CONTROLS_STYLE", controlsStyle);
    }

    const QString styleConfigFileName = getStyleConfigFileName();

    if (!styleConfigFileName.isEmpty())
        m_environment.appendOrSet("QT_QUICK_CONTROLS_CONF", styleConfigFileName);
}

void PuppetEnvironmentBuilder::addPixelRatio() const
{
    m_environment.set("FORMEDITOR_DEVICE_PIXEL_RATIO",
                      QString::number(QmlDesignerPlugin::formEditorDevicePixelRatio()));
}

void PuppetEnvironmentBuilder::addVirtualKeyboard() const
{
    if (usesVirtualKeyboard()) {
        m_environment.set("QT_IM_MODULE", "qtvirtualkeyboard");
        m_environment.set("QT_VIRTUALKEYBOARD_DESKTOP_DISABLE", "1");
    }
}

void PuppetEnvironmentBuilder::addQuick3D() const
{
    // set env var if QtQuick3D import exists
    QmlDesigner::Import import = QmlDesigner::Import::createLibraryImport("QtQuick3D", "1.0");
    if (m_model.hasImport(import, true, true))
        m_environment.set("QMLDESIGNER_QUICK3D_MODE", "true");

    import = QmlDesigner::Import::createLibraryImport("QtQuick3D.Particles3D", "1.0");
    if (m_model.hasImport(import, true, true))
        m_environment.set("QMLDESIGNER_QUICK3D_PARTICLES3D_MODE", "true");

    bool particlemode = m_designerSettings.value("particleMode").toBool();
    if (!particlemode)
        m_environment.set("QT_QUICK3D_DISABLE_PARTICLE_SYSTEMS", "1");
    else
        m_environment.set("QT_QUICK3D_EDITOR_PARTICLE_SYSTEMS", "1");
}

void PuppetEnvironmentBuilder::addForceQApplication() const
{
    auto import = QmlDesigner::Import::createLibraryImport("QtCharts", "2.0");
    if (m_model.hasImport(import, true, true)) {
        m_environment.set("QMLDESIGNER_FORCE_QAPPLICATION", "true");
    } else if (m_target) {
        auto bs = qobject_cast<QmlProjectManager::QmlBuildSystem *>(m_target->buildSystem());
        if (bs && bs->widgetApp())
            m_environment.set("QMLDESIGNER_FORCE_QAPPLICATION", "true");
    }
}

void PuppetEnvironmentBuilder::addMultiLanguageDatatbase() const
{
    if (m_target) {
        if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current(m_target)) {
            if (!multiLanguageAspect->databaseFilePath().isEmpty())
                m_environment.set("QT_MULTILANGUAGE_DATABASE",
                                  multiLanguageAspect->databaseFilePath().toString());
        }
    }
}

void PuppetEnvironmentBuilder::addImportPaths() const
{
    QStringList importPaths = m_model.importPaths();

    if (m_availablePuppetType == PuppetType::Fallback)
        filterOutQtBaseImportPath(&importPaths);

    if (m_target) {
        QStringList designerImports = m_target->additionalData("QmlDesignerImportPath").toStringList();
        importPaths.append(designerImports);
    }

    if (m_availablePuppetType == PuppetType::Fallback)
        importPaths.prepend(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath));

    constexpr auto pathSep = Utils::HostOsInfo::pathListSeparator();
    m_environment.appendOrSet("QML2_IMPORT_PATH", importPaths.join(pathSep), pathSep);

    qCInfo(puppetEnvirmentBuild) << "Puppet import paths:" << importPaths;
}

void PuppetEnvironmentBuilder::addCustomFileSelectors() const
{
    QStringList customFileSelectors;

    if (m_target)
        customFileSelectors = m_target->additionalData("CustomFileSelectorsData").toStringList();

    customFileSelectors.append("DesignMode");

    constexpr auto pathSep = Utils::HostOsInfo::pathListSeparator();
    if (!customFileSelectors.isEmpty())
        m_environment.appendOrSet("QML_FILE_SELECTORS", customFileSelectors.join(","), pathSep);

    qCInfo(puppetEnvirmentBuild) << "Puppet selectors:" << customFileSelectors;
}

PuppetType PuppetEnvironmentBuilder::determinePuppetType() const
{
    if (m_target && m_target->kit() && m_target->kit()->isValid()) {
        if (pathForBinPuppet(m_target).isExecutableFile())
            return PuppetType::Kit;
    }

    return PuppetType::Fallback;
}

} // namespace QmlDesigner
