#include "KMBuilderCCache.h"
#include "KMBuilderCache.h"
#include "KMBuilderContext.h"
#include "KMBuilderUtils.h"
#include "common/KMLogger.h"

/**
 * @brief : 构造函数
 */
KMBuilderCache::KMBuilderCache()
{
    m_builderCache.isDisabled = false;
    std::string runDir = builderContext.getRunDir();
    m_builderCache.ccacheDir = builderContext.getCcacheDir();
    m_builderCache.cacheDir = builderContext.getCacheDir();
    m_builderCache.buildDir = builderContext.getBuildDir();
    m_builderCache.appDir = builderContext.getAppDir();
    m_builderCache.manifestName = builderContext.getConfigFile();
    m_builderCache.branch = builderContext.getArch() + "-" + g_path_get_basename(builderContext.getConfigFile().c_str());
    m_builderCache.pChecksum = g_checksum_new(G_CHECKSUM_SHA256);
    m_builderCache.pDevinoToCsumcache = ostree_repo_devino_cache_new();
    m_builderCache.pHashUnusedStages = nullptr;
}

/**
 * @brief : 析构函数
 */
KMBuilderCache::~KMBuilderCache()
{
    if (m_builderCache.repo)
        g_clear_object(&m_builderCache.repo);

    if (m_builderCache.pChecksum)
        g_checksum_free(m_builderCache.pChecksum);

    if (m_builderCache.pHashUnusedStages)
        g_hash_table_unref(m_builderCache.pHashUnusedStages);

    if (m_builderCache.pDevinoToCsumcache)
        ostree_repo_devino_cache_unref(m_builderCache.pDevinoToCsumcache);
}

/**
 * @brief : 初始化
 * @return: true-成功; false-失败
 */
bool KMBuilderCache::init()
{
    if (!KMBuilderCCache::setCcache(builderContext.isCcache(), m_builderCache.ccacheDir))
        return false;

    if (builderContext.isForceClean())
    {
        disableCache();
    }

    if (!repoInit())
        return false;

    updateInitChecksum();

    return true;
}

/**
 * @brief : 仓库初始化
 * @return: true-成功; false-失败
 */
bool KMBuilderCache::repoInit()
{
    string cacheDir = m_builderCache.cacheDir;
    g_autoptr(GFile) pCacheFile = g_file_new_for_path(cacheDir.c_str());
    m_builderCache.repo = ostree_repo_new(pCacheFile);

    if (!fs::exists(cacheDir))
    {
        if (fs::create_directories(cacheDir) == false)
            return false;

        if (!ostree_repo_create(m_builderCache.repo, OSTREE_REPO_MODE_BARE_USER_ONLY, nullptr, nullptr))
            return false;
    }

    if (!ostree_repo_open(m_builderCache.repo, nullptr, nullptr))
        return false;

    g_autoptr(GKeyFile) pConfig = ostree_repo_copy_config(m_builderCache.repo);

    g_key_file_set_value(pConfig, "core", "min-free-space-percent", "0");

    if (!ostree_repo_write_config(m_builderCache.repo, pConfig, nullptr))
        return false;

    /* Re-open */
    g_clear_object(&m_builderCache.repo);
    m_builderCache.repo = ostree_repo_new(pCacheFile);
    if (!ostree_repo_open(m_builderCache.repo, nullptr, nullptr))
        return false;

    ostree_repo_set_disable_fsync(m_builderCache.repo, true);
    ostree_repo_set_ref_immediate(m_builderCache.repo, nullptr, m_builderCache.branch.c_str(), nullptr, nullptr, nullptr);

    //   /* List all stages first so we can purge unused ones at the end */
    if (!ostree_repo_list_refs(m_builderCache.repo, m_builderCache.branch.c_str(), &m_builderCache.pHashUnusedStages, nullptr, nullptr))
        return false;

    return true;
}

/**
 * @brief : 禁用缓存
 * @return : true-成功; false-失败
 */
void KMBuilderCache::disableCache()
{
    m_builderCache.isDisabled = true;
}

/**
 * @brief : 查找是否已处理
 * @param : stage: 阶段名称
 * @return : true-成功; false-失败
 */
bool KMBuilderCache::find(const string &stage)
{
    if (stage.empty())
        return false;

    m_builderCache.stage = stage;

    if (m_builderCache.pHashUnusedStages != nullptr)
        g_hash_table_remove(m_builderCache.pHashUnusedStages, m_builderCache.stage.c_str());

    m_builderCache.curChecksum = g_checksum_get_string(m_builderCache.pChecksum);

    /* Reset the checksum, but feed it previous checksum so we chain it */
    g_checksum_reset(m_builderCache.pChecksum);
    updateChecksumStr(m_builderCache.curChecksum);

    if (m_builderCache.isDisabled)
        return false;

    string ref;
    if (!getRef(ref))
        return false;

    do
    {
        g_autofree char *pCommit = nullptr;
        if (!ostree_repo_resolve_rev(m_builderCache.repo, ref.c_str(), true, &pCommit, nullptr))
            break;

        if (pCommit == nullptr)
            break;

        g_autoptr(GVariant) variant = nullptr;
        const gchar *subject;

        if (!ostree_repo_load_variant(m_builderCache.repo, OSTREE_OBJECT_TYPE_COMMIT, pCommit, &variant, nullptr))
            break;

        g_variant_get(variant, "(a{sv}aya(say)&s&stayay)", nullptr, nullptr, nullptr, &subject, nullptr, nullptr, nullptr, nullptr);

        if (g_strcmp0(subject, m_builderCache.curChecksum.c_str()) != 0)
            break;

        m_builderCache.lastParent = KMBuilderUtils::stealPointer(&pCommit);
        return true;

    } while (0);

    if (!m_builderCache.lastParent.empty() && !checkout(m_builderCache.lastParent, true))
        KMError("Failed to checkout cache:  " + m_builderCache.lastParent);

    /* Don't use cache any more after first miss */
    m_builderCache.isDisabled = true;

    return false;
}

