#include "KMParseDeploy.h"

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

class KMParseDeploy::Private
{
public:
};

KMParseDeploy::KMParseDeploy()
    : d(make_shared<Private>())
{
}

KMParseDeploy::~KMParseDeploy() { }

bool KMParseDeploy::load(const std::string &deployFilePath)
{
    if (!fs::exists(deployFilePath))
    {
        kmlogger.error("%s is not exists!", deployFilePath.c_str());
        return false;
    }

    GBytes *deployData = nullptr;

    string refId = "";
    deployData = getDeployData(deployFilePath);
    if (deployData == nullptr)
    {
        return false;
    }

    const char *runtime = getRuntimeFromDeployData(deployData);
    kmlogger.info("deploy runtime: %s", runtime);

    const char *extensionOf = getRuntimeFromDeployData(deployData);
    kmlogger.info("deploy extension-of: %s", extensionOf);

    guint installedSize = getInstalledSizeFromDeployData(deployData);
    kmlogger.info("deploy installedSize: %d", (int)installedSize);

    const char *appdataName = getAppdataNameFromDeployData(deployData);
    kmlogger.info("deploy appdataname: %s", appdataName);

    const char *appdataVersion = getAppdataVersionFromDeployData(deployData);
    if (appdataVersion)
    {
        kmlogger.info("deploy appdataversion: %s", appdataVersion);
    }

    return true;
}

GBytes *KMParseDeploy::getDeployData(const string &deployFilePath)
{
    GError **error = nullptr;
    GCancellable *cancellable = nullptr;
    GBytes *deployData = nullptr;
    GFile *dataFile = nullptr;
    gchar *contents;
    gsize len;

    dataFile = g_file_new_for_path(deployFilePath.c_str());

    if (!g_file_load_contents(dataFile, cancellable, &contents, &len, nullptr, error))
    {
        return nullptr;
    }

    deployData = g_bytes_new_take(contents, len);

    return deployData;
}

/**
 * 判断 ref 是否已部署
 */
bool KMParseDeploy::isDeployed(const std::string &ref)
{
    if (!fs::exists(ref))
    {
        return false;
    }

    return true;
}

const char *KMParseDeploy::getStringFromDeployData(GBytes *deployData, const char *key)
{
    VarDeployDataRef ref = varDeployDataFromBytes(deployData);
    VarMetadataRef metadata = varDeployDataGetMetadata(ref);

    return varMetadataLookupString(metadata, key, nullptr);
}

const char *KMParseDeploy::getOriginFromDeployData(GBytes *deployData)
{
    VarDeployDataRef ref = varDeployDataFromBytes(deployData);
    return varDeployDataGetOrigin(ref);
}

const char *KMParseDeploy::getCommitFromDeployData(GBytes *deployData)
{
    VarDeployDataRef ref = varDeployDataFromBytes(deployData);
    return varDeployDataGetCommit(ref);
}

/**
 * 获取 metadata 数据，和 active/metadata 内容一致，因此可以直接解析 active/metadata 文件较为简单
 */
GVariant *KMParseDeploy::getMetadataFromDeployData(GBytes *deployData)
{
    VarDeployDataRef ref = varDeployDataFromBytes(deployData);
    VarMetadataRef metadata = varDeployDataGetMetadata(ref);

    return g_variant_ref_sink(varVetadataPeekAsGvariant(metadata));
}

gint32 KMParseDeploy::getVersionFromDeployData(GBytes *deployData)
{
    VarDeployDataRef ref = varDeployDataFromBytes(deployData);
    VarMetadataRef metadata = varDeployDataGetMetadata(ref);

    return varMetadataLookupInt32(metadata, "deploy-version", 0);
}

guint64 KMParseDeploy::getTimestampFromDeployData(GBytes *deployData)
{
    VarDeployDataRef ref = varDeployDataFromBytes(deployData);
    VarMetadataRef metadata = varDeployDataGetMetadata(ref);

    return varMetadataLookupUint64(metadata, "timestamp", 0);
}

