#include "KMInstall.h"

#include <sys/personality.h>
#include <fcntl.h>
#include <grp.h>
#include <pwd.h>
// #include "common/json.hpp"
#include <nlohmann/json.hpp>

#ifdef USE_SECCOMP
#  include <seccomp.h>
#endif

#include "common/KMError.h"
#include "common/KMLogger.h"
#include "common/KMUtil.h"
#include "syslog.h"

using namespace nlohmann;

/**
 * @brief 安装子命令的参数解析
 */
class KMInstall::Options : public KMOptions
{
public:
    Options() = default;
    virtual ~Options() = default;

protected:
    /**
     * @brief 命令行参数解析后操作
     */
    void preParseHook() override;

    /**
     * @brief 命令行参数解析后操作
     */
    void postParseHook() override;

private:
    /**
     * @brief 命令行参数添加描述选项
     */
    void addOptions();

public:
    bool m_help;
    bool m_assumeYes;
    string m_arch;
    vector<string> m_vecRefs;
};

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

/**
 * @brief 命令行参数解析后操作
 */
void KMInstall::Options::postParseHook()
{
    if (m_help)
    {
        showUsage();
        exit(EXIT_SUCCESS);
    }
}

/**
 * @brief 命令行参数添加描述选项
 */
void KMInstall::Options::addOptions()
{
    m_optionsDescription = std::make_unique<opt::options_description>(_("\nUsage:\n \tkaiming install [OPTION…] [REF…] - Install applications, runtimes or bases"));

    opt::options_description helpOptions(_("Help Options"));
    helpOptions.add_options()("help,h", opt::bool_switch(&m_help)->default_value(false), _("Show help options"));

    opt::options_description appOptions(_("Application Options"));
    appOptions.add_options()("assumeyes,y", opt::bool_switch(&m_assumeYes)->default_value(false), _("Automatically answer all questions yes"))
                            ("arch=ARCH", opt::value<string>(&m_arch), _("Architecture"));

    opt::options_description positionalOptions(_("Positional Options"));
    positionalOptions.add_options()("REF...", opt::value<vector<string>>(&m_vecRefs), _("REF..."));
    m_optionsDescription->add(helpOptions).add(appOptions).add(positionalOptions);

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


class KMInstall::Private
{
public:
    std::shared_ptr<Options> m_options;
    std::shared_ptr<KMFolder> m_folder;
    std::shared_ptr<KMPackageManager> m_packageManager;

    bool m_isYes = false;
    bool m_isOfflinePackage = false; // 区分离线包和在线包。 true: 离线包 false: 在线包
};

REGISTER_SUBCOMMAND_DYNCREATE(install, KMInstall)


KMInstall::KMInstall()
    : d(std::make_unique<Private>())
{
    init();
}

KMInstall::~KMInstall() { }

/**
 * @brief 命令行格式支持全部在线安装和全部离线安装两种格式，不支持混合在线和离线安装
 * 
 * 全部在线安装：id，或者全格式: kaiming-repo:stable/${id}/${version}/${arch}/${module} 
 * 全部离线安装：玲珑离线包(.uab, .layer后缀), 麒麟离线包(.ok后缀)
 */
int KMInstall::despose(int argc, char **argv)
{
    vector<string> vecInstall;

    // setenv("G_MESSAGES_DEBUG", "all", 1);

    init();

    d->m_options->parseCommandLine(argc, argv);

    if (d->m_options->m_vecRefs.size() < 1)
    {
        kmlogger.error("Args error");
        return KM_ERROR_INVALID_ARGS;
    }

    for (auto &ref : d->m_options->m_vecRefs)
    {
        vecInstall.push_back(ref);
    }

    int ret = start(vecInstall);

    return EXIT_SUCCESS;
}

/**
 * @brief 开始对已获取的用户软件包列表执行安装
 * 
 * 此处分为离线包和在线包下载。
 * 
 * 离线包格式： 
 *      玲珑方面(.uab, .layer 后缀格式)
 *      麒麟方面(.ok 后缀格式)
 *
 * @param vectorAppIds 待安装的用户软件包列表
 * @return int
 */
int KMInstall::start(const vector<string> &vectorAppIds)
{
    PackageManagerOptions pmOptions;
    pmOptions.assumeYes = d->m_options->m_assumeYes;
    pmOptions.arch = d->m_options->m_arch;

    int ret = d->m_packageManager->processTask(TASK_TYPE_INSTALL, vectorAppIds, pmOptions);

    return ret;
}

/**
 * @brief 处理离线安装的包
 * 
 * 离线包格式为： .uab, .layer, .ok 为后缀，因此不单独加区分
 * 
 * @param vectorAppIds
 * @return int
 */
int KMInstall::offlineInstall(const vector<string> &vectorAppIds)
{
    bool ret = false;

    // TODO： 离线包解压

    // TODO： 离线包部署

    return ret;
}

const char **KMInstall::getGLDrivers()
{
    static gsize drivers = 0;

    if (g_once_init_enter(&drivers))
    {
        gsize newDrivers;
        char **newDriversChar = 0;
        const char *env = g_getenv("KAIMING_GL_DRIVERS");
        if (env != NULL && *env != 0)
            newDriversChar = g_strsplit(env, ":", -1);
        else
        {
            g_autofree char *nvidiaVersion = NULL;
            char *dot;
            GPtrArray *array = g_ptr_array_new();

            if (g_file_get_contents("/sys/module/nvidia/version", &nvidiaVersion, NULL, NULL))
            {
                g_strstrip(nvidiaVersion);
                /* Convert dots to dashes */
                while ((dot = strchr(nvidiaVersion, '.')) != NULL)
                    *dot = '-';
                g_ptr_array_add(array, g_strconcat("nvidia-", nvidiaVersion, NULL));
            }

            g_ptr_array_add(array, (char *)"default");
            g_ptr_array_add(array, (char *)"host");

            g_ptr_array_add(array, NULL);
            newDriversChar = (char **)g_ptr_array_free(array, false);
        }

        newDrivers = (gsize)newDriversChar;
        g_once_init_leave(&drivers, newDrivers);
    }

    return (const char **)drivers;
}

bool KMInstall::getHaveIntelGPU()
{
    static int haveIntelModule = -1;

    if (haveIntelModule == -1)
    {
        haveIntelModule = g_file_test("/sys/module/i915", G_FILE_TEST_EXISTS);
    }

    return haveIntelModule;
}

GHashTable *KMInstall::loadKernelModuleList()
{
    GHashTable *modules = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
    g_autofree char *modulesData = NULL;
    g_autoptr(GError) error = NULL;
    char *start, *end;

    if (!g_file_get_contents("/proc/modules", &modulesData, NULL, &error))
    {
        g_debug("Failed to read /proc/modules: %s", error->message);
        return modules;
    }

    /* /proc/modules is a table of modules.
     * Columns are split by spaces and rows by newlines.
     * The first column is the name. */
    start = modulesData;
    while (true)
    {
        end = strchr(start, ' ');
        if (end == NULL)
        {
            break;
        }

        g_hash_table_add(modules, g_strndup(start, (end - start)));

        start = strchr(end, '\n');
        if (start == NULL)
        {
            break;
        }

        start++;
    }

    return modules;
}

bool KMInstall::getHaveKernelModule(const char *moduleName)
{
    static GHashTable *kernelModules = NULL;

    if (g_once_init_enter(&kernelModules))
    {
        g_once_init_leave(&kernelModules, loadKernelModuleList());
    }

    return g_hash_table_contains(kernelModules, moduleName);
}

const char *KMInstall::getGtkTheme()
{
    static char *gtkTheme;

    if (g_once_init_enter(&gtkTheme))
    {
        /* The schema may not be installed so check first */
        GSettingsSchemaSource *source = g_settings_schema_source_get_default();
        g_autoptr(GSettingsSchema) schema = NULL;

        if (source == NULL)
        {
            g_once_init_leave(&gtkTheme, g_strdup(""));
        }
        else
        {
            schema = g_settings_schema_source_lookup(source, "org.gnome.desktop.interface", true);

            if (schema == NULL)
            {
                g_once_init_leave(&gtkTheme, g_strdup(""));
            }
            else
            {
                /* GSettings is used to store the theme if you use Wayland or GNOME.
                 * TODO: Check XSettings Net/ThemeName for other desktops.
                 * We don't care about any other method (like settings.ini) because they
                 *   aren't passed through the sandbox anyway. */
                g_autoptr(GSettings) settings = g_settings_new("org.gnome.desktop.interface");
                g_once_init_leave(&gtkTheme, g_settings_get_string(settings, "gtk-theme"));
            }
        }
    }

    return (const char *)gtkTheme;
}



void KMInstall::init()
{
    d->m_options = make_shared<Options>();
    d->m_folder = shared_ptr<KMFolder>(new KMFolder("/opt/kaiming", false));
    d->m_packageManager = shared_ptr<KMPackageManager>(new KMPackageManager());
}


