
/* src/common/KMStorageDir.cpp
 *
 * Copyright (C) 2023 OpenKylin, Inc
 */

#include "KMStorageDir.h"

#include <ostree.h>

#include <cassert>
#include <cstdlib>

#include "config.h"
#include "KMUtil.h"

class KMStorageDir::Private
{
public:
    bool m_user;
    std::string m_baseDir;
};

KMStorageDir::KMStorageDir() { }

KMStorageDir::KMStorageDir(const KMStorageDir &other)
{
    d = other.d;
}

KMStorageDir::KMStorageDir(const std::string &path, bool user)
    : d(std::make_shared<KMStorageDir::Private>())
{
    d->m_baseDir = path;
    d->m_user = user;

    /* Work around possible deadlock due to: https://bugzilla.gnome.org/show_bug.cgi?id=674885 */
    // g_type_ensure (G_TYPE_UNIX_SOCKET_ADDRESS);
}

KMStorageDir::~KMStorageDir() { }

/**
 * @brief : 获取KMDir对象路径
 */
std::string KMStorageDir::getBasePath()
{
    return d->m_baseDir;
}

/**
 * @brief : 获取开明包系统级安装的base目录
 * @return: 开明包系统级安装的base目录
 */
std::string KMStorageDir::getSystemDefaultBaseDirLocation()
{
    const char *systemDir = std::getenv("KAIMING_SYSTEM_DIR");
    if (nullptr == systemDir || *systemDir == '0')
    {
        return std::string(KAIMING_SYSTEM_PATH) + "/layers";
    }

    return std::string(systemDir) + "/layers";
}

/**
 * @brief : 获取用户数据目录
 * @return: 用户数据目录
 */
std::string KMStorageDir::getUserDataDirLocation()
{
    const char *home = std::getenv("HOME");
    if (nullptr == home || *home == '0')
    {
        return std::string("~/.local/share");
    }

    return std::string(home) + "/.local/share";
}

/**
 * @brief : 获取开明包用户数据base目录
 * @return: 开明包用户数据base目录
 */
std::string KMStorageDir::getUserBaseDirLocation()
{
    const char *userDir = std::getenv("KAIMING_USER_DIR");
    if (nullptr == userDir || *userDir == '0')
    {
        return KMStorageDir::getUserDataDirLocation() + "/kaiming";
    }

    return std::string(userDir);
}

/**
 * @brief : 获取开明包用户安装目录KMStorageDir对象
 * @return: 开明包用户用户安装目录KMStorageDir对象
 */
KMStorageDir KMStorageDir::getUserDir()
{
    return KMStorageDir(KMStorageDir::getUserBaseDirLocation() + "/layers", true);
}

/**
 * @brief : 获取应用数据目录
 * @param : [in] id，应用id
 * @return: 数据目录
 */
std::string KMStorageDir::getDataDir(const std::string &id)
{
    const char *home = std::getenv("HOME");
    if (nullptr == home || *home == '0')
    {
        return std::string("~/.var/app/") + id;
    }

    return std::string(home) + "/.var/app/" + id;
}

/**
 * @brief : 获取开明包系统级安装的KMStorageDir对象
 * @return: 开明包系统级安装的KMStorageDir对象
 */
KMStorageDir KMStorageDir::getSystemDefaultDir()
{
    return KMStorageDir(KMStorageDir::getSystemDefaultBaseDirLocation(), false);
}

/**
 * listRefs:
 * @brief : 根据目录名（系统安装目录下的app或runtime下的子目录名）对应的目录树信息判断已经安装的开明包信息列表
 * @param : [in] baseDir，系统安装目录下的app或runtime目录
 * @param : [in] kinds，查找开明包安装目录的类型，app还是runtime等
 * @param : [out] refs，已经安装的开明包信息列表
 * @return: 是否存在
 */
bool KMStorageDir::listRefs(const fs::path &baseDir, KaimingKinds kind, std::vector<KMRef> &refs)
{
    assert(kind == KaimingKinds::KAIMING_KINDS_APP || kind == KaimingKinds::KAIMING_KINDS_RUNTIME);

    if (!fs::exists(baseDir) || !fs::is_directory(baseDir))
    {
        return false;
    }

    std::string id;
    for (auto &entryId : fs::directory_iterator(baseDir))
    {
        if (!entryId.is_directory())
        {
            return false;
        }

        id = entryId.path().filename();
        fs::path appDir = baseDir / id;
        if (!fs::exists(appDir))
        {
            return false;
        }

        for (auto &entry : fs::directory_iterator(appDir))
        {
            if (!entry.is_directory())
            {
                continue;
            }

            std::string arch = entry.path().filename();
            if (arch == "data")
            {
                continue;
            }

            fs::path archDir = appDir / arch;
            if (!fs::exists(archDir))
            {
                continue;
            }
            for (auto &entryArch : fs::directory_iterator(archDir))
            {
                if (!entryArch.is_directory())
                {
                    continue;
                }

                std::string branch = entryArch.path().filename();
                fs::path deploy = entryArch.path() / "active/deploy";
                if (fs::exists(deploy))
                {
                    refs.push_back(KMUtil::constructRef(kind, id, arch, branch));
                }
            }
        }
    }

    return refs.size() > 0;
}

