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

#include "KMOABUtils.h"

#include <unistd.h>
#include <sys/wait.h>
#include <sys/utsname.h>
#include <cstring>
#include <filesystem>
#include <cstdlib>
#include <fstream>
#include <limits.h> // for PATH_MAX


#include "common/KMLogger.h"
#include "common/KMStringUtils.h"

namespace fs = std::filesystem;

/**
 * @brief : 执行外部程序
 * @param : [in] exec, 外部程序
 * @param : [in] args, 执行外部程序需要传入的参数列表
 * @param : [in] ignoreExitStatus，是否忽略外部程序退出状态；true-忽略; false-不忽略
 * @return: true - 成功执行；false - 出现错误
*/
bool KMOABUtils::spawn(const std::string &exec, const std::vector<std::string> &args, bool ignoreExitStatus)
{
    std::string cmdline(exec);
    std::vector<const char *> execvpArgs;
    execvpArgs.push_back(exec.c_str());
    for (auto &arg : args)
    {
        execvpArgs.push_back(arg.c_str());
        cmdline += " " + arg;
    }
    execvpArgs.push_back(nullptr);
    KMDebug(cmdline);

    int pipefd[2];
    if (::pipe(pipefd) == -1)
    {
        KMError(::strerror(errno));
        return false;
    }

    pid_t pid = ::fork();
    if (pid == -1)
    {
        ::close(pipefd[0]);
        ::close(pipefd[1]);
        KMError(::strerror(errno));

        return false;
    }
    else if (pid == 0)
    {
        // 子进程
        ::close(pipefd[0]); // 关闭读端
        ::dup2(pipefd[1], STDOUT_FILENO); // 将标准输出重定向到管道的写端
        ::close(pipefd[1]); // 关闭原来的写端

        ::execvp(exec.c_str(), (char **)execvpArgs.data());

        KMError(exec + " : " + ::strerror(errno)); // 如果execvp返回，说明执行失败
        ::exit(EXIT_FAILURE);
    }
    else
    {
        // 父进程
        ::close(pipefd[1]);  // 关闭写端
        char buffer[1025] = { 0 };
        ssize_t count = 0;

        // 从管道读取数据并输出到标准输出
        while ((count = ::read(pipefd[0], buffer, sizeof(char) * 1024)) > 0)
        {
            ::write(STDOUT_FILENO, buffer, count);
            ::memset(buffer, 0, 1025);
        }
        ::close(pipefd[0]); // 关闭读端

        int status = 0;
        pid_t result = ::waitpid(pid, &status, 0);
        if (result == -1)
        {
            KMError(::strerror(errno));
            return false;
        }

        if (!ignoreExitStatus)
        {
            if (WIFEXITED(status))
            {
                int exitCode = WEXITSTATUS(status);
                if (exitCode)
                {
                    KMError(exec + " exited with failure status " + std::to_string(exitCode));
                    return false;
                }
            }
            else if (WIFSIGNALED(status))
            {
                KMError(exec + std::string(" terminated by signal ") + std::to_string(WTERMSIG(status)));
                return false;
            }
        }
    }

    return true;
}

/**
 * @brief : 执行外部程序
 * @param : [in] exec, 外部程序名称，不包含路径
 * @param : [in] args, 执行外部程序需要传入的参数列表
 * @param : [out] result, 外部程序执行时的标准输出和标准错误信息
 * @return: true - 成功执行；false - 出现错误
 */
bool KMOABUtils::spawn(const std::string &exec, const std::vector<std::string> &args, std::string &result)
{
    std::string cmdline(exec);
    std::vector<const char *> execvpArgs;
    execvpArgs.push_back(exec.c_str());
    for (auto &arg : args)
    {
        execvpArgs.push_back(arg.c_str());
        cmdline += " " + arg;
    }
    execvpArgs.push_back(nullptr);
    KMDebug(cmdline);

    int pipefd[2];
    if (::pipe(pipefd) == -1)
    {
        KMError(::strerror(errno));
        return false;
    }

    pid_t pid = ::fork();
    if (pid == -1)
    {
        ::close(pipefd[0]);
        ::close(pipefd[1]);
        KMError(::strerror(errno));

        return false;
    }
    else if (pid == 0)
    {
        // 子进程
        ::close(pipefd[0]); // 关闭读端
        ::dup2(pipefd[1], STDOUT_FILENO); // 将标准输出重定向到管道的写端
        ::close(pipefd[1]); // 关闭原来的写端

        ::execvp(exec.c_str(), (char **)execvpArgs.data());

        KMError(exec + " : " + ::strerror(errno)); // 如果execvp返回，说明执行失败
        ::exit(EXIT_FAILURE);
    }
    else
    {
        // 父进程
        ::close(pipefd[1]);  // 关闭写端
        char buffer[1025] = { 0 };
        ssize_t count = 0;

        // 从管道读取数据并输出到标准输出
        while ((count = ::read(pipefd[0], buffer, sizeof(char) * 1024)) > 0)
        {
            result.append(buffer);
            ::memset(buffer, 0, 1025);
        }
        ::close(pipefd[0]); // 关闭读端

        int status = 0;
        pid_t wpid = ::waitpid(pid, &status, 0);
        if (wpid == -1)
        {
            KMError(::strerror(errno));
            return false;
        }

        if (WIFEXITED(status))
        {
            int exitCode = WEXITSTATUS(status);
            if (exitCode)
            {
                KMError(exec + " exited with failure status " + std::to_string(exitCode));
                return false;
            }
        }
        else if (WIFSIGNALED(status))
        {
            KMError(exec + std::string(" terminated by signal ") + std::to_string(WTERMSIG(status)));
            return false;
        }
    }

    return true;
}

