/*
 * Copyright (c) KylinSoft  Co., Ltd. 2024. All rights reserved.
 *
 * kaiming is licensed under the GPL v2.0+.
 * 
 * See the LICENSE file for more details.
 */

#ifndef __KMFILEUTILS_H__
#define __KMFILEUTILS_H__

#include <string>
#include <filesystem>
#include <nlohmann/json.hpp>
#include <fstream>
#include <set>

#include "KMStringUtils.h"
#include "KMFileGuard.h"

using json = nlohmann::json;
namespace fs = std::filesystem;

class KMTmpfile
{
public:
    KMTmpfile();
    ~KMTmpfile();

    void init()
    {
        initialized = false;
        anonymous = false;
        srcDfd = -1;
        fg.setFd(-1);
        path = "";
    }

    int stealFd()
    {
        int fd = fg.stealFd();
        init();
        return fd;
    }

    int fd() { return fg.fd(); }

    bool initialized = false;
    bool anonymous = false;
    int srcDfd = -1;
    KMFileGuard fg{-1};
    std::string path;
};

typedef enum
{
    FILE_REPLACE_DATASYNC_NEW = (1 << 0),
    FILE_REPLACE_NODATASYNC = (1 << 1),
    FILE_REPLACE_INCREASING_MTIME = (1 << 2),
} FileReplaceFlags;

typedef enum
{
    LINK_TMPFILE_REPLACE,
    LINK_TMPFILE_NOREPLACE,
    LINK_TMPFILE_NOREPLACE_IGNORE_EXIST
} LinkTmpfileReplaceMode;

class KMFileUtils
{
public:
    /**
     * @brief 获取文件时间戳
     * 
     * @param filePath 文件路径
     * @return int 文件时间戳
     */
    static int getFileTimestamp(const std::string &filePath);

    /**
     * @brief 判断文件是否过期
     * 
     * 比较当前时间戳和文件时间戳大小，如果当前时间戳大于文件时间戳，并且时间差大于1小时，则返回true，否则返回false
     * 
     * @param filePath 文件路径
     * @param expiredTime 过期时间，单位为秒
     * @return true 文件过期
     * @return false 文件未过期
     */
    static bool isFileExpired(const std::string &filePath, int expiredTime);

    /**
     * @brief 判断文件是否是 json 文件
     * 
     * @param filePath 文件路径
     * @return true 是 json 文件
     * @return false 不是 json 文件
     */
    static bool isJsonFile(const std::string &filePath);

    /**
     * @brief 判断文件是否存在
     * 
     * @param fileName 文件名
     * @return true 文件存在
     * @return false 文件不存在
     */
    static bool fileExists(const std::string &fileName);

    /**
     * @brief 获取当前时间的函数
     * 
     * @return std::string 
     */
    static std::string getCurrentTime();

    /**
     * @brief 创建文件或者清空已有文件并写入当前时间
     * 
     * @param fileName 
     * @param fileName 文件名
     * @return true 文件操作成功
     * @return false 文件操作失败
     */
    static bool createFileIfNotExist(const std::string &fileName);

    /**
     * isEmptyDirectory:
     * @brief : 判断路径是否空目录，使用前先判断是否是目录
     * @param : [in] path，路径
     * @return: true-空目录或不存在; false-不是空目录或不是目录
     */
    static bool isEmptyDirectory(const std::string &path);

    /**
     * @brief: 递归查询目录是否为空
     * @param: [in] path, 路径
     * @return: true-空目录或不存在; false-不是空目录或不是目录
     */
    static bool isEmptyDir(const std::string &path);

    /**
     * removeEmptyDirectory:
     * @brief : 递归删除空子目录
     * @param : [in] path，路径
     * @return: true-操作成功; false-操作失败
     */
    static bool removeEmptyDirectory(const std::string &path, std::error_code &ec);

    /**
     * @brief : 目录或文件是否存在
     * @param : [in] path, 待目录或文件
     * @return : true-存在；false-不存在
     */
    static bool pathExists(const std::string &path);

    /**
     * @brief : 获取文件或目录中文件大小
     */
    static uint64_t getDirectorySize(const fs::path &dir);

    /**
     * @brief : 递归创建目录
     * @param : [in] path, 目录
     * @param : [out] std::error_code，保存错误信息
     * @return : true-存在；false-不存在
     */
    static bool mkpath(const std::string &path, std::error_code &ec);
    static bool mkpath(const std::string &path);
    static bool mkpath(const std::string &path, unsigned int mode);

