/* common/KMInstalledAppQuery.cpp
 *
 * Copyright (C) 2024 OpenKylin, Inc
 */

#include "KMInstalledAppQuery.h"
#include "KMInfoJson.h"
#include "semver.hpp"
KMInstalledAppQuery::KMInstalledAppQuery()
{
    m_condition = {
        .id = "",
        .channel = "",
        .version = "",
        .arch = "",
        .module = "",
        .branch = "",
        .kind = "",
        .refCommitId = ""
    };
}

KMInstalledAppQuery::~KMInstalledAppQuery()
{}

std::vector<KMRef> KMInstalledAppQuery::query()
{
    return collect(m_condition);
}

/**
 * childrenDir:
 * @brief : 列出指定目录的子目录名列表
 * @param : [in] baseDir, 查找的目标目录
 * @return: std::vector<std::string> 子目录名列表
 */
std::vector<std::string> KMInstalledAppQuery::childrenDir(const std::string &baseDir)
{
    std::vector<std::string> subdirs;

    fs::path basePath(baseDir);
    if (!fs::exists(basePath))
    {
        return subdirs;
    }

    for (auto const &dirEntry : fs::directory_iterator{ basePath })
    {
        fs::path p = dirEntry.path();
        if (fs::is_directory(p))
        {
            subdirs.push_back(p.filename());
        }
    }

    return subdirs;
}

/**
 * orderVersions:
 * @brief : 根据版本号规则进行降序排序
 * @param : [in] versions, 版本号列表
 * @return: std::vector<std::string> 降序排序后的版本号列表
 */
std::vector<std::string> KMInstalledAppQuery::orderVersions(const std::vector<std::string>& versions)
{
    //版本号排序
    Semver::KMVersion tmpParse;
    std::vector<Semver::KMVersion> versionsParses;
    for(auto &version:versions)
    {
        tmpParse = Semver::KMVersion::parse(version);
        versionsParses.push_back(tmpParse);
    }
    //倒序排序
    std::sort(versionsParses.begin(),versionsParses.end());  
    std::reverse(versionsParses.begin(),versionsParses.end());

    std::vector<std::string> orderedVersions;
    for(auto &version: versionsParses)
    {
        orderedVersions.push_back(version.str());
    }
    return orderedVersions;
}

/**
 * matchVersions:
 * @brief : 查找版本号列表versions中与版本version模糊匹配的版本列表，版本号列表versions中精确包含版本version时，不需要调用此函数
 * @param : [in] versions, 版本号列表
 * @param : [in] version, 精确版本号或前缀版本号
 * @return: std::vector<std::string> 降序排序的匹配的版本列表；
 * @note  : 进行匹配前，先查看下是否有精确匹配，有则不需要调用此函数
 */
std::vector<std::string> KMInstalledAppQuery::matchVersions(const std::vector<std::string>& versions, const std::string& version)
{
    std::vector<std::string> orderedVersions = orderVersions(versions);

    if (version.empty())
    {
        return orderedVersions;
    }

    std::vector<std::string> matchs;
    if (KMUtil::contains(versions, version))
    {
        // 精确匹配的只返回精确版本号
        matchs.push_back(version);
        return matchs;
    }


    KMInfoJson::InfoBase versionStandard , versionChecked;  
    Semver::KMVersion versionStandardParse , versionCheckedParse;

    //解析version
    versionStandard.m_baseVersion = version;
    versionStandard.versionParse();
    //version是从yaml文件中读取出来的，非标准格式
    versionStandardParse = Semver::KMVersion::parse(version, false);  

    for (auto& ver : versions)
    {
        //解析ver
        versionChecked.m_baseVersion = ver;
        versionChecked.versionParse();
        versionCheckedParse = Semver::KMVersion::parse(ver);
            
        if (versionChecked.checkVersion(versionStandard) && versionCheckedParse >= versionStandardParse)
        {
            matchs.push_back(ver);
        }
    }

    return matchs;
}

/**
 * collect:
 * @brief : 根据设置的条件查询已安装的开明包(包括base\runtime\app)列表
 * @param : [in] condition, 筛选条件
 * @return: std::vector<std::string> 降序排序后的版本号列表
 */
std::vector<KMRef> KMInstalledAppQuery::collect(const KMRef &condition)
{
    std::vector<KMRef> result;
    KMInstalledAppQuery query;
    KMRef ref = condition;

    // 开明包系统级安装目录，形如：/opt/kaiming/layers/${channel}/${arch}/${kind}/${id}/{module}/${version}
    std::string baseDir = KMStorageDir::getSystemDefaultBaseDirLocation();
    {   // channel
        if (!fs::exists(baseDir))
        {
            return result;
        }

        if (condition.channel.empty())
        {
            std::vector<std::string> channels = childrenDir(baseDir);

            for (auto& channel : channels)
            {
                ref.channel = channel;
                KMUtil::append(result, query.ref(ref).query());
            }

            return result;
        }
    }
    
    baseDir = KMUtil::buildFilename(baseDir, condition.channel);
    {   // arch
        if (!fs::exists(baseDir))
        {
            return result;
        }

        if (condition.arch.empty())
        {
            std::vector<std::string> archs = childrenDir(baseDir);

            for (auto& arch : archs)
            {
                ref.arch = arch;
                KMUtil::append(result, query.ref(ref).query());
            }

            return result;
        }
    }

    baseDir = KMUtil::buildFilename(baseDir, condition.arch);
    {   // kind
        if (!fs::exists(baseDir))
        {
            return result;
        }

        if (condition.kind.empty())
        {
            std::vector<std::string> kinds = childrenDir(baseDir);
            for (auto& kind : kinds)
            {
                ref.kind = kind;
                KMUtil::append(result, query.ref(ref).query());
            }

            return result;
        }
    }
    
    baseDir = KMUtil::buildFilename(baseDir, condition.kind);
    {   // id
        if (!fs::exists(baseDir))
        {
            return result;
        }

        if (condition.id.empty())
        {
            std::vector<std::string> ids = childrenDir(baseDir);

            for (auto& id : ids)
            {
                ref.id = id;
                KMUtil::append(result, query.ref(ref).query());
            }

            return result;
        }
    }

    baseDir = KMUtil::buildFilename(baseDir, condition.id);
    {   // module
        if (!fs::exists(baseDir))
        {
            return result;
        }

        if (condition.module.empty())
        {
            std::vector<std::string> modules = childrenDir(baseDir);

            for (auto& module : modules)
            {
                ref.module = module;
                KMUtil::append(result, query.ref(ref).query());
            }

            return result;
        }
    }

    baseDir = KMUtil::buildFilename(baseDir, condition.module);
    {   // version
        if (!fs::exists(baseDir))
        {
            return result;
        }

        std::vector<std::string> versions = childrenDir(baseDir);
        if (versions.empty())
        {
            return result;
        }

        if (!condition.version.empty() && KMUtil::contains(versions, condition.version))
        {
            // do nothing
        }
        else
        {
            std::vector<std::string> matchs;
            if (condition.version.empty())
            {
                matchs = orderVersions(versions);
            }
            else
            {
                matchs = matchVersions(versions, condition.version);
            }

            for (auto& version : matchs)
            {
                ref.version = version;
                KMUtil::append(result, query.ref(ref).query());
            }

            return result;
        }
    }

    baseDir = KMUtil::buildFilename(baseDir, condition.version);
    {   // deploy
        if (!fs::exists(baseDir))
        {
            return result;
        }
        
        std::string deploy = KMUtil::buildFilename(baseDir, "deploy");
        if (fs::exists(deploy))
        {
            result.push_back(ref);
        }
    }

    return result;
}