/**
 * @brief : 递归创建目录
 * @param : [in] path, 目录
 * @param : [out] std::error_code，保存错误信息
 * @return : true-存在；false-不存在
 */
bool KMOABUtils::mkpath(const std::string &path, std::error_code &ec)
{
    bool result = fs::create_directories(path, ec);
    if (!result && 0 == ec.value())
    {
        return true;
    }
    return result;
}

/**
 * @brief : 递归创建目录，如果目录已经存在则不报告错误
 * @param : [in] path, 目录
 * @return : true-存在；false-不存在
 */
bool KMOABUtils::mkpath(const std::string &path)
{
    std::error_code ec;
    return KMOABUtils::mkpath(path, ec);
}

/**
 * @brief : 递归创建目录，如果目录已经存在则不报告错误
 * @param : [in] path, 目录
 * @param : [in] mode，路径权限，可八进制写法，如：0755
 * @return : true-存在；false-不存在
 */
bool KMOABUtils::mkpath(const std::string &path, unsigned int mode)
{
    std::error_code ec;
    if (KMOABUtils::mkpath(path, ec))
    {
        fs::permissions(path, (fs::perms)mode);
        return true;
    }

    return false;
}

/**
 * canonical:
 * @brief : 规范化路径，绝对路径并路径中不包含.和..
 * @param : [in] path，路径
 * @return: true-空目录或不存在; false-不是空目录或不是目录
 */
std::string KMOABUtils::canonical(const std::string &path)
{
    return fs::canonical(path);
}

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

    if (!fs::is_directory(p))
    {
        return false;
    }

    return fs::is_empty(p);
}

/**
 * @brief : 强制递归删除目录或文件
 * @param : [in] path, 待目录或文件
 * @return : true-成功；false-失败
 */
bool KMOABUtils::rmRfAll(const std::string &path)
{
    std::vector<std::string> args;
    args.push_back("-rf");
    args.push_back(path);

    return KMOABUtils::spawn("rm", args);
}

/**
 * @brief : 目录或文件拷贝
 * @param : [in] src, 源目录或文件
 * @param : [in] dest, 目标目录或文件
 * @return : true-成功；false-失败
 */
bool KMOABUtils::copyAll(const std::string &src, const std::string &dest)
{
    std::vector<std::string> args;
    args.push_back("-R");
    args.push_back(src);
    args.push_back(dest);

    return KMOABUtils::spawn("cp", args);
}

/**
 * @brief : 移动文件或重命名文件
 * @param : [in] src, 源目录或文件
 * @param : [in] dest, 目标目录或文件
 * @return : true-成功；false-失败
 */
bool KMOABUtils::mv(const std::string &src, const std::string &dest)
{
    std::vector<std::string> args;
    args.push_back("-f");
    args.push_back(src);
    args.push_back(dest);

    return KMOABUtils::spawn("mv", args);
}

/**
 * @brief : 获取文件sha256校验和
 * @param : [in] file，文件路径
 * @return: 文件内容校验和
  */ 
std::string KMOABUtils::getFileSha256sum(const std::string &file)
{
    std::string result;
    if (!fs::exists(file))
    {
        return result;
    }
    
    std::vector<std::string> args;
    args.push_back("-b");
    args.push_back(file);

    if (KMOABUtils::spawn("sha256sum", args, result))
    {
        std::vector<std::string> splits = KMStringUtils::splitString(result, " ");
        if (!splits.empty())
        {
            result = splits.at(0);
        }
        return result;
    }
    
    KMError(result);
    return "";
}