/**
 * listRefs:
 * @brief : 根据kinds查找已经安装的app或sdk/runtime的开明包信息列表
 * @param : [in] kinds，查找开明包安装目录的类型，app还是runtime等
 * @return: 返回已安装的app或sdk/runtime等的开明包信息列表
 */
std::vector<KMRef> KMStorageDir::listRefs(KaimingKinds kinds)
{
    std::vector<KMRef> refs;

    if (!fs::exists(d->m_baseDir))
    {
        return refs;
    }

    if (kinds & KaimingKinds::KAIMING_KINDS_APP)
    {
        std::string appDir = d->m_baseDir + "/app";
        KMStorageDir::listRefs(appDir, KaimingKinds::KAIMING_KINDS_APP, refs);
    }

    if (kinds & KaimingKinds::KAIMING_KINDS_RUNTIME)
    {
        std::string runtimeDir = d->m_baseDir + "/runtime";
        KMStorageDir::listRefs(runtimeDir, KaimingKinds::KAIMING_KINDS_RUNTIME, refs);
    }

    return refs;
}

/**
 * getAllInstalledRefs:
 * @brief : 根据kinds查找已经安装的app或sdk/runtime的开明包信息
 * @param : [in] kinds，查找开明包安装目录的类型，app还是runtime等
 * @return: 返回已安装的app或sdk/runtime等的开明包信息列表
 */
std::vector<KMRef> KMStorageDir::getAllInstalledRefs(KaimingKinds kinds)
{
    std::vector<KMRef> refs;

    KMFolder folder(d->m_baseDir, d->m_user);
    if (!folder.ensureRepo(true))
    {
        return refs;
    }

    refs = listRefs(kinds);

    return refs;
}

/**
 * findInstalledRef:
 * @brief : 根据入参查找匹配的已经安装的app或sdk、runtime信息
 * @param : [in] appName，app或sdk等的id，形如"org.videolan.VLC"或"org.freedesktop.Sdk"
 * @param : [in] branch，版本
 * @param : [in] arch，架构
 * @param : [in] kinds，查找开明包安装目录的类型，app还是runtime等
 * @return: 返回匹配的已安装的app或sdk/runtime等的开明包信息，未找到则返回的KMRef对象的id、arch、branch等信息为空串
 */
KMRef KMStorageDir::findInstalledRef(const std::string &appName, const std::string &branch, const std::string &arch, KaimingKinds kinds)
{
    //======== 下面是实现方法1: 原flatpak的实现方式==========
    std::string defalutArch = arch;
    if (defalutArch.empty())
    {
        defalutArch = KMUtil::kmGetArch();
    }

    return KMStorageDir::findMatchingRef(appName, branch, defalutArch, kinds, getAllInstalledRefs(kinds));

    //======== 下面是实现方法2：优化后的实现方式，应用多时效率应比上面方式高==========
    // if (kinds & KaimingKinds::KAIMING_KINDS_APP) {
    //     KMRef ref = KMUtil::constructRef(KaimingKinds::KAIMING_KINDS_APP, appName, arch, branch);
    //     std::string deploy = KMStorageDir::getDeployedDir(ref);
    //     if (deploy.empty())
    //         return ref;
    // }

    // if (kinds & KaimingKinds::KAIMING_KINDS_RUNTIME) {
    //     KMRef ref = KMUtil::constructRef(KaimingKinds::KAIMING_KINDS_RUNTIME, appName, arch, branch);
    //     std::string deploy = KMStorageDir::getDeployedDir(ref);
    //     if (deploy.empty())
    //         return ref;
    // }

    // return KMRef();
}

/**
 * collectDeployedRefs:
 * @brief : 根据目录名（系统安装目录下的app或runtime下的子目录名）对应的目录树信息判断已经安装的开明包信息列表
 * @param : [in] baseDir，系统安装目录下的app或runtime目录
 * @param : [in] kinds，查找开明包安装目录的类型，app还是runtime等
 * @param : [in] namePrefix，app或sdk等的id的前缀，形如"org.videolan.VLC."或"org.freedesktop.Sdk."
 * @param : [in] arch，架构
 * @param : [in] branch，版本
 * @return: 返回已安装的app或sdk/runtime等的开明包信息列表
 */