/**
 * @brief : 获取ref命令
 * @param : 返回ref名称
 * @return : true-成功; false-失败
 */
bool KMBuilderCache::getRef(string &ref)
{
    if (m_builderCache.stage.empty() || m_builderCache.branch.empty())
        return false;

    ref = m_builderCache.branch + "/" + m_builderCache.stage;

    return true;
}

string KMBuilderCache::getRefStr(string stage)
{
    string ref = "";
    if (m_builderCache.branch.empty())
        return ref;

    ref = m_builderCache.branch + "/" + stage;
    return ref;
}

/**
 * @brief : 缓存目录checkout
 * @param : commit名
 * @param : isDelDir是否删除目录
 * @return : true-成功; false-失败
 */
bool KMBuilderCache::checkout(const string &commit, bool isDelDir)
{
    g_autoptr(GError) my_error = nullptr;
    OstreeRepoCheckoutAtOptions options{};

    if (isDelDir)
    {
        std::error_code ec;
        if (fs::exists(m_builderCache.appDir, ec))
            fs::remove_all(m_builderCache.appDir, ec);

        if (!fs::create_directories(m_builderCache.appDir, ec))
        {
            KMError("Failed to create cache dir:  " + m_builderCache.appDir);
            return false;
        }
    }

    if (builderContext.getIsDisableRofilesFuse())
        options.force_copy = true;

    options.mode = OSTREE_REPO_CHECKOUT_MODE_USER;
    options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES;
    options.devino_to_csum_cache = m_builderCache.pDevinoToCsumcache;

    // TODO appdir 路径是否正确，还是需要后面增加 kaiming-file-path
    if (!ostree_repo_checkout_at(m_builderCache.repo, &options, AT_FDCWD, m_builderCache.appDir.c_str(), commit.c_str(), nullptr, nullptr))
        return false;

    if (options.force_copy && !KMBuilderUtils::zeroMtime(AT_FDCWD, m_builderCache.appDir))
        return false;

    return true;
}

/**
 * @brief : 更新checksum
 */
void KMBuilderCache::updateChecksumStr(const string &str)
{
    if (str.empty())
        g_checksum_update(m_builderCache.pChecksum, (const guchar *)"\1", 1);
    else
        g_checksum_update(m_builderCache.pChecksum, (const guchar *)str.c_str(), str.length() + 1);
}

/**
 * @brief : 获取改变
 */
bool KMBuilderCache::getChanges(vector<string> &changes)
{
    g_autoptr(GFile) curRoot = nullptr;
    g_autoptr(GFile) parentRoot = nullptr;
    g_autoptr(GVariant) variant = nullptr;
    g_autoptr(GVariant) vCommitMetada = nullptr;
    g_autofree char *parent_commit = nullptr;
    g_autoptr(GVariant) vchangesz = nullptr;
    g_autoptr(GVariant) vchanges = nullptr;

    if (!ostree_repo_read_commit(m_builderCache.repo, m_builderCache.lastParent.empty() ? nullptr : m_builderCache.lastParent.c_str(), &curRoot, nullptr, nullptr, nullptr))
        return false;

    if (!ostree_repo_load_variant(m_builderCache.repo, OSTREE_OBJECT_TYPE_COMMIT, m_builderCache.lastParent.c_str(), &variant, nullptr))
        return false;

    vCommitMetada = g_variant_get_child_value(variant, 0);
    vchangesz = g_variant_lookup_value(vCommitMetada, "changesz", G_VARIANT_TYPE_BYTESTRING);

    if (vchangesz)
        vchanges = variantUncompress(vchangesz, G_VARIANT_TYPE("as"));
    else
        vchanges = g_variant_lookup_value(vCommitMetada, "changes", G_VARIANT_TYPE("as"));

    if (vchanges)
    {
        for (int i = 0; i < g_variant_n_children(vchanges); i++)
        {
            char *str = nullptr;
            g_variant_get_child(vchanges, i, "s", &str);
            changes.emplace_back(str);
        }

        return true;
    }

    parent_commit = ostree_commit_get_parent(variant);
    if (parent_commit != nullptr && !ostree_repo_read_commit(m_builderCache.repo, parent_commit, &parentRoot, nullptr, nullptr, nullptr))
        return false;

    vector<string> removeChanges;
    if (doGetChanges(parentRoot, curRoot, changes, removeChanges))
        return false;

    return true;
}

