#include "KMBuilderContext.h"

#include <gio/gio.h>
#include <glib.h>

#include <cstdlib>
#include <iostream>
#include <memory>

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

KMBuilderContext::KMBuilderContext(token)
{
}

KMBuilderContext::~KMBuilderContext()
{
    if (m_optionsDescription != nullptr)
    {
        delete m_optionsDescription;
    }

    if (m_optionsPositionalDescription != nullptr)
    {
        delete m_optionsPositionalDescription;
    }

    if (m_optionsShowDescription != nullptr)
    {
        delete m_optionsShowDescription;
    }
}

void KMBuilderContext::addBuilderOptions(opt::options_description &builderOptions)
{
    builderOptions.add_options()("verbose,v", opt::bool_switch(&m_isVerbose), "Print debug information during command processing")
                                ("arch", opt::value<std::string>(&m_arch), "Architecture to build for (must be host compatible)")
                                ("ccache", opt::bool_switch(&m_isCcache), "Use ccache")
                                ("build-only", opt::bool_switch(&m_isBuildOnly), "Stop after build, don't run clean, finish, export phases")
                                ("finish-only", opt::bool_switch(&m_isFinishOnly), "Only run clean, finish, export phases")
                                ("export-only", opt::bool_switch(&m_isExportOnly), "Only run export phase")
                                // ("repo", opt::value<std::string>(&m_repo), "Repo to export into, DIR")
                                // ("package", opt::bool_switch(&m_isPackage), "Generate offline package '${appid}_${version}_${arch}.ok' from --repo")
                                // ("package-only", opt::bool_switch(&m_isPackageOnly), "Only run generate offline package '${appid}_${version}_${arch}.ok' from --repo")
                                ("force-clean", opt::bool_switch(&m_isForceClean), "Erase previous contents of DIRECTORY")
                                ("keep-build-dirs", opt::bool_switch(&m_isKeepBuildDirs), "Don't remove build directories after build")
                                ("stop-at", opt::value<std::string>(&m_stopAt), "MODULENAME. Stop building at this module (implies --build-only)")
                                ("jobs", opt::value<int>(&m_jobs), "Number of parallel jobs to build (default=NCPU)");
}

void KMBuilderContext::addOptions()
{
    opt::options_description helpOptions("Help Options");
    helpOptions.add_options()("help,h", opt::bool_switch(&m_isHelp), "Show help options");
    opt::options_description appOptions("Application Options");
    opt::options_description positionalOptions("Positional Options");
    m_optionsPositionalDescription = new opt::positional_options_description();
    m_optionsDescription = new opt::options_description();
    addBuilderOptions(appOptions);

    positionalOptions.add_options()("MANIFEST", opt::value<std::string>(&m_manifest), "The MANIFEST file for building, support .yml/.yaml");

    m_optionsPositionalDescription->add("MANIFEST", 1);

    m_optionsDescription->add(helpOptions).add(appOptions).add(positionalOptions);

    m_optionsShowDescription = new opt::options_description("\nUsage\n \tkaiming-builder build [OPTION…] MANIFEST - Build manifest \n\nOptions");
    m_optionsShowDescription->add(helpOptions).add(appOptions);
}

void KMBuilderContext::checkOptions()
{
    bool isEmptyAppDir = KMBuilderUtils::isEmptyDirectory(getAppDir());

    if (isExportOnly() || isFinishOnly())
    {
        if (isEmptyAppDir)
        {
            throw KMException("App dir '" + getAppDir() + "' is empty or doesn't exist.");
        }
    }
    else
    {
        if (!isEmptyAppDir)
        {
            if (isForceClean())
            {
                KMDebug("Emptying app dir : " + getAppDir());
                KMBuilderUtils::rmRfAll(getAppDir());
                KMBuilderUtils::rmRfAll(getCacheDir());
            }
            else
            {
                throw KMException("App dir '" + getAppDir() + "' is not empty. Please delete the existing contents or use --force-clean.");
            }
        }

        KMBuilderUtils::rmRfAll(getBuildDir());
    }
}

std::string KMBuilderContext::getArch() const
{
    return m_arch.length() > 0 ? m_arch : KMBuilderUtils::getArch();
}

void KMBuilderContext::showCmdline() const
{
    std::cout << "cmdline:";
    for (int i = 0; i < m_argc; ++i)
    {
        std::cout << " ";
        std::cout << m_argv[i];
    }
    std::cout << std::endl;
}

void KMBuilderContext::parseCommandLine(int argc, char **argv)
{
    m_argc = argc;
    m_argv = argv;
    m_programName = argv[0];

    addOptions();

    opt::variables_map vars_map;
    try
    {
        opt::store(opt::command_line_parser(m_argc, m_argv).options(*m_optionsDescription).positional(*m_optionsPositionalDescription).allow_unregistered().run(), vars_map);
    }
    catch (...)
    {
        // showCmdline();
        std::cout << "Wrong options!\n";
        showUsage();
        exit(EXIT_FAILURE);
    }
    // 参数解析完成后，通知variables_map去更新所有的外部变量
    opt::notify(vars_map);

    if (m_isHelp)
    {
        showUsage();
        exit(EXIT_SUCCESS);
    }

    if (m_isVerbose)
        km_logger.setLogLevel(spdlog::level::trace);

    if (m_manifest.empty())
    {
        throw KMException("Must input MANIFEST!");
    }

    // 获取编译 base 路径（配置文件所在文件夹）
    m_baseDir = fs::absolute(m_manifest).parent_path();
    m_appDir = m_baseDir / "build";
    m_configFile = fs::absolute(m_manifest).string();
    m_runDir = fs::absolute(fs::current_path());
    m_stateDir = m_runDir / STATE_DIR;
    m_buildDir = m_stateDir / "build";
    m_cacheDir = m_stateDir / "cache";
    m_ccacheDir = m_stateDir / "ccache";
    m_downloadsDir = m_stateDir / "downloads";
    m_checksumDir = m_stateDir / "checksum";

    checkOptions();
}

/**
 * @brief : 加载配置文件 yaml/yml
 */
void KMBuilderContext::loadConfig()
{
    m_manifestConfig = KMBuilderManifest::loadFromYAML(m_configFile);
}
