/* src/KMBuilderManifest.cpp
 *
 * Copyright (C) 2024 OpenKylin, Inc
 */

#include "KMBuilderManifest.h"

#include <filesystem>

#include "KMBuilderUtils.h"
#include "common/KMException.h"
#include "common/KMLogger.h"
#include "common/KMYAMLNodeHelper.h"

namespace fs = std::filesystem;

KMBuilderManifest::KMBuilderManifest(YAML::Node rootNode)
{
    m_rootNode = rootNode;
}

KMBuilderManifest::~KMBuilderManifest()
{}

/**
 * @brief : 工厂函数，根据yaml格式的清单文件创建清单对象
 * @param : [in] yaml，yaml清单文件
 * @return: std::shared_ptr<KMBuilderManifest>，创建成功则关联对象；
 * @throws: KMException，创建失败则函数抛出异常
 */  
std::shared_ptr<KMBuilderManifest> KMBuilderManifest::loadFromYAML(const std::string& yaml)
{
    if (!KMBuilderUtils::endsWith(yaml, ".yaml") && !KMBuilderUtils::endsWith(yaml, ".yml"))
    {
        throw KMException("Manifest file must be ***.yaml or ***.yml");
    }

    std::shared_ptr<KMBuilderManifest> manifest;
    YAML::Node rootNode = KMYAMLNodeHelper::loadFromYAML(yaml);
    manifest.reset(new KMBuilderManifest(rootNode));
    manifest->m_yaml = yaml;
    manifest->parseFromYAML();

    return manifest;
}

void KMBuilderManifest::parseFromYAML()
{
    // 此方法主要解析顶层属性
    KMYAMLNodeHelper helper(m_rootNode);

    m_version = helper.getStringValue("version");
    m_commands = helper.getStringListValue("command");
    m_channel = helper.getStringValue("channel", "stable");
    m_permissions = helper.getStringListValue("permission");
    m_module = helper.getStringValue("module");
    m_base = helper.getStringValue("base");
    m_runtime = helper.getStringValue("runtime");
    m_envs = helper.getStringMapValue("env");
    

    // package属性
    YAML::Node packageNode = m_rootNode["package"];
    KMYAMLNodeHelper package(packageNode);
    m_package.m_id = package.getStringValue("id");
    m_package.m_name = package.getStringValue("name");
    m_package.m_version = package.getStringValue("version");
    m_package.m_kind = package.getStringValue("kind", PACKGE_KIND_APP);
    m_package.m_description = package.getStringValue("description");
    m_package.m_names = package.fromPrefix("name[");
    m_package.m_descriptions = package.fromPrefix("description[");

    // modules属性
    std::string saved_demarshalBaseDir = m_demarshalBaseDir;
    std::string demarshalBaseDir = fs::path(m_yaml).parent_path().string();
    m_demarshalBaseDir = demarshalBaseDir;
    YAML::Node modulesNode = m_rootNode["modules"];
    m_modules = parseModules(modulesNode);
    m_demarshalBaseDir = saved_demarshalBaseDir;

    validate();
}