const char *KMParseDeploy::getLocaledString(GBytes *deployData, const char *key)
{
    VarDeployDataRef ref = varDeployDataFromBytes(deployData);
    VarMetadataRef metadata = varDeployDataGetMetadata(ref);
    const char *const *languages = g_get_language_names();
    int i;

    for (i = 0; languages[i]; ++i)
    {
        g_autofree char *localedKey = nullptr;
        VarVariantRef varValue;

        if (strcmp(languages[i], "C") == 0)
        {
            localedKey = g_strdup(key);
        }
        else
        {
            localedKey = g_strdup_printf("%s@%s", key, languages[i]);
        }

        if (varMetadataLookup(metadata, localedKey, nullptr, &varValue) && isVarVariantType(varValue, G_VARIANT_TYPE_STRING))
        {
            return varVariantGetString(varValue);
        }
    }

    return nullptr;
}

const char *KMParseDeploy::getAltIdFromDeployData(GBytes *deployData)
{
    return getStringFromDeployData(deployData, "alt-id");
}

const char *KMParseDeploy::getEolFromDeployData(GBytes *deployData)
{
    return getStringFromDeployData(deployData, "eol");
}

const char *KMParseDeploy::getEolRebaseFromDeployData(GBytes *deployData)
{
    return getStringFromDeployData(deployData, "eolr");
}

/**
 * 实例：runtime: top.openkylin.Platform/x86_64/0.5
 */
const char *KMParseDeploy::getRuntimeFromDeployData(GBytes *deployData)
{
    return getStringFromDeployData(deployData, "runtime");
}

/**
 * 实例：extension-of: top.openkylin.Platform/x86_64/0.5
 */
const char *KMParseDeploy::getExtensionOfFromDeployData(GBytes *deployData)
{
    return getStringFromDeployData(deployData, "extension-of");
}

const char **KMParseDeploy::getSubpathsFromDeployData(GBytes *deployData)
{
    VarDeployDataRef ref = varDeployDataFromBytes(deployData);
    return varArrayofstringToStrv(varDeployDataGetSubpaths(ref), nullptr);
}

bool KMParseDeploy::hasSubpathsFromDeployData(GBytes *deployData)
{
    VarDeployDataRef ref = varDeployDataFromBytes(deployData);
    VarArrayofstringRef subpaths = varDeployDataGetSubpaths(ref);

    return varArrayofstringGetLength(subpaths) != 0;
}

/**
 * 软件包安装大小，单位为字节，如 org.kde.kclock 大小为 12363264 字节 = 12363264 / 1024 / 1024 = 11.8 MB
 */
guint64 KMParseDeploy::getInstalledSizeFromDeployData(GBytes *deployData)
{
    VarDeployDataRef ref = varDeployDataFromBytes(deployData);
    return varDeployDataGetInstalledSize(ref);
}

const char *KMParseDeploy::getAppdataNameFromDeployData(GBytes *deployData)
{
    return getStringFromDeployData(deployData, "appdata-name");
}

const char *KMParseDeploy::getAppdataSummaryFromDeployData(GBytes *deployData)
{
    return getStringFromDeployData(deployData, "appdata-summary");
}

const char *KMParseDeploy::getAppdataVersionFromDeployData(GBytes *deployData)
{
    return getStringFromDeployData(deployData, "appdata-version");
}

const char *KMParseDeploy::getAppdataLicenseFromDeployData(GBytes *deployData)
{
    return getStringFromDeployData(deployData, "appdata-license");
}

const char *KMParseDeploy::getAppdataContentRatingTypeFromDeployData(GBytes *deployData)
{
    VarDeployDataRef ref = varDeployDataFromBytes(deployData);
    VarMetadataRef metadata = varDeployDataGetMetadata(ref);
    VarVariantRef ratingVariant;

    if (varMetadataLookup(metadata, "appdata-content-rating", nullptr, &ratingVariant))
    {
        VarContentRatingRef rating = varContentRatingFromVariant(ratingVariant);
        return varContentRatingGetRatingType(rating);
    }

    return nullptr;
}

GHashTable *KMParseDeploy::getAppdataContentRatingFromDeployData(GBytes *deployData)
{
    VarDeployDataRef ref = varDeployDataFromBytes(deployData);
    VarMetadataRef metadata = varDeployDataGetMetadata(ref);
    VarVariantRef ratingVariant;
    g_autoptr(GHashTable) contentRating = nullptr;

    if (varMetadataLookup(metadata, "appdata-content-rating", nullptr, &ratingVariant))
    {
        VarContentRatingRef rating = varContentRatingFromVariant(ratingVariant);
        VarRatingsRef ratings = varContentRatingGetRatings(rating);
        gsize len, i;

        contentRating = g_hash_table_new_full(g_str_hash, g_str_equal, nullptr, nullptr);

        len = varRatingsGetLength(ratings);
        for (i = 0; i < len; i++)
        {
            VarRatingsEntryRef entry = varRatingsGetAt(ratings, i);

            g_hash_table_insert(contentRating, (gpointer)g_intern_string(varRatingsEntryGetKey(entry)), (gpointer)g_intern_string(varRatingsEntryGetValue(entry)));
        }
    }

    return KMUtil::stealPointer(&contentRating);
}