/* This returns removals too */
vector<string> KMBuilderCache::getAllChanges(GFile *from, GFile *to)
{
    vector<string> changes;
    int i;
    g_autoptr(GPtrArray) added = g_ptr_array_new_with_free_func(g_object_unref);
    g_autoptr(GPtrArray) modified = g_ptr_array_new_with_free_func((GDestroyNotify)ostree_diff_item_unref);
    g_autoptr(GPtrArray) removed = g_ptr_array_new_with_free_func(g_object_unref);

    if (!ostree_diff_dirs(OSTREE_DIFF_FLAGS_NONE, from, to, modified, removed, added, nullptr, nullptr))
        return changes;

    for (int i = 0; i < added->len; i++)
    {
        char *path = g_file_get_relative_path(to, static_cast<GFile *>(g_ptr_array_index(added, i)));
        if (path != nullptr)
            changes.emplace_back(path);
    }

    for (int i = 0; i < modified->len; i++)
    {
        OstreeDiffItem *modified_item = static_cast<OstreeDiffItem *>(g_ptr_array_index(modified, i));
        char *path = g_file_get_relative_path(to, modified_item->target);
        if (path != nullptr)
        {
            changes.emplace_back(path);
        }
    }

    for (int i = 0; i < removed->len; i++)
    {
        char *path = g_file_get_relative_path(to, static_cast<GFile *>(g_ptr_array_index(removed, i)));
        if (path != nullptr)
        {
            changes.emplace_back(path);
        }
    }
    return changes;
}

/* This returns removals too */
vector<string> KMBuilderCache::cacheGetAllChanges()
{
    vector<string> ret;
    g_autoptr(GFile) initRoot = NULL;
    g_autoptr(GFile) finishRoot = NULL;
    g_autofree char *initCommit = NULL;
    g_autofree char *finishCommit = NULL;
    string initRef = getRefStr("init");
    string finishRef = getRefStr("finish");

    if (!ostree_repo_resolve_rev(m_builderCache.repo, initRef.c_str(), false, &initCommit, nullptr))
        return ret;

    if (!ostree_repo_resolve_rev(m_builderCache.repo, finishRef.c_str(), false, &finishCommit, nullptr))
        return ret;

    if (!ostree_repo_read_commit(m_builderCache.repo, initCommit, &initRoot, nullptr, nullptr, nullptr))
        return ret;

    if (!ostree_repo_read_commit(m_builderCache.repo, finishCommit, &finishRoot, nullptr, nullptr, nullptr))
        return ret;

    return getAllChanges(initRoot, finishRoot);
}

/**
 * @brief : commit 提交
 * @param : body 提交的内容
 * @return : true-成功; false-失败
 */
bool KMBuilderCache::commit(const string &body)
{
    if (body.empty())
        return false;
    if (!KMBuilderUtils::zeroMtime(AT_FDCWD, m_builderCache.appDir))
        return false;
    if (!ostree_repo_prepare_transaction(m_builderCache.repo, nullptr, nullptr, nullptr))
        return false;
    g_autoptr(OstreeMutableTree) pMutree = ostree_mutable_tree_new();
    OstreeRepoCommitModifier *pcomitModifier = ostree_repo_commit_modifier_new(
      OSTREE_REPO_COMMIT_MODIFIER_FLAGS_SKIP_XATTRS,
      [](OstreeRepo *repo, const char *path, GFileInfo *file_info, gpointer commit_data)
      {
          g_file_info_set_attribute_uint32(file_info, "unix::uid", 0);
          g_file_info_set_attribute_uint32(file_info, "unix::gid", 0);
          guint mode = g_file_info_get_attribute_uint32(file_info, "unix::mode");
          if (g_file_info_get_file_type(file_info) == G_FILE_TYPE_DIRECTORY)
              mode = 0755 | S_IFDIR;
          else if (g_file_info_get_file_type(file_info) == G_FILE_TYPE_REGULAR)
          {
              if (mode & S_IXUSR)
                  mode = 0755 | S_IFREG;
              else
                  mode = 0644 | S_IFREG;
          }
          g_file_info_set_attribute_uint32(file_info, "unix::mode", mode);
          return OSTREE_REPO_COMMIT_FILTER_ALLOW;
      },
      nullptr,
      nullptr);

    bool ret = false;
    do
    {
        if (m_builderCache.pDevinoToCsumcache)
            ostree_repo_commit_modifier_set_devino_cache(pcomitModifier, m_builderCache.pDevinoToCsumcache);

        if (!ostree_repo_write_directory_to_mtree(m_builderCache.repo, g_file_new_for_path(m_builderCache.appDir.c_str()), pMutree, pcomitModifier, nullptr, nullptr))
            break;

        g_autoptr(GFile) root = nullptr;
        if (!ostree_repo_write_mtree(m_builderCache.repo, pMutree, &root, nullptr, nullptr))
            break;

        g_autoptr(GFile) parentRoot = nullptr;
        if (!m_builderCache.lastParent.empty() && !ostree_repo_read_commit(m_builderCache.repo, m_builderCache.lastParent.c_str(), &parentRoot, nullptr, nullptr, nullptr))
            break;

        vector<string> changes, removeChanges;
        if (!doGetChanges(parentRoot, root, changes, removeChanges))
            break;

        vector<const char *> pvChanges, pvRemoveChanges;
        for (auto &str : changes)
        {
            pvChanges.emplace_back(str.c_str());
        }
        for (auto &str : removeChanges)
        {
            pvRemoveChanges.emplace_back(str.c_str());
        }

        g_autoptr(GVariantDict) pMmetadataDict = g_variant_dict_new(nullptr);
        g_autoptr(GVariant) pChangesv = g_variant_ref_sink(g_variant_new_strv((const char *const *)pvChanges.data(), pvChanges.size()));
        g_autoptr(GVariant) pChangesvz = variantCompress(pChangesv);
        g_variant_dict_insert_value(pMmetadataDict, "changesz", pChangesvz);

        g_autoptr(GVariant) pRemovalsv = g_variant_ref_sink(g_variant_new_strv((const char *const *)pvRemoveChanges.data(), pvRemoveChanges.size()));
        g_autoptr(GVariant) pRemovalsvz = variantCompress(pRemovalsv);
        g_variant_dict_insert_value(pMmetadataDict, "removalsz", pRemovalsvz);

        g_autoptr(GVariant) pMetadata = g_variant_ref_sink(g_variant_dict_end(pMmetadataDict));

        const char *pLastParent = m_builderCache.lastParent.empty() ? nullptr : m_builderCache.lastParent.c_str();

        g_autofree char *pCommitChecksum = nullptr;
        if (!ostree_repo_write_commit(m_builderCache.repo, pLastParent, m_builderCache.curChecksum.c_str(), body.c_str(), pMetadata, OSTREE_REPO_FILE(root), &pCommitChecksum, nullptr, nullptr))
            break;

        string ref;
        if (!getRef(ref))
            break;

        ostree_repo_transaction_set_ref(m_builderCache.repo, nullptr, ref.c_str(), pCommitChecksum);

        g_autoptr(GFile) pLastroot = nullptr;
        if (!m_builderCache.lastParent.empty() && !ostree_repo_read_commit(m_builderCache.repo, pLastParent, &pLastroot, nullptr, nullptr, nullptr))
            break;

        if (!mtreePruneOldfiles(pMutree, OSTREE_REPO_FILE(pLastroot)))
            break;

        g_autoptr(GFile) pNewroot = nullptr;
        if (!ostree_repo_write_mtree(m_builderCache.repo, pMutree, &pNewroot, nullptr, nullptr))
            break;

        g_autofree char *pNewCommitChecksum = nullptr;
        if (!ostree_repo_write_commit(m_builderCache.repo, nullptr, m_builderCache.curChecksum.c_str(), body.c_str(), pMetadata, OSTREE_REPO_FILE(pNewroot), &pNewCommitChecksum, nullptr, nullptr))
            break;

        if (!ostree_repo_commit_transaction(m_builderCache.repo, nullptr, nullptr, nullptr))
            break;

        if (!checkout(pNewCommitChecksum, false))
            break;

        m_builderCache.lastParent = KMBuilderUtils::stealPointer(&pCommitChecksum);

        ret = true;

    } while (0);

    if (pcomitModifier)
    {
        ostree_repo_commit_modifier_unref(pcomitModifier);
    }

    return ret;
}

