// 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 "cmakewriter.h"
#include "cmakegenerator.h"
#include "cmakewriterv0.h"
#include "cmakewriterv1.h"

#include "qmlprojectmanager/qmlproject.h"
#include "qmlprojectmanager/buildsystem/qmlbuildsystem.h"

#include "utils/namevalueitem.h"

#include <QFile>
#include <QTextStream>

namespace QmlProjectManager {

namespace GenerateCmake {

const char TEMPLATE_BIG_RESOURCES[] = R"(
qt6_add_resources(%1 %2
    BIG_RESOURCES
    PREFIX "%3"
    VERSION 1.0
    FILES %4
))";

CMakeWriter::Ptr CMakeWriter::create(CMakeGenerator *parent)
{
    const QmlProject *project = parent->qmlProject();
    QTC_ASSERT(project, return {});

    const QmlBuildSystem *buildSystem = parent->buildSystem();
    QTC_ASSERT(buildSystem, return {});

    const QString versionString = buildSystem->versionDesignStudio();
    bool ok = false;
    if (float version = versionString.toFloat(&ok); ok && version > 4.4)
        return std::make_unique<CMakeWriterV1>(parent);

    return std::make_unique<CMakeWriterV0>(parent);
}

QString CMakeWriter::readTemplate(const QString &templatePath)
{
    QFile templatefile(templatePath);
    templatefile.open(QIODevice::ReadOnly);
    QTextStream stream(&templatefile);
    QString content = stream.readAll();
    templatefile.close();
    return content;
}

CMakeWriter::CMakeWriter(CMakeGenerator *parent)
    : m_parent(parent)
{}

const CMakeGenerator *CMakeWriter::parent() const
{
    return m_parent;
}

bool CMakeWriter::isPlugin(const NodePtr &node) const
{
    if (node->type == Node::Type::Module)
        return true;
    return false;
}

QString CMakeWriter::sourceDirName() const
{
    return "src";
}

void CMakeWriter::transformNode(NodePtr &) const
{}

std::vector<Utils::FilePath> CMakeWriter::files(const NodePtr &node, const FileGetter &getter) const
{
    std::vector<Utils::FilePath> out = getter(node);
    for (const NodePtr &child : node->subdirs) {
        if (child->type == Node::Type::Module)
            continue;

        auto childFiles = files(child, getter);
        out.insert(out.end(), childFiles.begin(), childFiles.end());
    }
    return out;
}

std::vector<Utils::FilePath> CMakeWriter::qmlFiles(const NodePtr &node) const
{
    return files(node, [](const NodePtr &n) { return n->files; });
}

std::vector<Utils::FilePath> CMakeWriter::singletons(const NodePtr &node) const
{
    return files(node, [](const NodePtr &n) { return n->singletons; });
}

std::vector<Utils::FilePath> CMakeWriter::resources(const NodePtr &node) const
{
    return files(node, [](const NodePtr &n) { return n->resources; });
}

std::vector<Utils::FilePath> CMakeWriter::sources(const NodePtr &node) const
{
    return files(node, [](const NodePtr &n) { return n->sources; });
}

std::vector<QString> CMakeWriter::plugins(const NodePtr &node) const
{
    QTC_ASSERT(parent(), return {});
    std::vector<QString> out;
    collectPlugins(node, out);
    return out;
}

QString CMakeWriter::getEnvironmentVariable(const QString &key) const
{
    QTC_ASSERT(parent(), return {});

    QString value;
    if (m_parent->buildSystem()) {
        auto envItems = m_parent->buildSystem()->environment();
        auto confEnv = std::find_if(
            envItems.begin(), envItems.end(), [key](const Utils::EnvironmentItem &item) {
                return item.name == key;
            });
        if (confEnv != envItems.end())
            value = confEnv->value;
    }
    return value;
}

QString CMakeWriter::makeRelative(const NodePtr &node, const Utils::FilePath &path) const
{
    const QString dir = node->dir.toString();
    return "\"" + Utils::FilePath::calcRelativePath(path.toString(), dir) + "\"";
}

QString CMakeWriter::makeQmlFilesBlock(const NodePtr &node) const
{
    QTC_ASSERT(parent(), return {});

    QString qmlFileContent;
    for (const Utils::FilePath &path : qmlFiles(node))
        qmlFileContent.append(QString("\t\t%1\n").arg(makeRelative(node, path)));

    QString str;
    if (!qmlFileContent.isEmpty())
        str.append(QString("\tQML_FILES\n%1").arg(qmlFileContent));

    return str;
}

QString CMakeWriter::makeSingletonBlock(const NodePtr &node) const
{
    QString str;
    const QString setProperties("set_source_files_properties(%1\n\tPROPERTIES\n\t\t%2 %3\n)\n\n");
    for (const Utils::FilePath &path : node->singletons)
        str.append(setProperties.arg(path.fileName()).arg("QT_QML_SINGLETON_TYPE").arg("true"));
    return str;
}

QString CMakeWriter::makeSubdirectoriesBlock(const NodePtr &node) const
{
    QTC_ASSERT(parent(), return {});

    QString str;
    for (const NodePtr &n : node->subdirs) {
        if (n->type == Node::Type::Module || n->type == Node::Type::Library
            || n->type == Node::Type::App || parent()->hasChildModule(n))
            str.append(QString("add_subdirectory(%1)\n").arg(n->dir.fileName()));
    }
    return str;
}

QString CMakeWriter::makeSetEnvironmentFn() const
{
    QTC_ASSERT(parent(), return {});
    QTC_ASSERT(parent()->buildSystem(), return {});

    const QmlBuildSystem *buildSystem = parent()->buildSystem();
    const QString configFile = getEnvironmentVariable(ENV_VARIABLE_CONTROLCONF);

    QString out("inline void set_qt_environment() {\n");
    for (Utils::EnvironmentItem &envItem : buildSystem->environment()) {
        QString key = envItem.name;
        QString value = envItem.value;
        if (value == configFile)
            value.prepend(":/");
        out.append(QString("\tqputenv(\"%1\", \"%2\");\n").arg(key).arg(value));
    }
    out.append("}");

    return out;
}

std::tuple<QString, QString> CMakeWriter::makeResourcesBlocks(const NodePtr &node) const
{
    QString resourcesOut;
    QString bigResourcesOut;

    QString resourceFiles;
    std::vector<QString> bigResources;
    for (const Utils::FilePath &path : resources(node)) {
        if (path.fileSize() > 5000000) {
            bigResources.push_back(makeRelative(node, path));
            continue;
        }
        resourceFiles.append(QString("\t\t%1\n").arg(makeRelative(node, path)));
    }

    if (!resourceFiles.isEmpty())
        resourcesOut.append(QString("\tRESOURCES\n%1").arg(resourceFiles));

    QString templatePostfix;
    if (!bigResources.empty()) {
        QString resourceContent;
        for (const QString &res : bigResources)
            resourceContent.append(QString("\n    %1").arg(res));

        const QString prefixPath = QString(node->uri).replace('.', '/');
        const QString prefix = "/qt/qml/" + prefixPath;
        const QString resourceName = node->name + "BigResource";

        bigResourcesOut = QString::fromUtf8(TEMPLATE_BIG_RESOURCES, -1)
                              .arg(node->name, resourceName, prefix, resourceContent);
    }

    return {resourcesOut, bigResourcesOut};
}

void CMakeWriter::writeFile(const Utils::FilePath &path, const QString &content) const
{
    QFile fileHandle(path.toString());
    if (fileHandle.open(QIODevice::WriteOnly)) {
        QTextStream stream(&fileHandle);
        stream << content;
    } else {
        QString text("Failed to write");
        CMakeGenerator::logIssue(ProjectExplorer::Task::Error, text, path);
    }
    fileHandle.close();
}

void CMakeWriter::collectPlugins(const NodePtr &node, std::vector<QString> &out) const
{
    if (isPlugin(node))
        out.push_back(node->name);
    for (const auto &child : node->subdirs)
        collectPlugins(child, out);
}

} // End namespace GenerateCmake.

} // End namespace QmlProjectManager.