std::vector<KMRef> KMStorageDir::collectDeployedRefs(const fs::path &baseDir, KaimingKinds kind, const std::string &namePrefix, const std::string &arch, const std::string &branch)
{
    assert(kind == KaimingKinds::KAIMING_KINDS_APP || kind == KaimingKinds::KAIMING_KINDS_RUNTIME);
    std::vector<KMRef> refs;

    if (!fs::exists(baseDir) || !fs::is_directory(baseDir))
    {
        return refs;
    }

    std::string id;
    for (auto &entryId : fs::directory_iterator(baseDir))
    {
        if (!entryId.is_directory())
        {
            continue;
        }

        id = entryId.path().filename();
        if (!KMUtil::startsWith(id, namePrefix))
        {
            continue;
        }

        fs::path appDir = baseDir / id / arch / branch / "active";
        if (!fs::exists(appDir))
        {
            continue;
        }

        refs.push_back(KMUtil::constructRef(kind, id, arch, branch));
    }

    return refs;
}

/**
 * listDeployedRefs:
 * @brief : 根据入参查找匹配的已经安装的app或sdk、runtime信息
 * @param : [in] kinds，查找开明包安装目录的类型，app还是runtime等
 * @param : [in] namePrefix，app或sdk等的id的前缀，形如"org.videolan.VLC."或"org.freedesktop.Sdk."
 * @param : [in] arch，架构
 * @param : [in] branch，版本
 * @return: 返回已安装的app或sdk/runtime等的开明包信息列表
 */
std::vector<KMRef> KMStorageDir::listDeployedRefs(KaimingKinds kinds, const std::string &namePrefix, const std::string &arch, const std::string &branch)
{
    std::vector<KMRef> refs;

    if (!fs::exists(d->m_baseDir))
    {
        return refs;
    }

    if (kinds & KaimingKinds::KAIMING_KINDS_APP)
    {
        std::string appDir = d->m_baseDir + "/app";
        KMUtil::append(refs, KMStorageDir::collectDeployedRefs(appDir, KaimingKinds::KAIMING_KINDS_APP, namePrefix, arch, branch));
    }

    if (kinds & KaimingKinds::KAIMING_KINDS_RUNTIME)
    {
        std::string runtimeDir = d->m_baseDir + "/runtime";
        KMUtil::append(refs, KMStorageDir::collectDeployedRefs(runtimeDir, KaimingKinds::KAIMING_KINDS_RUNTIME, namePrefix, arch, branch));
    }

    return refs;
}

/**
 * findFuzzyMatchingRef:
 * @brief : 根据入参在已安装的app或sdk/runtime等的开明包信息列表中查找匹配的开明包信息
 * @param : [in] appName，app或sdk等的id，形如"org.videolan.VLC"或"org.freedesktop.Sdk"
 * @param : [in] kinds，查找开明包安装目录的类型，app还是runtime等
 * @param : [in] refs，已安装的app或sdk/runtime等的开明包信息列表
 * @return: 返回匹配的已安装的开明包信息，未找到则返回的KMRef对象的id、arch、branch等信息为空串
 */
std::vector<KMRef> KMStorageDir::findFuzzyMatchingRef(const std::string &appName, KaimingKinds kinds, const std::vector<KMRef> &refs)
{
    std::vector<KMRef> vRefs;
    for (auto &ref : refs)
    {
        if (appName.empty())
        {
            continue;
        }

        if (ref.id.find(appName) == std::string::npos)
        {
            continue;
        }

        if ((kinds & KaimingKinds::KAIMING_KINDS_RUNTIME) && ref.kind == BASE_TYPE_RUNTIME)
        {
            vRefs.emplace_back(ref);
        }

        if ((kinds & KaimingKinds::KAIMING_KINDS_APP) && ref.kind == BASE_TYPE_APP)
        {
            vRefs.emplace_back(ref);
        }
    }

    return vRefs;
}

/**
 * findMatchingRef:
 * @brief : 根据入参在已安装的app或sdk/runtime等的开明包信息列表中查找匹配的开明包信息
 * @param : [in] appName，app或sdk等的id，形如"org.videolan.VLC"或"org.freedesktop.Sdk"
 * @param : [in] branch，版本
 * @param : [in] arch，架构
 * @param : [in] kinds，查找开明包安装目录的类型，app还是runtime等
 * @param : [in] refs，已安装的app或sdk/runtime等的开明包信息列表
 * @return: 返回匹配的已安装的开明包信息，未找到则返回的KMRef对象的id、arch、branch等信息为空串
 */