/**
 * @brief : 查找可执行文件路径
 * @param : [in] exe，可执行文件名
 * @return: 可执行文件绝对路径
 * @note  : 只探索下面目录，如果需要请自行添加
 *          /usr/local/sbin
 *          /usr/local/bin
 *          /usr/bin
 *          /usr/sbin
 *          /bin
 *          /sbin
 */ 
std::string KMOABUtils::searchExecutable(const std::string &exe)
{
    char * path = std::getenv("PATH");
    std::vector<std::string> binDirs;
    if (path && path[0] != '\0')
    {
        binDirs = KMStringUtils::splitString(path, ":");
    }
    else
    {
        std::vector<std::string> binDirsDefault = {
            "/usr/local/sbin",
            "/usr/local/bin",
            "/usr/sbin",
            "/usr/bin",
            "/sbin",
            "/bin"
        };

        binDirs = binDirsDefault;
    }

    std::string result;

    for (const std::string& binDir : binDirs)
    {
        std::string execPath = KMStringUtils::buildFilename(binDir, exe);
        fs::path p(execPath);
        if (!fs::exists(p))
        {
            continue;
        }

        if (!fs::is_regular_file(p))
        {
            continue;
        }

        return execPath;
    }

    return result;
}

/**
 * @brief : 获取真实的xdg运行时路径
 * @note  : 有时这是/var/run-->/run，它是一个符号链接，
 *          当我们将其作为路径传递到沙箱时会引起奇怪的问题
 */ 
std::string KMOABUtils::getRealXdgRuntimeDir()
{
    // const char *userRuntimeDir = g_get_user_runtime_dir();
    // if (userRuntimeDir != nullptr)
    // {
    //     std::string runtimeDir(userRuntimeDir);
    //     if (fs::is_symlink(runtimeDir))
    //     {
    //         fs::path link = fs::read_symlink(runtimeDir);
    //         runtimeDir = fs::absolute(link).string();
    //     }

    //     return runtimeDir;
    // }
    // else
    // {
        return std::string("/run/user/") + std::to_string(::getuid());
    // }
}

/**
 * @brief : 获取默认的xdg目录: XDG_CONFIG_HOME(~/.config)
 */ 