std::vector<std::shared_ptr<KMBuilderModule>> KMBuilderManifest::parseModules(const YAML::Node& modulesNode)
{
    if (!modulesNode.IsDefined())
    {
        throw KMException("Not found 'modules' in manifest file");
    }

    std::vector<std::shared_ptr<KMBuilderModule>> modules;
    if (modulesNode.IsSequence())
    {
        for (auto iter = modulesNode.begin(); iter != modulesNode.end(); iter++)
        {
            std::string saved_demarshalBaseDir = m_demarshalBaseDir;

            YAML::Node moduleNode = *iter;
            if (moduleNode.IsScalar())
            {
                // 共享模块，其内容为共享模块所在的文件相对路径，形如：openkylin-shared-modules/mate-common/mate-common.json
                YAML::Node shareModule = loadShareNode(moduleNode);
                if (!shareModule)
                {
                    KMWarn(moduleNode.as<std::string>() + " parse failed ");
                    continue ;
                }

                // 共享模块的base目录变更为共享模块文件所在的目录
                std::string shareFile = moduleNode.as<std::string>();
                shareFile = KMBuilderUtils::buildFilename(m_demarshalBaseDir, shareFile);
                m_demarshalBaseDir = fs::path(shareFile).parent_path().string();

                moduleNode = shareModule;
            }

            // 添加一个 module
            KMYAMLNodeHelper helper(moduleNode);
            std::shared_ptr<KMBuilderModule> builderModule = make_shared<KMBuilderModule>();
            builderModule->setProperty(KMBuilderModule::Module_Name, helper.getStringValue("name"));
            builderModule->setProperty(KMBuilderModule::Module_Subdir, helper.getStringValue("subdir"));           
            builderModule->setProperty(KMBuilderModule::Module_RmConfigure, helper.getBoolValue("rm-configure"));   
            builderModule->setProperty(KMBuilderModule::Module_Disable, helper.getBoolValue("disabled"));           
            builderModule->setProperty(KMBuilderModule::Module_NoAutogen, helper.getBoolValue("no-autogen"));       
            builderModule->setProperty(KMBuilderModule::Module_NoParallelMake, helper.getBoolValue("no-parallel-make"));    
            builderModule->setProperty(KMBuilderModule::Module_NoMakeInstall, helper.getBoolValue("no-make-install"));      
            builderModule->setProperty(KMBuilderModule::Module_NoPythonTimestampFix, helper.getBoolValue("no-python-timestamp-fix"));
            builderModule->setProperty(KMBuilderModule::Module_Cmake, helper.getBoolValue("cmake"));

            std::string buildSystem = helper.getStringValue("buildsystem");
            builderModule->setBuildSystem(buildSystem);

            builderModule->setProperty(KMBuilderModule::Module_InstallRule, helper.getStringValue("install-rule"));
            builderModule->setProperty(KMBuilderModule::Module_TestRule, helper.getStringValue("test-rule"));
            builderModule->setProperty(KMBuilderModule::Module_Builddir, helper.getBoolValue("builddir"));

            YAML::Node sourcesNode = moduleNode["sources"];
            builderModule->setBuildSources(parseSources(sourcesNode));

            builderModule->setConfigOptions(helper.getStringListValue("config-opts"));
            builderModule->setBuildCommands(helper.getStringListValue("build-commands"));
            builderModule->setTestCommands(helper.getStringListValue("test-commands"));
            builderModule->setCleanup(helper.getStringListValue("cleanup"));
            builderModule->setCleanupPlatform(helper.getStringListValue("cleanup-platform"));
            builderModule->setPostInstalls(helper.getStringListValue("post-install"));

            // 处理 modules 包含的 子modules
            YAML::Node subModules = moduleNode["modules"];
            if (subModules.IsDefined())
            {
                builderModule->setBuilderModules(parseModules(subModules));
            }
            YAML::Node buildOptions = moduleNode["build-options"];
            builderModule->setBuildOptions(parseBuildOptions(buildOptions));

            modules.push_back(builderModule);

            m_demarshalBaseDir = saved_demarshalBaseDir;
        }
    }
    else
    {
        throw KMException("'modules' must be sequence in manifest file");
    }

    return modules;
}

YAML::Node KMBuilderManifest::loadShareNode(const YAML::Node& shareNode)
{
    std::string shareFile = shareNode.IsScalar() ? shareNode.as<std::string>() : "";
    shareFile = KMBuilderUtils::buildFilename(m_demarshalBaseDir, shareFile);

    if (KMBuilderUtils::endsWith(shareFile, ".yaml") || KMBuilderUtils::endsWith(shareFile, ".yml"))
    {
        return KMYAMLNodeHelper::loadFromYAML(shareFile);
    }
    else if (KMBuilderUtils::endsWith(shareFile, ".json"))
    {
        return KMYAMLNodeHelper::loadFromJson(shareFile);
    }

    return YAML::Node();
}