/**
 * @brief 存储字节流到文件中，比如 deploy文件
 *
 * @param dest 存储的目标文件路径
 * @param bytes 需存储的字节流
 * @param cancellable
 * @param error
 * @return true
 * @return false
 */
bool KMParseDeploy::saveBytesToFile(GFile *dest, GBytes *bytes, GCancellable *cancellable, GError **error)
{
    kmlogger.info("dest file: %s", g_file_get_path(dest));

    if (!dest)
    {
        kmlogger.error("dest file is NULL");
        return false;
    }

    if (!bytes)
    {
        kmlogger.error("bytes file is NULL");
        return false;
    }

    kmlogger.info("ready to save bytes to file %s", g_file_get_path(dest));

    // 获取字节数据和长度
    const guint8 *data = (const guint8 *)g_bytes_get_data(bytes, NULL);
    gsize size = g_bytes_get_size(bytes);

    std::ofstream outFile(g_file_get_path(dest), std::ios::binary);
    if (!outFile)
    {
        kmlogger.error("Failed to open deploy file.");
        return false;
    }
    outFile.write(reinterpret_cast<const char *>(data), size);
    outFile.close();

    kmlogger.info("File written successfully.");

    return true;
}

/**
 * @brief 获取部署数据字节流
 *
 * @param ref
 * @param commitDataPath commit目录地址
 * @param commitDataGVariant 从 ostree ostree_repo_load_commit() 中获取的 commit 对应的数据
 * @param commitMetadataGVariant
 * @param metadata  metadata 元数据对应的数据文件 : /opt/kaiming/app/org.kde.kclock/current/active/metadata
 * @return GBytes*  返回的需要部署的字节流
 */
GBytes *KMParseDeploy::deployDataToGBytes(KMCommonRef ref, fs::path commitDataPath, GVariant *commitDataGVariant, GVariant *commitMetadataGVariant, GKeyFile *metadata)
{
    string id = ref.id;
    string commit = ref.commit;
    string completedId = ref.completedId;
    string baseType;
    guint64 installedSize = ref.installedSizeInt;
    fs::path deployDir = commitDataPath;

    char *empty_subpaths[] = { NULL };

    g_auto(GVariantDict) metadataDict = KAIMING_VARIANT_DICT_INITIALIZER;
    GVariant *res = NULL;

    if (KMUtil::startsWithForRef(completedId, "app"))
    {
        baseType = "app";
    }
    else
    {
        baseType = "runtime";
    }

    kmlogger.info("baseType: %s compltedId: %s commit: %s", baseType.c_str(), completedId.c_str(), commit.c_str());

    g_variant_dict_init(&metadataDict, NULL);
    g_variant_dict_insert_value(&metadataDict, "deploy-version", g_variant_new_int32(KAIMING_DEPLOY_VERSION_CURRENT));
    g_variant_dict_insert_value(&metadataDict, "timestamp", g_variant_new_uint64(ostree_commit_get_timestamp(commitDataGVariant)));

    addCommitMetadataToDeployData(&metadataDict, commitMetadataGVariant);

    if (metadata != NULL)
    {
        addMetadataToDeployDataWithType(&metadataDict, metadata, baseType);
    }

    addAppdataToDeployData(&metadataDict, deployDir, id.c_str());

    res = g_variant_ref_sink(g_variant_new("(ss^ast@a{sv})", REPO_NAME, commit.c_str(), empty_subpaths, GUINT64_TO_BE(installedSize), g_variant_dict_end(&metadataDict)));
    return g_variant_get_data_as_bytes(res);
}

/**
 * @brief 添加 commit 目录下的 metadata 到字节流数据词典中
 *
 * @param metadataDict 字节流词典
 * @param commitMetadataGVariant 从 ostree ostree_repo_load_commit() 中获取的 commit 对应的数据，和commit 目录下的 metadata 字节流数据保持一致
 */
