/* app/KMApplication.cpp
 *
 * Copyright (C) 2023 OpenKylin, Inc
 */

#include "KMApplication.h"

#include <boost/program_options.hpp>
#include <iomanip>
#include <iostream>
#include <locale>
#include <string>
#include <vector>

#include "config.h"
#include "common/KMLogger.h"
#include "common/KMOptions.h"
#include "common/kmtranslation.h"
#include "KMSubCommand.h"

class KMApplication::Options : public KMOptions
{
public:
    Options() = default;
    virtual ~Options() = default;

    const std::string &logLevel() const { return m_loglevel; }

    const std::string &subCommand() const { return m_subcmd; }

protected:
    void preParseHook() override;
    void postParseHook() override;
    void showUsage() const override;

private:
    void addOptions();
    void checkOptions(const opt::variables_map &varsMap);

private:
    std::string m_programName;
    std::string m_subcmd;

    bool m_help = false;
    bool m_verbose = false;
    std::string m_loglevel;
    std::vector<std::string> m_othersPositionalOptions;
};

void KMApplication::Options::preParseHook()
{
    addOptions();
}

void KMApplication::Options::postParseHook()
{
    if (m_help && m_subcmd.empty())
    {
        showUsage();
        exit(EXIT_SUCCESS);
    }

    if (m_verbose)
    {
        m_loglevel = "debug";
    }
}

void KMApplication::Options::addOptions()
{
    m_optionsDescription = std::make_unique<opt::options_description>(_("\nUsage:\n \tkaiming [OPTION…] [SUBCOMMAND] ... \n\nOptions"));

    opt::options_description helpOptions(_("Help Options"));
    helpOptions.add_options()("help,h", opt::bool_switch(&m_help), _("Show help options"));
    opt::options_description appOptions(_("Application Options"));
    appOptions.add_options()("loglevel", opt::value<std::string>(&m_loglevel)->default_value("info"), _("loglevel: trace, debug, info, warn, error, critical, off"))
                            ("verbose,v", opt::bool_switch(&m_verbose), _("Show debug information"))
                            ("SUBCOMMAND", opt::value<std::string>(&m_subcmd), _("Subcommand of kaiming, such as 'install', usage: kaiming install ..."))
                            ("...", opt::value<std::vector<std::string>>(&m_othersPositionalOptions), _("Other options"));

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

    m_optionsPositionalDescription->add("SUBCOMMAND", 1);
    m_optionsPositionalDescription->add("...", -1);
}

void KMApplication::Options::showUsage() const
{
    if (m_optionsDescription != nullptr)
    {
        std::cout << *m_optionsDescription << std::endl;
    }
    std::cout << _("Builtin Commands:") << std::endl;
    for (auto entry : KMSubCommandFactory::subCommandEntrys())
    {
        if (entry.m_name.empty())
        {
            std::cout << entry.m_description << std::endl;
        }
        else
        {
            std::cout << "  " << std::left << std::setw(24) << entry.m_name << entry.m_description << std::endl;
        }
    }
    std::cout << std::endl;
}

class KMApplication::Private
{
public:
    int m_argc;
    char **m_argv;

    std::unique_ptr<Options> m_kmOptions;
};

KMApplication::KMApplication(token, int argc, char **argv)
    : d(std::make_unique<Private>())
{
    d->m_argc = argc;
    d->m_argv = argv;
    d->m_kmOptions = std::make_unique<Options>();
}

KMApplication::~KMApplication() { }

void KMApplication::init()
{
    /*
        客户端进程和系统助手之间共享的子repo确实需要支持创建其他人可读的文件，因此将umask重写为022。
        理想情况下，这应该在需要时设置，但umask是线程不安全的，所以实际上没有本地方法来解决这个问题。
    */
    umask(022);

    std::locale::global(std::locale(""));
    kmI18nInit(GETTEXT_PACKAGE, LOCALEDIR);

    d->m_kmOptions->parseCommandLine(d->m_argc, d->m_argv);

    KMLogger::instance().setLogLevel(d->m_kmOptions->logLevel());
}

int KMApplication::exec()
{
    int ret = EXIT_FAILURE;

    std::unique_ptr<KMSubCommand> subCommand = KMSubCommandFactory::createKMSubCommand(d->m_kmOptions->subCommand());
    if (subCommand)
    {
        int off = d->m_kmOptions->getPositionalOptionIndex();
        try
        {
            ret = subCommand->despose(d->m_argc - off, d->m_argv + off);
        }
        catch (const std::exception &e)
        {
            KMError(e.what());
        }
        catch (...)
        {
            KMError(_("Unknowned exception"));
        }
    }
    else
    {
        KMError(std::string(_("Unknowned subcommand : ")).append(d->m_kmOptions->subCommand()));
    }

    return ret;
}