    /**
     * @brief : 递归删除目录或文件
     * @param : [in] path, 待目录或文件
     * @param : [out] std::error_code，保存错误信息
     * @return : true-成功；false-失败
     */
    static bool removeAll(const std::string &path, std::error_code &ec);
    static bool removeAll(const std::string &path);

    /**
     * @brief : 强制递归删除目录或文件
     * @param : [in] path, 待目录或文件
     * @return : true-成功；false-失败
     * @note  : 有些场景KMFileUtils::removeAll有无法删除的目录，使用KMFileUtils::rmRfAll可以删除。如overlay中的workdir
     */
    static bool rmRfAll(const std::string &path);

    /**
     * @brief : 获取路径的真实路径
     * @param : [in] path，路径
     * @return: 真实路径,软链接会替换为其连接路径
     */
    static std::string realpath(const std::string &path);

    /**
     * @brief : 创建目录软连接
     * @param : [in] target 目标目录
     * @param : [in] link 连接符号
     * @return: true-创建成功；false-创建失败
     */
    static bool createDirectorySymlink(const std::string &target, const std::string &link);
    /**
     * @brief : 创建目录软连接
     * @param : [in] target 目标目录
     * @param : [in] link 连接符号
     * @param : [out] ec 错误信息
     * @return: true-创建成功；false-创建失败
     */
    static bool createDirectorySymlink(const std::string &target, const std::string &link, std::error_code &ec);

    /**
     * @brief : 规范化文件路径
     * @param : [in] path，原始文件路径
     * @return: 规范化后的文件路径
     */
    static std::string canonicalizeFilename(const std::string &path);

    /**
     * @brief : 生成临时文件名
     * @param : [in | out] tmp，临时文件名，必需如下形式的:*.XXXXXX
     * @return: 是否成功
     */
    static bool genTempName(std::string &tmp);

    static std::string kmGenerateTempName(const std::string &tempName);

    /**
     * @brief : 获取目录所有文件md5校验和（包含子孙目录）
     * @param : [in] dir，目录路径
     * @return: 目录所有文件md5校验和
     * @note  : 此方法废弃，替换为KMDirMd5sum::md5sum
     */
    static std::string getDirMd5sum(const std::string &dir);

    /**
     * @brief : 判断是否废弃的md5计算方式的结果
     * @param : md5sumFile，md5校验和文件
     * @return: true-废弃的；false-未废弃的
     */
    static bool isObsolete(const std::string &md5sumFile);

    /**
     * @brief : 获取文件sha256校验和
     * @param : [in] file，文件路径
     * @return: 文件内容校验和
     */
    static std::string getFileSha256sum(const std::string &file);

    /**
     * @brief : 获取文件md5校验和
     * @param : [in] file，文件路径
     * @return: 文件内容校验和
     */
    static std::string getFileMd5sum(const std::string &file);

    /**
     * @brief 读取文件内容
     * @param fileName 文件名，包含路径
     * @param content 文件内容
     * @return bool true成功 false失败
     */
    static bool readFile(const std::string &fileName, std::string &content);

    /**
     * @brief 立即写文件
     * @param fileName 文件名，包含路径
     * @param content 文件内容
     * @return bool
     */
    static bool syncWriteFile(const std::string &fileName, const std::string &content);

    /**
     * @brief : 获取目录下所有的文件列表（包含子目录下的文件）
     * @param : [in] dir，路径
     */
    static std::vector<std::string> getDirFiles(const fs::path &dir);

    /**
     * findFileInTree:
     * @brief : 在目录树中查找文件是否存在
     * @param : [in] baseDir, 查找的目标目录
     * @param : [in] filename，待查找的文件名
     * @return: true-找到; false-未找到
     */
    static bool findFileInTree(const std::string &baseDir, const std::string &filename);

    /**
     * @brief 将source中的文件拷贝到destination中，调用文件fileCopy
     * 
     * @param source 源文件目录
     * @param destination 目标文件目录
     * @param skipPatterns 不需要复制的文件名，若无设置为空
     * 
     * @return 返回<错误信息，true或false>
     * 
     */
    static std::pair<std::string, bool> fileCopy(const std::string &source, const std::string &destination, const std::vector<std::string> &skipPatterns);
    static void recursive_copy(const fs::path &src, const fs::path &dst, const std::vector<std::regex> &regexPatterns);
    static bool should_skip(const fs::path &path, const std::vector<std::regex> &regexPatterns);