KMRef KMStorageDir::findMatchingRef(const std::string &appName, const std::string &branch, const std::string &arch, KaimingKinds kinds, const std::vector<KMRef> &refs)
{
    for (auto &ref : refs)
    {
        if (appName.empty())
        {
            continue;
        }

        bool bMatch = false;
        if ((branch.empty() || arch.empty()) && appName == ref.id)
        {
            bMatch = true;
        }

        if (!branch.empty() && !arch.empty())
        {
            if (appName == ref.id && branch == ref.branch && arch == ref.arch)
            {
                bMatch = true;
            }
        }

        if (bMatch)
        {
            bool bTypeMatch = false;
            if ((kinds & KaimingKinds::KAIMING_KINDS_RUNTIME) && ref.kind == BASE_TYPE_RUNTIME)
            {
                bTypeMatch = true;
            }

            if ((kinds & KaimingKinds::KAIMING_KINDS_APP) && ref.kind == BASE_TYPE_APP)
            {
                bTypeMatch = true;
            }

            if (!bMatch)
            {
                continue;
            }

            return ref;
        }
    }

    return KMRef();
}

/**
 * @brief : 根据开明包ref信息获取开明包安装的base目录
 * @param : [in] ref, 开明包ref信息
 * @return: 开明包安装的base目录
 * @note  : 目前开明包仅支持系统级安装
 */
std::string KMStorageDir::getRefBaseDirLocation(const KMRef &ref)
{
    return KMStorageDir::getSystemDefaultBaseDirLocation() + "/" + ref.channel + "/" + ref.arch + "/" + ref.kind + "/" + ref.id + "/" + ref.module + "/" + ref.version;
}

/**
 * getDeployedDir:
 * @brief : 根据ref信息获取开明包部署路径
 * @param : [in] ref, 开明包基础信息
 * @param : [out] ref, 会更新里面的commit信息（主要是将active>具体的commitid）
 * @return: 开明包部署路径
 */
std::string KMStorageDir::getDeployedDir(const KMRef &ref)
{
    fs::path deployedDir(KMUtil::realpath(KMStorageDir::getRefBaseDirLocation(ref)));

    if (deployedDir.empty())
    {
        return "";
    }

    if (!fs::is_directory(deployedDir))
    {
        return "";
    }

    if (!fs::exists(deployedDir / "deploy"))
    {
        return "";
    }

    return deployedDir.c_str();
}

/**
 * loadDeployed:
 * @brief : 根据ref信息查询开明包是否部署
 * @param : [in] ref, 开明包基础信息
 * @return: 返回部署信息
 */
std::shared_ptr<KMDeploy> KMStorageDir::loadDeployed(const KMRef &ref)
{
    std::shared_ptr<KMDeploy> deploy = std::make_shared<KMDeploy>();
    std::string deployDir = KMStorageDir::getDeployedDir(ref);
    if (!deployDir.empty())
    {
        deploy->m_dir = deployDir;

        std::shared_ptr<KMInfoJson> infoJson = std::make_shared<KMInfoJson>();
        infoJson->loadFile(KMUtil::buildFilename(deployDir, "info.json"));
        deploy->m_infoJson = infoJson;

        deploy->m_ref = ref;
    }

    return deploy;
}

/**
 * @brief : 获取未维护的扩展包目录
 * @param : [in] ref，扩展包的ref
 * @return: 未维护的扩展包目录
 */
std::string KMStorageDir::getUnmaintainedExtensionDir(const KMRef &ref)
{
    return d->m_baseDir + "/extension/" + ref.id + "/" + ref.arch + "/" + ref.branch;
}

/**
 * @brief : 获取未维护的扩展包目录
 * @param : [in] ref，扩展包的ref
 * @return: 未维护的扩展包目录
 */
std::string KMStorageDir::findUnmaintainedExtensionDirIfExists(const KMRef &ref)
{
    fs::path dir(KMUtil::realpath(getUnmaintainedExtensionDir(ref)));
    if (!fs::is_directory(dir))
    {
        return "";
    }

    return dir.c_str();
}