std::vector<std::shared_ptr<KMBuilderSource>> KMBuilderManifest::parseSources(const YAML::Node& sourcesNode)
{
    if (!sourcesNode.IsDefined())
    {
        throw KMException("Not found 'sources' in manifest file");
    }

    std::vector<std::shared_ptr<KMBuilderSource>> sources;
    if (sourcesNode.IsSequence())
    {
        for (auto iter = sourcesNode.begin(); iter != sourcesNode.end(); iter++)
        {
            std::string saved_demarshalBaseDir = m_demarshalBaseDir;

            YAML::Node sourceNode = *iter;
            if (sourceNode.IsScalar())
            {
                // 共享源，其内容为共享源所在的文件相对路径，形如：openkylin-shared-modules/mate-common/mate-common.json
                YAML::Node shareSource = loadShareNode(sourceNode);
                if (!shareSource)
                {
                    KMWarn(sourceNode.as<std::string>() + " parse failed ");
                    continue ;
                }

                // 共享模块的base目录变更为共享模块文件所在的目录
                std::string shareFile = sourceNode.as<std::string>();
                shareFile = KMBuilderUtils::buildFilename(m_demarshalBaseDir, shareFile);
                m_demarshalBaseDir = fs::path(shareFile).parent_path().string();

                sourceNode = shareSource;
            }

            // 动态构建源类对象，并解析源
            KMYAMLNodeHelper helper(sourceNode);
            std::string sourceType = helper.getStringValue("type");
            std::shared_ptr<KMBuilderSource> source = KMBuilderSource::createSource(sourceType);
            source->setBaseDir(m_demarshalBaseDir);
            source->deserialize(sourceNode);

            sources.push_back(source);

            m_demarshalBaseDir = saved_demarshalBaseDir;
        }
    }
    else
    {
        throw KMException("'sources' must be sequence in manifest file");
    }

    return sources;
}

std::shared_ptr<KMBuildOptions> KMBuilderManifest::parseBuildOptions(YAML::Node &node)
{
    std::shared_ptr<KMBuildOptions> buildOptions = make_shared<KMBuildOptions>();

    if (!node.IsDefined())
    {
        return buildOptions;
    }

    KMYAMLNodeHelper helper(node);
    buildOptions->m_strip = helper.getBoolValue("strip");
    buildOptions->m_noDebuginfo = helper.getBoolValue("no-debuginfo");
    buildOptions->m_noDebuginfoCompression = helper.getBoolValue("no-debuginfo-compression");
    buildOptions->m_cFlags = helper.getStringValue("cflags");
    buildOptions->m_cflagsOverride = helper.getBoolValue("cflags-override");
    buildOptions->m_cppFlags = helper.getStringValue("cppflags");
    buildOptions->m_cppFlagsOverride = helper.getBoolValue("cppflags-override");
    buildOptions->m_cxxFlags = helper.getStringValue("cxxflags");
    buildOptions->m_cxxFlagsOverride = helper.getBoolValue("cxxflags-override");
    buildOptions->m_ldFlags = helper.getStringValue("ldflags");
    buildOptions->m_ldFlagsOverride = helper.getBoolValue("ldflags-override");
    buildOptions->m_appendPath = helper.getStringValue("append-path");
    buildOptions->m_prependPath = helper.getStringValue("prepend-path");
    buildOptions->m_appendLdLibraryPath = helper.getStringValue("append-ld-library-path");
    buildOptions->m_prependLdLibraryPath = helper.getStringValue("prepend-ld-library-path");
    buildOptions->m_appendPkgConfigPath = helper.getStringValue("append-pkg-config-path");
    buildOptions->m_prependPkgConfigPath = helper.getStringValue("prepend-pkg-config-path");
    buildOptions->m_prefix = helper.getStringValue("prefix");
    buildOptions->m_libDir = helper.getStringValue("libdir");
    buildOptions->m_buildArgs = helper.getStringListValue("build-args");
    buildOptions->m_testArgs = helper.getStringListValue("test-args");
    buildOptions->m_configOpts = helper.getStringListValue("config-opts");
    buildOptions->m_secretOpts = helper.getStringListValue("secret-opts");
    buildOptions->m_makeArgs = helper.getStringListValue("make-args");
    buildOptions->m_makeInstallArgs = helper.getStringListValue("make-install-args");

    buildOptions->m_env = helper.getStringMapValue("env");
    buildOptions->m_secretEnv = helper.getStringMapValue("secret-env");

    return buildOptions;
}

void KMBuilderManifest::validate()
{
    if (m_version.empty())
    {
        throw KMException("Not found 'version' in manifest file");
    }
    
    if (m_base.empty())
    {
        throw KMException("Not found 'base' in manifest file");
    }
    
    if (m_package.m_id.empty())
    {
        throw KMException("Not found 'package' --> 'id' in manifest file");
    }
    
    if (m_package.m_kind.empty())
    {
        throw KMException("Not found 'package' --> 'kind' in manifest file");
    }
    
    if (m_package.m_version.empty())
    {
        throw KMException("Not found 'package' --> 'version' in manifest file");
    }
}
