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

#include "KMBuildFlow.h"

#include "KMBuilderBuild.h"
#include "KMBuilderContext.h"
#include "KMBuilderCache.h"
#include "KMBuilderExport.h"
#include "KMBuilderInfo.h"
#include "KMBuilderUtils.h"
#include "common/KMException.h"
#include "common/KMInfoJson.h"

namespace fs = std::filesystem;

REGISTER_SUBCOMMAND_DYNCREATE(build, KMBuildFlow)

KMBuildFlow::KMBuildFlow()
{
    enableVfs();
}

KMBuildFlow::~KMBuildFlow()
{}

int KMBuildFlow::dispose(int argc, char **argv)
{
    init(argc, argv);

    return run();
}

void KMBuildFlow::init(int argc, char **argv)
{
    // 参数解析
    builderContext.parseCommandLine(argc, argv);

    // 加载清单配置
    builderContext.loadConfig();
}

int KMBuildFlow::run()
{
    setCcache(builderContext.isCcache(), builderContext.getCcacheDir());

    if (!builderContext.isFinishOnly() && !builderContext.isExportOnly() && !builderContext.isPackageOnly())
    {
        std::map<std::shared_ptr<KMBuilderModule>, std::string> moudle_str_map;
        downloadFile(moudle_str_map);

        buildInit();

        build(moudle_str_map);

        if (builderContext.isBuildOnly())
        {
            return EXIT_SUCCESS;
        }
    }

    if (!builderContext.isExportOnly())
    {
        cleanup();

        buildFinish();

        if (builderContext.isFinishOnly())
        {
            return EXIT_SUCCESS;
        }
    }

    // if (!builderContext.isExportOnly() && !builderContext.isPackageOnly())
    // {
    //     std::shared_ptr<KMBuilderCleanup> pKMBuilderCleanup = std::make_shared<KMBuilderCleanup>(pKMBuilderCache);
    //     if (!pKMBuilderCleanup->configCleanup())
    //     {
    //         return EXIT_FAILURE;
    //     }

    //     std::shared_ptr<KMBuilderFinish> pKMBuilderFinish = std::make_shared<KMBuilderFinish>(pKMBuilderCache);
    //     if (!pKMBuilderFinish->builderContextFinish())
    //     {
    //         return EXIT_FAILURE;
    //     }
    // }

    // if (!builderContext.isPackageOnly())
    // {
    //     std::shared_ptr<KMBuilderExport> pKMBuilderExport = std::make_shared<KMBuilderExport>(pKMBuilderCache);
    //     if (!pKMBuilderExport->exportStage())
    //     {
    //         return EXIT_FAILURE;
    //     }
    // }

    // KMBuilderInfo info;
    // if (!info.writeInfo())
    // {
    //     return EXIT_FAILURE;
    // }

    return EXIT_SUCCESS;
}

/**
 * @brief: 设置ccache目录
 * @param: 是否启用
 * @param: ccache缓存目录
 */
bool KMBuildFlow::setCcache(bool enable, const std::string &ccacheDir)
{
    if (enable)
    {
        std::error_code ec;
        std::string binDir = KMBuilderUtils::buildFilename(ccacheDir, "bin");
        if (!KMBuilderUtils::mkpath(binDir, ec))
        {
            throw KMException(ec.message());
        }

        std::vector<string> compilers = { "cc", "c++", "gcc", "g++" };
        for (auto &compiler : compilers)
        {
            std::string symlinkPath = KMBuilderUtils::buildFilename(binDir, compiler);
            if (fs::exists(symlinkPath, ec) && !fs::remove(symlinkPath, ec))
            {
                throw KMException(ec.message());
            }
            fs::create_symlink("/usr/bin/ccache", symlinkPath, ec);
        }
    }
    else
    {
        std::error_code ec;
        std::string disabledDir = ccacheDir + "/disabled";
        if (!KMBuilderUtils::mkpath(disabledDir, ec))
        {
            throw KMException(ec.message());
        }

        std::string ccacheConfiPath = disabledDir + "/ccache.conf";
        std::ofstream file(ccacheConfiPath, ios::out);
        file << "disable = true\n";
        file.close();
    }

    return true;
}

/**
 * @brief : 编译初始化
 * @return : true-成功; false-失败
 */