/**
 * @brief : 获取缓存结构
 * @return : 缓存结构
 */
BuilderCache KMBuilderCache::getBuilderCache()
{
    return m_builderCache;
}

/**
 * @brief : 更新checksum
 */
void KMBuilderCache::updateInitChecksum()
{
    updateChecksumStr(CONFIG_CHECKSUM_VERSION);
    shared_ptr<KMBuilderConfig> pBuildconfig = builderContext.getBuilderConfig();
    if (pBuildconfig == nullptr)
        return;

    if (pBuildconfig->getAttribute("id").has_value())
        updateChecksumStr(any_cast<string>(pBuildconfig->getAttribute("id")));
    if (pBuildconfig->getAttribute("runtime").has_value())
        updateChecksumStr(any_cast<string>(pBuildconfig->getAttribute("runtime")));
    if (pBuildconfig->getAttribute("runtime-version").has_value())
    {
        string runtimeVersion = any_cast<string>(pBuildconfig->getAttribute("runtime-version"));
        updateChecksumStr(runtimeVersion.empty() ? DEFAULT_BRANCH : runtimeVersion);
    }
    if (pBuildconfig->getAttribute("sdk").has_value())
        updateChecksumStr(any_cast<string>(pBuildconfig->getAttribute("sdk")));

    bool isWritableSdk, isBuildRuntime;
    if (pBuildconfig->getAttribute("writable-sdk").has_value())
        isWritableSdk = any_cast<bool>(pBuildconfig->getAttribute("writable-sdk"));
    if (pBuildconfig->getAttribute("build-runtime").has_value())
        isBuildRuntime = any_cast<bool>(pBuildconfig->getAttribute("build-runtime"));

    if (isWritableSdk || isBuildRuntime)
    {
        if (pBuildconfig->getAttribute("sdk-commit").has_value())
            updateChecksumStr(any_cast<string>(pBuildconfig->getAttribute("sdk-commit")));
    }

    if (pBuildconfig->getAttribute("var").has_value())
        updateChecksumStr(any_cast<string>(pBuildconfig->getAttribute("var")));

    vector<string> tags;
    if (pBuildconfig->getAttribute("tags").has_value())
        updateChecksumStrVec(any_cast<vector<string>>(pBuildconfig->getAttribute("tags")));

    if (pBuildconfig->getAttribute("metadata").has_value())
        updateChecksumStr(any_cast<string>(pBuildconfig->getAttribute("metadata")));

    updateChecksumBool(isWritableSdk);
    updateChecksumStrVec(pBuildconfig->getConfigData()->sdkExtensions);
    updateChecksumBool(isBuildRuntime);
    if (pBuildconfig->getAttribute("build-extension").has_value())
    {
        updateChecksumBool(any_cast<bool>(pBuildconfig->getAttribute("build-extension")));
    }

    string base, baseVersion;
    if (pBuildconfig->getAttribute("base").has_value())
        updateChecksumStr(any_cast<string>(pBuildconfig->getAttribute("base")));
    if (pBuildconfig->getAttribute("base-version").has_value())
        updateChecksumStr(any_cast<string>(pBuildconfig->getAttribute("base-version")));
    if (pBuildconfig->getAttribute("separate-locales").has_value())
        updateChecksumBool(any_cast<bool>(pBuildconfig->getAttribute("separate-locales")));
    if (pBuildconfig->getAttribute("base-commit").has_value())
        updateChecksumStr(any_cast<string>(pBuildconfig->getAttribute("base-commit")));
    if (pBuildconfig->getAttribute("base-extensions").has_value())
        updateChecksumStrVec(any_cast<vector<string>>(pBuildconfig->getAttribute("base-extensions")));
    if (pBuildconfig->getAttribute("extension-tag").has_value())
        updateChecksumStr(any_cast<string>(pBuildconfig->getAttribute("extension-tag")));

    updateBuildOptChecksum(pBuildconfig->getConfigData()->buildOptions);

    for (auto [k, v] : pBuildconfig->getConfigData()->addBuildExtensions)
    {
        updateBuildExtChecksum(k, v);
    }
}