    /**
     * @brief : 规范化目录文件描述符
     * @param : [in] fd，目录文件描述符
     */
    static int dirfdCanonicalize(int fd);

    /*
        不支持链接。适用于真正的临时存储。fd将被分配到指定的目录中.
     */
    static bool openAnonymousTmpfile(int flags, KMTmpfile &tmpfile, std::string &errMsg);
    static bool openAnonymousTmpfileFull(int flags, const std::string &dir, KMTmpfile &tmpfile, std::string &errMsg);
    static bool openTmpfileCore(int dfd, const std::string &subpath, int flags, KMTmpfile &tmpfile, std::string &errMsg);
    static bool openTmpfileLinkableAt(int dfd, const std::string &subpath, int flags, KMTmpfile &tmpfile, std::string &errMsg);

    /*
        如果memfd_create（）可用，则生成一个内容为@str的密封memfd。否则，在匿名模式下使用O_TMPFILE@tmpf，将@str写入@tmpf中，
        并将lseek（）写回起始位置。另请参阅类似的用法，例如rpm-ostree，用于运行dracut。
     */
    static bool sealedMemfdOrTmpfile(KMTmpfile &tmpfile, const std::string &name, const char *data, size_t dataLen, std::string &errMsg);

    /*
        像write（）一样，但循环直到@nbytes被写入，或者出现错误。
        出现错误时，返回-1，并设置了@errno。注：这是API对此函数以前版本的更改。
     */
    static int loopWrite(int fd, const unsigned char *buf, size_t nbytes);

    /**
     * @brief : 替换文件内容
     * @param : [in] dfd, 目录文件描述符
     * @param : [in] subpath，子路径
     * @param : [in] buf, 文件内容
     * @param : [in] size, 文件内容长度
     * @param : [in] mode, File mode; if `-1`, use `0644`
     * @param : [in] uid, 归属用户id
     * @param : [in] gid, 归属组id
     * @param : [in] flags，替换flag
     * @param : [out] errMsg，错误信息
     * @return: 操作是否成功
     */
    static bool replaceFileContentsWithPermsAt(int dfd, const char *subpath, const unsigned char *buf, size_t size, mode_t mode, uid_t uid, gid_t gid, FileReplaceFlags flags, std::string &errMsg);

    static bool linkTmpfileAt(KMTmpfile &tmpfile, LinkTmpfileReplaceMode mode, int target_dfd, const char *target, std::string &errMsg);

    /**
     * @brief : 使用cp实现目录或文件拷贝
     * @param : [in] src, 源目录或文件
     * @param : [in] dest, 目标目录或文件
     * @return : true-成功；false-失败
     */
    static bool cpRfAll(const std::string &src, const std::string &dest);
    static bool cpRfAllStar(const std::string &src, const std::string &dest);

    /**
     * @brief : 使用rsync同步目录中的内容
     * @param : [in] src, 源目录
     * @param : [in] dest, 目标目录
     * @return : true-成功；false-失败
     */
    static bool rsyncDir(const std::string &src, const std::string &dest);

    /*
     * @brief: 解析ASCII文件
     * @brief: [in] path, 源文件目录
     */
    static std::vector<std::string> parseTxt(const std::string &path);

    /**
     * @brief: 判断是否为V11系统
     */
    static bool isV11();

    /**
     * @description: 判断是不是可执行文件
     * @param {path} &path 传入的文件路径
     * @return true: 是
     */
    static bool isExecutableByFileCmd(const fs::path &path);

    /**
     * @description: 判断是不是脚本文件
     * @param {path} &path 传入的文件路径
     * @return {*}
     */
    static bool isShellScriptByFileCmd(const fs::path &path);

    /**
     * @description: 排除非可执行文件类型
     * @param {path} &path 传入的文件路径
     * @return {*}
     */
    static bool isNonExecExt(const fs::path &path);

    /**
     * @description: 根据文件类型拷贝
     * @param {path} &sourceDir 源目录
     * @param {path} &destDir  目标目录
     * @param {string} &extension 文件后缀
     * @param {string} &fileType  文件类型
     * @param {set<std::string>} &installedFiles 安装文件列表
     * @return {*}
     */
    static std::string copyFilesByExtension(const fs::path &sourceDir, const fs::path &destDir, const std::string &extension, const std::string &fileType, std::set<std::string> &installedFiles);
};

#endif //!__KMFILEUTILS_H__