bool KMBuildFlow::buildInit()
{
    KMTrace("KMBuildFlow::buildInit invoke begin");

    // 清理appDir，重新初始化
    KMBuilderUtils::rmRfAll(builderContext.getAppDir());

    std::vector<std::string> args;
    initArgs(args);

    std::string result;
    if (!KMBuilderUtils::spawn("kaiming", args, result))
    {
        throw KMException(result);
    }
    else
    {
        KMDebug(result);
    }

    KMTrace("KMBuildFlow::buildInit invoke end");
    return true;
}

/**
 * @brief : 分离locale目录
 * @param : pBuilderCache - 编译缓存
 * @return : true-成功; false-失败
 */
bool KMBuildFlow::separateLocales(std::shared_ptr<KMBuilderCache> &pBuilderCache)
{
    do
    {
        std::shared_ptr<KMBuilderConfig> pBuildconfig = builderContext.getBuilderConfig();
        if (pBuildconfig == nullptr)
            break;
        if (!pBuildconfig->getAttribute("separate-locales").has_value() || !pBuildconfig->getAttribute("build-runtime").has_value())
            break;
        if (!std::any_cast<bool>(pBuildconfig->getAttribute("separate-locales")) || !std::any_cast<bool>(pBuildconfig->getAttribute("build-runtime")))
            break;

        std::string rootdir = pBuilderCache->getBuilderCache().appDir + "/usr";
        if (!KMBuilderUtils::migrateLocaleDirs(rootdir))
            return false;

    } while (0);

    return true;
}

/**
 * @brief : 下载
 * @return : true-成功; false-失败
 */
bool KMBuildFlow::downloadFile(std::map<std::shared_ptr<KMBuilderModule>, std::string> &moudle_str_map)
{
    KMTrace("KMBuildFlow::downloadFile invoke begin");

    std::shared_ptr<KMBuilderManifest> manifest = builderContext.manifestConfig();
    expandModules(manifest->m_modules);
    KMInfo("Modules : ");
    for (auto module : m_expandModules)
    {
        std::cout << std::any_cast<std::string>(module->getProperty(KMBuilderModule::Module_Name)) << std::endl;
    }
    
    KMInfo("Starting download");
    std::string stopAt = builderContext.getStopat();
    for (auto module : m_expandModules)
    {
        std::string name = std::any_cast<std::string>(module->getProperty(KMBuilderModule::Module_Name));
        if (stopAt == name)
        {
            return true;
        }

        if (!createSubdir(builderContext.getBuildDir(), name, moudle_str_map[module]))
        {
            throw KMException("Failed to create build dir for module : " + name);
        }

        if (!download(module, moudle_str_map[module]))
        {
            throw KMException("Failed to download sources for module : " + name);
        }
    }

    KMTrace("KMBuildFlow::downloadFile invoke end");
    return true;
}

/**
 * @brief : 编译
 * @return : true-成功; false-失败
 */
bool KMBuildFlow::build(std::map<std::shared_ptr<KMBuilderModule>, std::string> &moudle_str_map)
{
    KMTrace("KMBuildFlow::build invoke begin");
    
    KMInfo("Starting build");
    std::string stopAt = builderContext.getStopat();
    for (auto module : m_expandModules)
    {
        std::string name = std::any_cast<std::string>(module->getProperty(KMBuilderModule::Module_Name));
        if (stopAt == name)
        {
            return true;
        }

        if (!isShouldBuild(module))
        {
            continue;
        }

        KMBuilderBuild build;
        build.m_subdir = moudle_str_map[module];
        if (!build.doBuild(module))
        {
            throw KMException("Failed to build module : " + name);
        }
    }

    KMTrace("KMBuildFlow::build invoke end");
    return true;
}

/**
 * @brief : 初始化参数
 * @return : true-成功; false-失败
 */