void KMBuilderCache::updateChecksumFinish()
{
    shared_ptr<ConfigData> pConfigData = builderContext.getBuilderConfig()->getConfigData();
    updateChecksumStr(CONFIG_CHECKSUM_FINISH_VERSION);
    updateChecksumStrVec(pConfigData->finishArgs);
    // updateChecksumStr(pConfigData->command);  (被整合map中了)
    updateChecksumStrVec(pConfigData->inheritExtensions);
    updateChecksumStrVecCompat(pConfigData->inheritSdkExtensions);
    for (auto [k, v] : pConfigData->addExtensions)
    {
        updateBuildExtChecksum(k, v);
    }
}

void KMBuilderCache::updateChecksumFinishJson(const string &json)
{
    updateChecksumStr(json);
}

void KMBuilderCache::updateChecksumcleanup()
{
    GList *l;
    shared_ptr<ConfigData> pConfigData = builderContext.getBuilderConfig()->getConfigData();
    updateChecksumStr(CONFIG_CHECKSUM_CLEANUP_VERSION);
    updateChecksumStrVec(pConfigData->cleanup);
    updateChecksumStrVec(pConfigData->cleanupCommands);

    map<string, any> attributeMap = pConfigData->attributeMap;
    updateChecksumStr(std::any_cast<const string &>(attributeMap["rename-desktop-file"]));
    updateChecksumStr(std::any_cast<const string &>(attributeMap["rename-appdata-file"]));
    updateChecksumStr(std::any_cast<const string &>(attributeMap["appdata-license"]));
    updateChecksumStr(std::any_cast<const string &>(attributeMap["rename-icon"]));
    updateChecksumBool(std::any_cast<const bool &>(attributeMap["copy-icon"]));
    updateChecksumStr(std::any_cast<const string &>(attributeMap["desktop-file-name-prefix"]));
    updateChecksumStr(std::any_cast<const string &>(attributeMap["desktop-file-name-suffix"]));
    updateChecksumBool(std::any_cast<const bool &>(attributeMap["appstream-compose"]));

    shared_ptr<KMBuilderConfig> pBuilderConfig = builderContext.getBuilderConfig();
    vector<shared_ptr<KMBuilderModule>> vBuilderModule = pBuilderConfig->getModules();
    for (auto pBuilderModule : vBuilderModule)
    {
        shared_ptr<BuilderModuleData> pBuilderModuleData = pBuilderModule->getData();
        ChecksumForCleanup(pBuilderModuleData);
    }
}

void KMBuilderCache::ChecksumForCleanup(shared_ptr<BuilderModuleData> self)
{
    updateChecksumStr(MODULE_CHECKSUM_VERSION);
    updateChecksumStr(self->m_name);
    updateChecksumStrVec(self->cleanup);
}

void KMBuilderCache::ChecksumForPlatformBase()
{
    shared_ptr<ConfigData> pConfigData = builderContext.getBuilderConfig()->getConfigData();
    string metadata_platform = std::any_cast<const string &>(pConfigData->attributeMap["metadata-platform"]);
    updateChecksumStr(CONFIG_CHECKSUM_PLATFORM_VERSION);
    updateChecksumStr((std::any_cast<const string &>(pConfigData->attributeMap["id-platform"])));
    updateChecksumStr((std::any_cast<const string &>(pConfigData->attributeMap["runtime-commit"])));
    updateChecksumStrVec(pConfigData->platformExtensions);
    updateChecksumStr(metadata_platform);

    if (!metadata_platform.empty())
    {
        string baseDir = builderContext.getBaseDir();
        string metadata = baseDir + "/" + metadata_platform;
        string data = "";

        data = KMBuilderUtils::fileGetContents(metadata);
        if (!data.empty())
        {
            updateChecksumStr(data);
        }
        else
        {
            KMWarn("Can't load metadata-platform file " + metadata);
        }
    }
}

void KMBuilderCache::checksumForModulesCleanPlatform(const std::vector<std::shared_ptr<KMBuilderModule>> &modules)
{
    for (auto &module : modules)
    {
        updateChecksumStrVec(module->cleanupPlatform());
        const std::vector<std::shared_ptr<KMBuilderModule>> &submodules = module->builderModules();
        if (!submodules.empty())
        {
            checksumForModulesCleanPlatform(submodules);
        }
    }
}