void KMParseDeploy::addCommitMetadataToDeployData(GVariantDict *metadataDict, GVariant *commitMetadataGVariant)
{
    const char *altId = NULL;
    const char *eol = NULL;
    const char *eolRebase = NULL;

    g_variant_lookup(commitMetadataGVariant, "xa.alt-id", "&s", &altId);
    g_variant_lookup(commitMetadataGVariant, OSTREE_COMMIT_META_KEY_ENDOFLIFE, "&s", &eol);
    g_variant_lookup(commitMetadataGVariant, OSTREE_COMMIT_META_KEY_ENDOFLIFE_REBASE, "&s", &eolRebase);

    if (altId)
    {
        g_variant_dict_insert_value(metadataDict, "alt-id", g_variant_new_string(altId));
    }

    if (eol)
    {
        g_variant_dict_insert_value(metadataDict, "eol", g_variant_new_string(eol));
    }

    if (eolRebase)
    {
        g_variant_dict_insert_value(metadataDict, "eolr", g_variant_new_string(eolRebase));
    }
}

void KMParseDeploy::addMetadataToDeployData(GVariantDict *metadataDict, GKeyFile *keyfile)
{
    g_autofree char *applicationRuntime = NULL;
    g_autofree char *extensionOf = NULL;
    if (!keyfile)
    {
        kmlogger.warn("keyfile is NULL");
        return;
    }

    applicationRuntime = g_key_file_get_string(keyfile, METADATA_GROUP_APPLICATION, METADATA_KEY_RUNTIME, NULL);
    extensionOf = g_key_file_get_string(keyfile, METADATA_GROUP_EXTENSION_OF, METADATA_KEY_REF, NULL);

    if (applicationRuntime)
    {
        g_variant_dict_insert_value(metadataDict, "runtime", g_variant_new_string(applicationRuntime));
    }

    if (extensionOf)
    {
        g_variant_dict_insert_value(metadataDict, "extension-of", g_variant_new_string(extensionOf));
    }
}

void KMParseDeploy::addMetadataToDeployDataWithType(GVariantDict *metadataDict, GKeyFile *keyfile, string baseType)
{
    g_autofree char *applicationRuntime = NULL;
    g_autofree char *extensionOf = NULL;
    if (!keyfile)
    {
        kmlogger.warn("keyfile is NULL");
        return;
    }

    if (baseType == "app")
    {
        applicationRuntime = g_key_file_get_string(keyfile, METADATA_GROUP_APPLICATION, METADATA_KEY_RUNTIME, NULL);
    }
    else
    {
        applicationRuntime = g_key_file_get_string(keyfile, METADATA_GROUP_RUNTIME, METADATA_KEY_RUNTIME, NULL);
    }
    extensionOf = g_key_file_get_string(keyfile, METADATA_GROUP_EXTENSION_OF, METADATA_KEY_REF, NULL);

    if (applicationRuntime)
    {
        g_variant_dict_insert_value(metadataDict, "runtime", g_variant_new_string(applicationRuntime));
    }

    if (extensionOf)
    {
        g_variant_dict_insert_value(metadataDict, "extension-of", g_variant_new_string(extensionOf));
    }
}

/**
 * @brief 将 指定路径下的xml文件添加到部署内容中
 *
 * 指定路径xml文件： /opt/kaiming/app/org.kde.kclock/current/active/files/share/app-info/xmls/org.kde.kclock.xml.gz
 *
 * @param metadataDict 字节流词典
 * @param deployDir 部署目录,即 commit对应目录
 * @param id 应用或运行时id
 */