bool KMBuildFlow::initArgs(std::vector<std::string> &args)
{
    /* 
       用法：kaiming build-init [OPTIONS] DIRECTORY APPID
       样例：kaiming build-init --base=dev.openkylin.Base/channel/23.0.0 --runtime=dev.openkylin.Runtime/channel/23.0.0 build top.openkylin.demo
     */
    // args.emplace_back("kaiming");
    args.emplace_back("build-init");

    std::shared_ptr<KMBuilderManifest> manifest = builderContext.manifestConfig();
    args.emplace_back("--arch=" + builderContext.getArch());
    args.emplace_back("--kind=" + manifest->m_package.m_kind);
    args.emplace_back("--channel=" + manifest->m_channel);
    args.emplace_back("--base=" + manifest->m_base);

    if (!manifest->m_runtime.empty())
    {
        args.emplace_back("--runtime=" + manifest->m_runtime);
    }

    args.emplace_back(builderContext.getAppDir());
    args.emplace_back(manifest->m_package.m_id);

    return true;

    ////////////////////////////////////////////////////////////

    // std::shared_ptr<KMBuilderConfig> pBuildconfig = builderContext.getBuilderConfig();
    // std::string id, runtime, sdk;
    // if (pBuildconfig->getAttribute("id").has_value())
    //     id = std::any_cast<std::string>(pBuildconfig->getAttribute("id"));
    // if (pBuildconfig->getAttribute("runtime").has_value())
    //     runtime = std::any_cast<std::string>(pBuildconfig->getAttribute("runtime"));
    // if (pBuildconfig->getAttribute("sdk").has_value())
    //     sdk = std::any_cast<std::string>(pBuildconfig->getAttribute("sdk"));
    // if (id.empty() && pBuildconfig->getAttribute("app-id").has_value())
    //     id = std::any_cast<std::string>(pBuildconfig->getAttribute("app-id"));

    // if (id.empty() || runtime.empty() || sdk.empty())
    //     return false;

    // args.emplace_back("kaiming");
    // args.emplace_back("build-init");

    // bool isWritableSdk, isBuildRuntime;
    // if (pBuildconfig->getAttribute("writable-sdk").has_value())
    //     isWritableSdk = std::any_cast<bool>(pBuildconfig->getAttribute("writable-sdk"));
    // if (pBuildconfig->getAttribute("build-runtime").has_value())
    //     isBuildRuntime = std::any_cast<bool>(pBuildconfig->getAttribute("build-runtime"));

    // if (isBuildRuntime)
    //     args.emplace_back("--type=runtime");
    // else if (isWritableSdk)
    //     args.emplace_back("--writable-sdk");

    // for (auto [k, v] : pBuildconfig->getConfigData()->addBuildExtensions)
    // {
    //     args.emplace_back("--extension=" + k + "=directory" + "=" + v->directory);
    //     args.emplace_back("--extension=" + k + "=autodelete" + "=true");
    //     args.emplace_back("--extension=" + k + "=no-autodownload" + "=true");
    //     args.emplace_back("--extension=" + k + "=locale-subset" + "=true");
    //     args.emplace_back("--extension=" + k + "=subdirectories" + "=true");
    //     args.emplace_back("--extension=" + k + "=add-ld-path" + "=" + v->addLdPath);
    //     args.emplace_back("--extension=" + k + "=download-if" + "=" + v->downloadIf);
    //     args.emplace_back("--extension=" + k + "=enable-if" + "=" + v->enableIf);
    //     args.emplace_back("--extension=" + k + "=subdirectory-suffix" + "=" + v->subdirectorySuffix);
    //     args.emplace_back("--extension=" + k + "=version" + "=" + v->version);
    //     args.emplace_back("--extension=" + k + "=merge-dirs" + "=" + v->mergeDirs.front());
    //     args.emplace_back("--extension=" + k + "=versions" + "=" + v->versions.front());
    // }

    // for (auto sdkext : pBuildconfig->getConfigData()->sdkExtensions)
    //     args.emplace_back("--sdk-extension=" + sdkext);

    // if (pBuildconfig->getAttribute("build-extension").has_value() && std::any_cast<bool>(pBuildconfig->getAttribute("build-extension")))
    //     args.emplace_back("--type=extension");

    // std::vector<std::string> tags;
    // if (pBuildconfig->getAttribute("tags").has_value())
    //     tags = std::any_cast<std::vector<std::string>>(pBuildconfig->getAttribute("tags"));

    // for (auto tag : tags)
    //     args.emplace_back("--tag=" + tag);

    // if (pBuildconfig->getAttribute("var").has_value())
    // {
    //     std::string var = std::any_cast<std::string>(pBuildconfig->getAttribute("var"));
    //     if (!var.empty())
    //         args.emplace_back("--var=" + var);
    // }

    // std::string base, baseVersion;
    // if (pBuildconfig->getAttribute("base").has_value())
    //     base = std::any_cast<std::string>(pBuildconfig->getAttribute("base"));
    // if (pBuildconfig->getAttribute("base-version").has_value())
    //     baseVersion = std::any_cast<std::string>(pBuildconfig->getAttribute("base-version"));

    // if (!base.empty())
    // {
    //     args.emplace_back("--base=" + base);
    //     if (!baseVersion.empty())
    //         args.emplace_back("--base-version=" + baseVersion);
    //     else
    //     {
    //         if (pBuildconfig->getAttribute("branch").has_value())
    //             args.emplace_back("--base-version=" + std::any_cast<std::string>(pBuildconfig->getAttribute("branch")));
    //         else if (pBuildconfig->getAttribute("default-branch").has_value())
    //             args.emplace_back("--base-version=" + std::any_cast<std::string>(pBuildconfig->getAttribute("default-branch")));
    //         else if (!builderContext.getDefaultBranch().empty())
    //             args.emplace_back("--base-version=" + builderContext.getDefaultBranch());
    //         else
    //             args.emplace_back("--base-version=" DEFAULT_BRANCH);
    //     }
    //     if (pBuildconfig->getAttribute("base-extensions").has_value())
    //     {
    //         for (auto baseExtension : std::any_cast<std::vector<std::string>>(pBuildconfig->getAttribute("base-extensions")))
    //             args.emplace_back("--base-extension=" + baseExtension);
    //     }
    // }

    // if (pBuildconfig->getAttribute("extension-tag").has_value())
    // {
    //     std::string extensionTag = std::any_cast<std::string>(pBuildconfig->getAttribute("extension-tag"));
    //     if (!extensionTag.empty())
    //         args.emplace_back("--extension-tag=" + extensionTag);
    // }

    // args.emplace_back("--arch=" + builderContext.getArch());
    // args.emplace_back(builderContext.getAppDir());
    // args.emplace_back(id);
    // args.emplace_back(sdk);
    // args.emplace_back(runtime);

    // if (pBuildconfig->getAttribute("runtime-version").has_value())
    // {
    //     std::string runtimeVersion = std::any_cast<std::string>(pBuildconfig->getAttribute("runtime-version"));
    //     args.emplace_back(runtimeVersion.empty() ? DEFAULT_BRANCH : runtimeVersion);
    // }

    // return true;
}