void KMBuilderCache::ChecksumForPlatformPrepare()
{
    std::shared_ptr<ConfigData> pConfigData = builderContext.getBuilderConfig()->getConfigData();
    updateChecksumStrVec(pConfigData->preparePlatformCommands);
    updateChecksumStrVec(pConfigData->cleanupPlatform);

    std::shared_ptr<KMBuilderConfig> pBuilderConfig = builderContext.getBuilderConfig();
    if (!pBuilderConfig)
        return;
    const std::vector<std::shared_ptr<KMBuilderModule>> &modules = pBuilderConfig->getModules();
    if (!modules.empty())
    {
        checksumForModulesCleanPlatform(modules);
    }
}

void KMBuilderCache::ChecksumForPlatformFinish()
{
    shared_ptr<ConfigData> pConfigData = builderContext.getBuilderConfig()->getConfigData();
    updateChecksumStrVec(pConfigData->cleanupPlatformCommands);
}

void KMBuilderCache::ChecksumForBundleSources()
{
    shared_ptr<ConfigData> pConfigData = builderContext.getBuilderConfig()->getConfigData();
    bool isBundleSources = builderContext.getIsBundleSources();
    updateChecksumStr(CONFIG_CHECKSUM_BUNDLE_SOURCES_VERSION);
    updateChecksumBool(isBundleSources);
}

/**
 * @brief : 更新buidextension
 * @param : buidextension 结构体
 */
void KMBuilderCache::updateBuildExtChecksum(const string &name, const shared_ptr<BuilderExtension> &ext)
{
    updateChecksumStr("5");
    updateChecksumStr(name);
    updateChecksumStr(ext->directory);
    updateChecksumBool(ext->bundle);
    updateChecksumBool(ext->autodelete);
    updateChecksumBool(ext->noAutoDownload);
    updateChecksumBool(ext->localeSubset);
    updateChecksumBool(ext->subdirectories);
    updateChecksumStr(ext->addLdPath);
    updateChecksumStr(ext->downloadIf);
    updateChecksumStr(ext->enableIf);
    updateChecksumStrList(ext->mergeDirs);
    updateChecksumStr(ext->subdirectorySuffix);
    updateChecksumStr(ext->version);
    updateChecksumStrList(ext->versions);
    updateChecksumBool(ext->removeAfterBuild);
}

/**
 * @brief : 更新buidoptions
 * @param : KMBuildOptions 结构体
 */
void KMBuilderCache::updateBuildOptChecksum(shared_ptr<KMBuildOptions> &opt)
{
    updateChecksumStr("1");
    updateChecksumStr(opt->m_cFlags);
    updateChecksumBool(opt->m_cflagsOverride);
    updateChecksumStr(opt->m_cxxFlags);
    updateChecksumBool(opt->m_cxxFlagsOverride);
    updateChecksumStr(opt->m_cppFlags);
    updateChecksumBool(opt->m_cppFlagsOverride);
    updateChecksumStr(opt->m_ldFlags);
    updateChecksumBool(opt->m_ldFlagsOverride);
    updateChecksumStr(opt->m_prefix);
    updateChecksumStr(opt->m_libDir);

    for (auto [k, v] : opt->m_env)
    {
        updateChecksumStr(v);
    }

    for (auto [k, v] : opt->m_secretEnv)
    {
        updateChecksumStr(v);
    }

    updateChecksumStrVec(opt->m_buildArgs);
    updateChecksumStrVec(opt->m_testArgs);
    updateChecksumStrVec(opt->m_configOpts);
    updateChecksumStrVec(opt->m_secretOpts);
    updateChecksumStrVec(opt->m_makeArgs);
    updateChecksumStrVec(opt->m_makeInstallArgs);
    updateChecksumBool(opt->m_strip);
    updateChecksumBool(opt->m_noDebuginfo);
    updateChecksumBool(opt->m_noDebuginfoCompression);
    updateChecksumStr(opt->m_appendPath);
    updateChecksumStr(opt->m_prependPath);
    updateChecksumStr(opt->m_appendLdLibraryPath);
    updateChecksumStr(opt->m_prependLdLibraryPath);
    updateChecksumStr(opt->m_appendPkgConfigPath);
    updateChecksumStr(opt->m_prependPkgConfigPath);
}

/**
 * @brief : 更新module checksum
 * @return : true-成功; false-失败
 */