void KMParseDeploy::addAppdataToDeployData(GVariantDict *metadataDict, fs::path deployDir, const char *id)
{
    g_autofree char *appdataXml = NULL;
    g_autoptr(GHashTable) names = NULL;
    g_autoptr(GHashTable) comments = NULL;
    g_autofree char *version = NULL;
    g_autofree char *license = NULL;
    g_autofree char *contentRatingType = NULL;
    g_autoptr(GHashTable) contentRating = NULL;

    appdataXml = readAppdataXmlFromDeployDir(deployDir, id);
    if (appdataXml == NULL)
    {
        return;
    }
    // kmlogger.info("appdataXml: %s", appdataXml); // 软件信息文件较大,暂不输出

    if (parseAppdata(appdataXml, id, &names, &comments, &version, &license, &contentRatingType, &contentRating))
    {
        addLocaleMetadataString(metadataDict, "appdata-name", names);
        addLocaleMetadataString(metadataDict, "appdata-summary", comments);
        if (version)
        {
            g_variant_dict_insert_value(metadataDict, "appdata-version", g_variant_new_string(version));
        }

        if (license)
        {
            g_variant_dict_insert_value(metadataDict, "appdata-license", g_variant_new_string(license));
        }

        if (contentRatingType != NULL && contentRating != NULL)
        {
            g_variant_dict_insert_value(metadataDict, "appdata-content-rating", appdataContentRatingToVariant(contentRatingType, contentRating));
        }
    }
}

char *KMParseDeploy::readAppdataXmlFromDeployDir(fs::path deployDir, const char *id)
{
    g_autoptr(GFile) appdataGFile = NULL;
    g_autoptr(GFileInputStream) appdataInputStream = NULL;
    gsize size;

    string appdataName = string(id).append(".xml.gz");
    fs::path appdataDirPath = deployDir / "files/share/app-info/xmls";
    string appdataFile = appdataDirPath / appdataName;
    kmlogger.info("appdataFile: %s", appdataFile.c_str());

    appdataGFile = g_file_new_for_path(appdataFile.c_str());

    appdataInputStream = g_file_read(appdataGFile, NULL, NULL);
    if (appdataInputStream)
    {
        g_autoptr(GZlibDecompressor) decompressor = g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP);
        g_autoptr(GInputStream) converter = g_converter_input_stream_new(G_INPUT_STREAM(appdataInputStream), G_CONVERTER(decompressor));
        g_autoptr(GBytes) appdataXml = NULL;

        appdataXml = readStream(converter, TRUE, NULL);
        if (appdataXml)
        {
            return (char *)g_bytes_unref_to_data(KMUtil::stealPointer(&appdataXml), &size);
        }
    }
    return NULL;
}

void KMParseDeploy::addLocaleMetadataString(GVariantDict *metadataDict, const char *keyname, GHashTable *values)
{
    if (values == NULL)
    {
        return;
    }

    HASH_TABLE_FOREACH_KV(values, const char *, locale, const char *, value)
    {
        const char *key;
        g_autofree char *keyFree = NULL;

        if (strcmp(locale, "C") == 0)
        {
            key = keyname;
        }
        else
        {
            keyFree = g_strdup_printf("%s@%s", keyname, locale);
            key = keyFree;
        }

        if (value)
        {
            g_variant_dict_insert_value(metadataDict, key, g_variant_new_string(value));
        }
    }
}

GVariant *KMParseDeploy::appdataContentRatingToVariant(const char *contentRatingType, GHashTable *contentRating)
{
    g_autoptr(GVariantBuilder) builder = g_variant_builder_new(G_VARIANT_TYPE("(sa{ss})"));
    GHashTableIter iter;
    gpointer key, value;

    g_variant_builder_add(builder, "s", contentRatingType);
    g_variant_builder_open(builder, G_VARIANT_TYPE("a{ss}"));

    g_hash_table_iter_init(&iter, contentRating);

    while (g_hash_table_iter_next(&iter, &key, &value))
    {
        const char *id = (const char *)key, *val = (const char *)value;
        g_variant_builder_add(builder, "{ss}", id, val);
    }

    g_variant_builder_close(builder);

    return g_variant_builder_end(builder);
}

GBytes *KMParseDeploy::readStream(GInputStream *in, gboolean nullTerminate, GError **error)
{
    g_autoptr(GOutputStream) memStream = NULL;

    memStream = g_memory_output_stream_new_resizable();
    if (g_output_stream_splice(memStream, in, (GOutputStreamSpliceFlags)0, NULL, error) < 0)
    {
        return NULL;
    }

    if (nullTerminate)
    {
        if (!g_output_stream_write(G_OUTPUT_STREAM(memStream), "\0", 1, NULL, error))
        {
            return NULL;
        }
    }

    if (!g_output_stream_close(G_OUTPUT_STREAM(memStream), NULL, error))
    {
        return NULL;
    }

    return g_memory_output_stream_steal_as_bytes(G_MEMORY_OUTPUT_STREAM(memStream));
}