std::string KMOABUtils::getXdgConfigHome()
{
    const char *home = ::getenv("HOME");
    if (nullptr == home || *home == '0')
    {
        return std::string("~/.config");
    }

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

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

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

// #if !defined(__i386__) && !defined(__x86_64__) && !defined(__aarch64__) && !defined(__arm__)
static std::string get_kernel_arch()
{
    static struct utsname buf;
    static std::string arch ;

    if (::uname(&buf))
    {
        arch = "unknown";
        return arch;
    }

    arch = buf.machine;

    /* Override for some arches */
    std::string mstr = buf.machine;
    if (mstr.length() == 4 && mstr.at(0) == 'i' && mstr.at(2) == '8' && mstr.at(3) == '6')
    {
        arch = "i386";
    }
    else if (mstr == "mips")
    {
#  if G_BYTE_ORDER == G_LITTLE_ENDIAN
        arch = "mipsel";
#  endif
    }
    else if (mstr == "mips64")
    {
#  if G_BYTE_ORDER == G_LITTLE_ENDIAN
        arch = "mips64el";
#  endif
    }

    return arch;
}
// #endif

const std::string KMOABUtils::arch()
{
#if defined(__i386__)
    return "i386";
#elif defined(__x86_64__)
    return "x86_64";
#elif defined(__aarch64__)
    return "aarch64";
#elif defined(__arm__)
#  if G_BYTE_ORDER == G_LITTLE_ENDIAN
    return "arm";
#  else
    return "armeb";
#  endif
#else
    return get_kernel_arch();
#endif
}

/**
 * @brief : 获取平台库目录
 * @return: 平台库目录，如："x86_64-linux-gnu"
 */ 
std::string KMOABUtils::getPlatformLibraryDir()
{
#if defined(__x86_64__)
    return "x86_64-linux-gnu";
#elif defined(__i386__)
    return "i386-linux-gnu";
#elif defined(__arm__) && defined(__ARMEL__)
    return "arm-linux-gnueabi";
#elif defined(__arm__) && defined(__ARMHF__)
    return "arm-linux-gnueabihf";
#elif defined(__aarch64__)
    return "aarch64-linux-gnu";
#elif defined(__powerpc64__) && defined(__LITTLE_ENDIAN__)
    return "powerpc64le-linux-gnu";
#elif defined(__powerpc64__) && !defined(__LITTLE_ENDIAN__)
    return "powerpc64-linux-gnu";
#else
    return KMOABUtils::arch() + "-linux-gnu";
#endif
}

/**
 * @brief : 获取时区
 */
std::string KMOABUtils::getTimeZone()
{
    std::string localtime("/etc/localtime");
    /* Resolve relative path */
    if (fs::is_symlink(localtime))
    {
        fs::path link = fs::read_symlink(localtime);
        localtime = fs::absolute(link).string();
    }
    
    if (fs::exists(localtime))
    {
        std::string canonical = fs::canonical(localtime);
        /* Strip the prefix and slashes if possible. */
        const char *tzdir = ::getenv("TZDIR");
        if (tzdir != nullptr && KMStringUtils::startsWith(canonical, tzdir))
        {
            std::string canonicalSuffix = canonical.substr(strlen(tzdir));
            while (canonicalSuffix.at(0) == '/')
            {
                canonicalSuffix = canonicalSuffix.substr(1);
            }

            return canonicalSuffix;
        }

        std::string defaultTzpath("/usr/share/zoneinfo");
        if (KMStringUtils::startsWith(canonical, defaultTzpath))
        {
            std::string canonicalSuffix = canonical.substr(defaultTzpath.length());
            while (canonicalSuffix.at(0) == '/')
            {
                canonicalSuffix = canonicalSuffix.substr(1);
            }

            return canonicalSuffix;
        }
    }

    if (fs::exists("/etc/timezone"))
    {
        std::fstream fileStream("/etc/timezone");
        if (fileStream.is_open())
        {
            std::string ret;
            std::getline(fileStream, ret);
            return ret;
        }
    }

    return std::string("UTC");
}

/**
 * @brief : 获取PulseAudio用于其套接字的运行时目录
 */ 
std::string KMOABUtils::getPulseRuntimeDir()
{
    const char *val = ::getenv("PULSE_RUNTIME_PATH");
    if (val != nullptr)
    {
        char resolved_path[PATH_MAX];
        ::realpath(val, resolved_path);
        return resolved_path;
    }

    {
        std::string userRuntimeDir = KMOABUtils::getRealXdgRuntimeDir();
        if (!userRuntimeDir.empty())
        {
            std::string dir = KMStringUtils::buildFilename(userRuntimeDir, "pulse");
            if (fs::is_directory(dir))
            {
                char resolved_path[PATH_MAX];
                ::realpath(dir.c_str(), resolved_path);
                return resolved_path;
            }
        }
    }

    {
        std::string pulseHome = KMOABUtils::getPulseHome();
        std::string machineId = KMOABUtils::getPulseMachineId();
        if (!pulseHome.empty() && !machineId.empty())
        {
            /* This is usually a symlink, but we take its realpath() anyway */
            std::string dir = pulseHome + "/" + machineId + "-runtime";
            if (fs::is_directory(dir))
            {
                char resolved_path[PATH_MAX];
                ::realpath(dir.c_str(), resolved_path);
                return resolved_path;
            }
        }
    }

    return "";
}

/*
 * 获取PulseAudio用于其配置的目录。
 */
std::string KMOABUtils::getPulseHome()
{
    /* Legacy path ~/.pulse is tried first, for compatibility */
    {
        const char *home = ::getenv("HOME");
        if (nullptr == home || *home == '0')
        {
            home = "~";
        }
        std::string ret = KMStringUtils::buildFilename(home, ".pulse");
        if (fs::is_directory(ret))
        {
            return ret;
        }
    }

    /* The more modern path, usually ~/.config/pulse */
    {
        std::string ret = KMStringUtils::buildFilename(KMOABUtils::getXdgConfigHome(), "pulse");
        if (fs::is_directory(ret))
        {
            return ret;
        }
    }

    return "";
}

/*
 * 获取PulseAudio使用的机器ID。这是systemd/D-Bus机器ID，否则是主机名。
 */
std::string KMOABUtils::getPulseMachineId()
{
    std::vector<std::string> machineIds = { "/etc/machine-id", "/var/lib/dbus/machine-id" };

    for (auto &machineId : machineIds)
    {
        std::fstream file(machineId);
        if (file.is_open())
        {
            std::string line;
            std::getline(file, line);
            line = KMStringUtils::strstrip(line);
            size_t i = 0;
            for (; i < line.length(); i++)
            {
                if (!::isxdigit(line.at(i)))
                {
                    break;
                }
            }

            if (!line.empty() && i == line.length())
            {
                return line;
            }
        }
    }

    char hostname[HOST_NAME_MAX + 1];
    ::gethostname(hostname, sizeof(hostname));
    return hostname;
}

/**
 * @brief : 用于将路径名解析为绝对路径的函数，它会处理符号链接、相对路径以及多余的 . 和 .. 组件
 * @param : [in] path, 路径
 * @return: 返回规范后的路径
 */  
std::string KMOABUtils::realpath(const std::string &path)
{
    char resolved_path[PATH_MAX];
    ::realpath(path.c_str(), resolved_path);
    return resolved_path;
}