void KMBuilderCache::updataModuleChecksum(shared_ptr<KMBuilderModule> &module)
{
    updateChecksumStr("1");
    updateChecksumStr(module->getData()->m_name);
    updateChecksumStr(module->getData()->m_subdir);
    updateChecksumStrVec(module->getData()->m_postInstall);
    updateChecksumStrVec(module->getData()->m_configOpts);
    updateChecksumStrVec(module->getData()->m_secretOpts);
    updateChecksumStrVec(module->getData()->m_makeArgs);
    updateChecksumStrVec(module->getData()->m_makeInstallArgs);
    updateChecksumStrVec(module->getData()->m_ensureWritable);
    updateChecksumStrVec(module->getData()->m_onlyArches);
    updateChecksumStrVec(module->getData()->m_skipArches);
    updateChecksumBool(module->getData()->m_rmConfigure);
    updateChecksumBool(module->getData()->m_noAutogen);
    updateChecksumBool(module->getData()->m_disabled);
    updateChecksumBool(module->getData()->m_noParallelMake);
    updateChecksumBool(module->getData()->m_noMakeInstall);
    updateChecksumBool(module->getData()->m_noPythonTimestampFix);
    updateChecksumBool(module->getData()->m_cmake);
    updateChecksumBool(module->getData()->m_buildDir);
    updateChecksumStrVec(module->getData()->m_buildCommands);
    updateChecksumStr(module->getData()->m_buildSystem);
    updateChecksumStr(module->getData()->m_installRule);
    updateChecksumBool(module->getData()->m_runTests);

    // if (self->buildOptions)
    //     updateBuildOptChecksum(self->buildOptions, cache, context);

    for (auto source : module->getData()->m_sources)
    {
        if (!source->isEnable())
            continue;

        updateChecksumStr(source->getDest());
        updateChecksumStrVec(source->getOnlyArches());
        updateChecksumStrVec(source->getSkipArches());

        // TODO builder是否需要提供checksum接口
    }
}

/**
 * @brief : 更新checksum bool
 * @param : boolvalue 布尔值
 */
void KMBuilderCache::updateChecksumBool(bool boolvalue)
{
    if (boolvalue)
    {
        g_checksum_update(m_builderCache.pChecksum, (const guchar *)"\1", 1);
    }
    else
    {
        g_checksum_update(m_builderCache.pChecksum, (const guchar *)"\0", 1);
    }
}

/**
 * @brief : 更新checksum字符串数组
 * @param : vec 字符串数组
 */
void KMBuilderCache::updateChecksumStrVec(const vector<string> &vec)
{
    if (vec.empty())
    {
        g_checksum_update(m_builderCache.pChecksum, (const guchar *)"\2", 1);
    }
    else
    {
        g_checksum_update(m_builderCache.pChecksum, (const guchar *)"\1", 1);
        for (auto &str : vec)
        {
            updateChecksumStr(str);
        }
    }
}

/**
 * @brief : 更新checksum字符串链表
 * @param : vec 字符串数组
 */
void KMBuilderCache::updateChecksumStrList(const list<string> &l)
{
    if (l.empty())
    {
        g_checksum_update(m_builderCache.pChecksum, (const guchar *)"\2", 1);
    }
    else
    {
        g_checksum_update(m_builderCache.pChecksum, (const guchar *)"\1", 1);
        for (auto &str : l)
        {
            updateChecksumStr(str);
        }
    }
}

/**
 * @brief : 不为空时更新checksum字符串数组
 * @param : vec 字符串数组
 */
void KMBuilderCache::updateChecksumStrVecCompat(const vector<string> &vec)
{
    if (vec.size())
    {
        updateChecksumStrVec(vec);
    }
}

/**
 * @brief : variant 解压
 * @param : variant
 * @param : type 类型
 * @return : 解压后的variant
 */
GVariant *KMBuilderCache::variantUncompress(GVariant *variant, const GVariantType *type)
{
    g_autoptr(GInputStream) input_stream = nullptr;
    g_autoptr(GZlibDecompressor) decompressor = nullptr;
    g_autoptr(GInputStream) converter = nullptr;
    g_autoptr(GBytes) decompressedBytes = nullptr;
    const guint8 *compressed;
    gsize compressedSize;

    g_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_BYTESTRING));

    compressed = static_cast<const guint8 *>(g_variant_get_data(variant));
    compressedSize = g_variant_get_size(variant);

    input_stream = g_memory_input_stream_new_from_data(compressed, compressedSize, nullptr);
    decompressor = g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP);
    converter = g_converter_input_stream_new(G_INPUT_STREAM(input_stream), G_CONVERTER(decompressor));
    decompressedBytes = readStream(converter, false);
    return g_variant_ref_sink(g_variant_new_from_bytes(type, decompressedBytes, true));
}

/**
 * @brief : variant 压缩
 * @param : variant
 * @return : 压缩后的variant
 */
GVariant *KMBuilderCache::variantCompress(GVariant *variant)
{
    g_autoptr(GInputStream) inputstream = nullptr;
    g_autoptr(GZlibCompressor) compressor = nullptr;
    g_autoptr(GInputStream) converter = nullptr;
    g_autoptr(GBytes) compressedBytes = nullptr;

    const guint8 *decompressed = static_cast<const guint8 *>(g_variant_get_data(variant));
    gsize decompressedSize = g_variant_get_size(variant);

    inputstream = g_memory_input_stream_new_from_data(decompressed, decompressedSize, nullptr);
    compressor = g_zlib_compressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1);
    converter = g_converter_input_stream_new(G_INPUT_STREAM(inputstream), G_CONVERTER(compressor));
    compressedBytes = readStream(converter, false);

    return g_variant_ref_sink(g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, compressedBytes, true));
}

/**
 * @brief : 读取文件流
 * @param : in 输入流
 * @param : null_terminate 是否以空字符结尾
 * @return : 返回字节流
 */
GBytes *KMBuilderCache::readStream(GInputStream *in, bool nullterminate)
{
    g_autoptr(GOutputStream) outstream = g_memory_output_stream_new_resizable();
    if (g_output_stream_splice(outstream, in, static_cast<GOutputStreamSpliceFlags>(0), nullptr, nullptr) < 0)
        return nullptr;

    if (nullterminate && !g_output_stream_write(G_OUTPUT_STREAM(outstream), "\0", 1, nullptr, nullptr))
        return nullptr;

    if (!g_output_stream_close(G_OUTPUT_STREAM(outstream), nullptr, nullptr))
        return nullptr;

    return g_memory_output_stream_steal_as_bytes(G_MEMORY_OUTPUT_STREAM(outstream));
}