/**
 * @brief : 展开所有的builderModules
 * @param modules : 所有的builderModules
 * @param expandModules : 展开后的builderModules
 * @return : true-成功; false-失败
 */
void KMBuildFlow::expandModules(const std::vector<std::shared_ptr<KMBuilderModule>> &modules)
{
    for (auto module : modules)
    {
        std::vector<std::shared_ptr<KMBuilderModule>> inmodules = module->builderModules();
        if (!inmodules.empty())
        {
            expandModules(inmodules);
        }

        m_expandModules.emplace_back(module);
    }
}

/**
 * @brief : 是否需要构建
 * @param module : builderModule模块
 * @return : true-需要; false-不需要
 */
bool KMBuildFlow::isShouldBuild(const std::shared_ptr<KMBuilderModule> &module)
{
    if (module->buildSources().empty())
        return false;

    if (module->buildSystem() == KMBuilderModule::BuildSystem::BS_Unknow)
        return false;
    
    if (module->getData()->m_disabled)
        return false;

    return true;
}

/**
 * @brief : 构建流程中的cleanup环节
 */ 
void KMBuildFlow::cleanup()
{
    KMTrace("KMBuildFlow::cleanup invoke begin");

    std::string appFiles = KMBuilderUtils::buildFilename(builderContext.getAppDir(), "files");
    for (auto& module : m_expandModules)
    {
        if (module->cleanup().empty())
        {
            continue ;
        }

        std::vector<std::string> toRemoves;
        KMBuildFlow::collectCleanupForPath(appFiles, appFiles, module->cleanup(), toRemoves);

        for (auto& remove : toRemoves)
        {
            KMBuilderUtils::rmRfAll(remove);
        }
    }

    // 删除构建结果中的因挂载而生成到构建结果中的文件，需要在删除空目录之前
    std::vector<std::string> defaultCleanups = {
        "/etc/passwd",
        "/etc/group",
        "/etc/pkcs11/pkcs11.conf",
        "/etc/shadow",
        "/etc/gshadow",
        "/etc/sudoers",
        "/usr/bin/sudo",
        "/etc/machine-id",
        "/etc/localtime",
        "/usr/share/zoneinfo",
        "/etc/resolv.conf",
        "/etc/host.conf",
        "/etc/hosts",
        "/etc/gai.conf",
    };

    for (auto& cleanup : defaultCleanups)
    {
        std::string remove = KMBuilderUtils::buildFilename(appFiles, cleanup);
        KMBuilderUtils::rmRfAll(remove);
    }

    // 删除构建结果中的空文件夹，这些空文件夹应该都是overlay叠加挂载时创建upperdir时创建的（目的是使得目录可写）
    std::error_code ec;
    if (!KMBuilderUtils::removeEmptyDirectory(appFiles, ec))
    {
        throw KMException(ec.message());
    }

    // 删除build/var目录
    std::string appVar = KMBuilderUtils::buildFilename(builderContext.getAppDir(), "var");
    KMBuilderUtils::rmRfAll(appVar);

    KMTrace("KMBuildFlow::cleanup invoke end");
}