bool KMStorageDir::extensionMatchesReason(const std::string &extensionId, const std::string &reasons, bool defaultValue)
{
    if (reasons.empty())
    {
        return defaultValue;
    }

    std::size_t index = extensionId.find_last_of(".");
    if (index == std::string::npos)
    {
        return false;
    }
    std::string extensionBasename = extensionId.substr(index + 1);

    std::vector<std::string> reasonList = KMUtil::splitString(reasons, ";");
    for (const std::string &reason : reasonList)
    {
        if (reason == "active-gl-driver")
        {
            /* handled below */
            // TODO:
            return true;
        }
        else if (reason == "active-gtk-theme")
        {
            // TODO:
            return true;
        }
        else if (reason == "have-intel-gpu")
        {
            // TODO:
            return true;
        }
        else if (KMUtil::startsWith(reason, "have-kernel-module-"))
        {
            // TODO:
            return true;
        }
        else if (KMUtil::startsWith(reason, "on-xdg-desktop-"))
        {
            // TODO:
            return true;
        }
    }

    return false;
}

void KMStorageDir::addExtension(const KMExtension &ext, std::vector<KMExtension> &list)
{
    if (ext.directory.empty())
    {
        return;
    }

    KMExtension extension = ext;
    std::string files = findUnmaintainedExtensionDirIfExists(extension.ref);
    std::string deployDir = KMStorageDir::getDeployedDir(extension.ref);

    if (files.empty())
    {
        if (!deployDir.empty())
        {
            files = deployDir + "/files";
        }
    }
    else
    {
        extension.isUnmaintained = true;
    }

    /* Prefer a full extension (org.freedesktop.Locale) over subdirectory ones (org.freedesktop.Locale.sv) */
    if (!files.empty())
    {
        if (KMStorageDir::extensionMatchesReason(ext.name, ext.enableIf, true))
        {
            extension.extensionFilesPath = files;
            if (!deployDir.empty() && !ext.isUnmaintained)
            {
                fs::path deployPath(deployDir);
                extension.ref.refCommitId = deployPath.filename().c_str();
            }

            if (ext.isUnmaintained)
            {
                extension.priority = 1000;
            }
            else
            {
                std::shared_ptr<KMMetaData> metadata = std::make_shared<KMMetaData>(ext.ref.kind == BASE_TYPE_APP ? true : false);
                std::string metadataPath = deployDir + "/metadata";
                if (metadata->load(metadataPath))
                {
                    extension.priority = metadata->getLongValue(METADATA_GROUP_EXTENSION_OF, METADATA_KEY_PRIORITY, 1000);
                }
                else
                {
                    extension.priority = 1000;
                }
            }

            extension.version = extension.ref.branch;
            extension.versions.clear();

            list.push_back(extension);
        }
    }
    else if (ext.subdirectories)
    {
        std::string prefix = ext.name + ".";
        KMStorageDir dir = KMStorageDir::getSystemDefaultDir();
        std::vector<KMRef> refs(dir.listDeployedRefs(KaimingKinds::KAIMING_KINDS_RUNTIME, prefix, ext.ref.arch, ext.ref.branch));
        for (auto &ref : refs)
        {
            std::string name(ref.id);
            std::string extendedDir = ext.directory + "/" + KMUtil::replace(name, prefix, "");

            extension.ref = ref;
            extension.name = ref.id;
            extension.directory = extendedDir;

            std::string subdirDeployDir = KMStorageDir::getDeployedDir(ref);
            std::string subdirFiles;
            if (!subdirDeployDir.empty())
            {
                subdirFiles = subdirDeployDir + "/files";
            }
            if (!subdirFiles.empty() && KMStorageDir::extensionMatchesReason(ref.id, ext.enableIf, true))
            {
                extension.needsTmpfs = true;
                extension.extensionFilesPath = subdirFiles;

                if (!subdirDeployDir.empty())
                {
                    fs::path deployPath(subdirDeployDir);
                    extension.ref.refCommitId = deployPath.filename().c_str();
                }

                std::shared_ptr<KMMetaData> metadata = std::make_shared<KMMetaData>(ext.ref.kind == BASE_TYPE_APP ? true : false);
                std::string metadataPath = subdirDeployDir + "/metadata";
                if (metadata->load(metadataPath))
                {
                    extension.priority = metadata->getLongValue(METADATA_GROUP_EXTENSION_OF, METADATA_KEY_PRIORITY, 1000);
                }
                else
                {
                    extension.priority = 1000;
                }

                extension.version = ref.branch;
                extension.versions.clear();

                list.push_back(extension);
            }

            // TODO: 暂时不提供unmaintained处理，kaiming后面不一定有unmaintained的状态
        }
    }
}

/**
 * @brief 确保仓库存在
 * @param allowEmpty 是否允许为空
 * @return true 存在 false 不存在
 */
bool KMStorageDir::ensureRepo(bool allowEmpty)
{
    KMFolder folder(d->m_baseDir, d->m_user);
    return folder.ensureRepo(allowEmpty);
}
