/* 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 "common/KMLogger.h"
#include "common/KMOptions.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);
    }
    else if (getPositionalOptionIndex() != 1 && !m_subcmd.empty())
    {
        showUsage();
        exit(EXIT_SUCCESS);
    }

    if (m_verbose)
    {
        m_loglevel = "trace";
    }
    else
    {
        m_loglevel = "info";
    }
}

void KMApplication::Options::addOptions()
{
    m_optionsDescription = std::make_unique<opt::options_description>("\nUsage:\n \tkaiming-builder [SUBCOMMAND] [OPTION…]  ... \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()("SUBCOMMAND", opt::value<std::string>(&m_subcmd), "Subcommand of kaiming-builder, usage: kaiming-builder build ...")
                            ("verbose,v", opt::bool_switch(&m_verbose), "Show debug information")
                            ("...", 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);

    m_optionsShowDescription = std::make_unique<opt::options_description>("\nUsage:\n \tkaiming-builder [SUBCOMMAND] [OPTION…]  ... \n\nOptions");
    m_optionsShowDescription->add(helpOptions);
}

void KMApplication::Options::showUsage() const
{
    if (m_optionsShowDescription)
    {
        std::cout << *m_optionsShowDescription << std::endl;
    }
    std::cout << "Subcommands" << 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()
{
    d->m_kmOptions->parseCommandLine(d->m_argc, d->m_argv);

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

int KMApplication::exec()
{
    init();

    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->dispose(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;
}