/**
 * @brief : 获取改变
 * @param : from 源目录
 * @param : to 目标目录
 * @param : changes 改变
 * @param : removeChanges 删除改变
 * @return : true-成功; false-失败
 */
bool KMBuilderCache::doGetChanges(GFile *from, GFile *to, vector<string> &changes, vector<string> &removeChanges)
{
    g_autoptr(GPtrArray) added = g_ptr_array_new_with_free_func(g_object_unref);
    g_autoptr(GPtrArray) modified = g_ptr_array_new_with_free_func((GDestroyNotify)ostree_diff_item_unref);
    g_autoptr(GPtrArray) removed = g_ptr_array_new_with_free_func(g_object_unref);

    if (!ostree_diff_dirs(OSTREE_DIFF_FLAGS_NONE, from, to, modified, removed, added, nullptr, nullptr))
        return false;

    for (int i = 0; i < added->len; i++)
    {
        char *path = g_file_get_relative_path(to, static_cast<GFile *>(g_ptr_array_index(added, i)));
        if (path != nullptr)
        {
            changes.emplace_back(path);
        }
    }

    for (int i = 0; i < modified->len; i++)
    {
        OstreeDiffItem *modified_item = static_cast<OstreeDiffItem *>(g_ptr_array_index(modified, i));
        char *path = g_file_get_relative_path(to, modified_item->target);
        if (path != nullptr)
        {
            changes.emplace_back(path);
        }
    }

    for (int i = 0; i < removed->len; i++)
    {
        char *path = g_file_get_relative_path(to, static_cast<GFile *>(g_ptr_array_index(removed, i)));
        if (path != nullptr)
        {
            removeChanges.emplace_back(path);
        }
    }

    return true;
}

/**
 * @brief : 可变树裁剪旧文件
 * @param : mtree 可变树
 * @param : old 旧文件
 * @return : true-成功; false-失败
 */
bool KMBuilderCache::mtreePruneOldfiles(OstreeMutableTree *mtree, OstreeRepoFile *old)
{
    GHashTable *files = ostree_mutable_tree_get_files(mtree);
    GHashTable *subdirs = ostree_mutable_tree_get_subdirs(mtree);
    GHashTableIter iter;
    gpointer key, value;

    ostree_mutable_tree_set_contents_checksum(mtree, nullptr);
    if (old != nullptr && !ostree_repo_file_ensure_resolved(old, nullptr))
        return false;

    g_hash_table_iter_init(&iter, files);
    while (g_hash_table_iter_next(&iter, &key, &value))
    {
        string name = static_cast<char *>(key);
        string csum = static_cast<char *>(value);
        gboolean isDir = false;
        gboolean isSame = false;
        g_autoptr(GVariant) container = nullptr;
        int n = -1;

        if (old)
            n = ostree_repo_file_tree_find_child(old, name.c_str(), &isDir, &container);

        if (n >= 0 && !isDir)
        {
            g_autoptr(GVariant) lodCsumBytes = nullptr;
            g_variant_get_child(container, n, "(@s@ay)", nullptr, &lodCsumBytes);
            string oldcsum = ostree_checksum_from_bytes_v(lodCsumBytes);

            if (oldcsum == csum)
                isSame = true;
        }

        if (isSame)
            g_hash_table_iter_remove(&iter);
    }

    g_hash_table_iter_init(&iter, subdirs);
    while (g_hash_table_iter_next(&iter, &key, &value))
    {
        string name = static_cast<char *>(key);
        OstreeMutableTree *subdir = static_cast<OstreeMutableTree *>(value);
        g_autoptr(GFile) oldSubdir = nullptr;
        int n = -1;
        gboolean isDir = false;

        if (old)
            n = ostree_repo_file_tree_find_child(old, name.c_str(), &isDir, nullptr);

        if (n >= 0 && isDir)
            oldSubdir = g_file_get_child(G_FILE(old), name.c_str());

        if (!mtreePruneOldfiles(subdir, OSTREE_REPO_FILE(oldSubdir)))
            return false;

        GHashTable *files = ostree_mutable_tree_get_files(subdir);
        GHashTable *subdirs = ostree_mutable_tree_get_subdirs(subdir);

        if (g_hash_table_size(files) == 0 && g_hash_table_size(subdirs) == 0)
            g_hash_table_iter_remove(&iter);
    }

    return true;
}

/**
 * @brief : 获取标准输出改变
 * @param : changes 输出改变
 * @return : true-成功; false-失败
 */
bool KMBuilderCache::getOutstandChanges(vector<string> &changes)
{
    KMBuilderUtils::getFilesInDir(m_builderCache.appDir, changes);
    return true;
}

void KMBuilderCache::ensureCheckout()
{
    if (m_builderCache.isDisabled)
    {
        return;
    }

    if (!m_builderCache.lastParent.empty())
    {
        km_logger.info("Everything cached, checking out from cache");
        if (!checkout(m_builderCache.lastParent, true))
        {
            km_logger.info("Failed to check out cache");
        }
    }
    m_builderCache.isDisabled = true;
}