/**
 * @brief : 收集指定路径下匹配规则的文件/目录列表
 * @param : [in] path, 指定路径
 * @param : [in] prefix, 前缀，匹配比较时，如果match是相对路径，需要去掉文件/目录的前缀后比较
 * @param : [in] matchs, 匹配规则
 * @param : [out] toRemoves，待删除的路径
 */ 
void KMBuildFlow::collectCleanupForPath(const std::string& path, const std::string& prefix, const std::vector<std::string> matchs, std::vector<std::string> toRemoves)
{
    fs::path basePath(path);
    if (!fs::exists(path) || matchs.empty())
    {
        return ;
    }

    for (auto const &dirEntry : fs::directory_iterator{ basePath })
    {
        fs::path p = dirEntry.path();
        std::string filename = p.filename();
        if (filename == "." || filename == "..")
        {
            continue ;
        }

        std::string subPath = p.string();
        std::string unprefixedPath = subPath.substr(prefix.length());

        bool isMatch = false;
        for (auto& match : matchs)
        {
            const char *rest =  nullptr;
            if (match.at(0) == '/')
            {
                rest = KMBuilderUtils::pathMatchPrefix(match.c_str(), unprefixedPath.c_str());
            }
            else
            {
                rest = KMBuilderUtils::pathMatchPrefix(match.c_str(), filename.c_str());
            }

            if (rest != nullptr)
            {
                isMatch = true;
                break ;
            }
        }

        if (isMatch)
        {
            toRemoves.push_back(subPath);
            continue ;
        }

        if (fs::is_directory(p))
        {
            KMBuildFlow::collectCleanupForPath(subPath, prefix, matchs, toRemoves);
        }
    }
}

/**
 * @brief : 填充info.json的部分信息
 */
void KMBuildFlow::fillInfoJson()
{
    // build-init时已经生成了部分info.json信息，此处填充其它信息
    KMInfoJson metadata;
    metadata.loadFile(KMBuilderUtils::buildFilename(builderContext.getAppDir(), "info.json"));

    std::shared_ptr<KMBuilderManifest> manifest = builderContext.manifestConfig();
    metadata.commands = manifest->m_commands;
    metadata.module = manifest->m_module;
    metadata.name = manifest->m_package.m_name;
    metadata.names = manifest->m_package.m_names;
    metadata.description = manifest->m_package.m_description;
    metadata.descriptions = manifest->m_package.m_descriptions;
    metadata.version = manifest->m_package.m_version;
    metadata.permissions = manifest->m_permissions;
    metadata.envs = manifest->m_envs;

    if (metadata.permissions.empty())
    {
        metadata.permissions.push_back("all");
    }

    metadata.saveFile(KMBuilderUtils::buildFilename(builderContext.getAppDir(), "info.json"));
}

/**
 * @brief : 构建流程中的finish环节
 */
void KMBuildFlow::buildFinish()
{
    KMTrace("KMBuildFlow::buildFinish invoke begin");

    std::shared_ptr<KMBuilderManifest> manifest = builderContext.manifestConfig();
    KMInfo("Finishing " + manifest->m_package.m_id);

    fillInfoJson();

    std::vector<std::string> args;
    args.push_back("build-finish");
    args.push_back(builderContext.getAppDir());

    std::string result;
    if (!KMBuilderUtils::spawn("kaiming", args, result))
    {
        throw KMException(result);
    }
    else
    {
        KMDebug(result);
    }

    KMTrace("KMBuildFlow::buildFinish invoke end");
}

