//
// Created by uos on 2022/3/18.
//

#include "Utils.h"
#include "Process.h"
#include "global.h"
#include <QFile>
#include <QStandardPaths>
#include <QtDBus/QtDBus>
#include <QtDBus/QDBusInterface>
#include <DSysInfo>
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>
#include <QProcess>
#include <QRegularExpression>
#include <dtkcore_global.h>
#include <QImageReader>

DCORE_USE_NAMESPACE

namespace Utils {

    QPixmap loadSvgImage(const QString& strPath, int iWidth, int iHeight)
    {
        QImageReader reader(strPath);
        if (reader.canRead()) {
            // 设置缩放尺寸
            reader.setScaledSize(QSize(iWidth, iHeight));
            // 设置抗锯齿
            reader.setAutoTransform(true);
            QImage image = reader.read();
            if (!image.isNull()) {
                return QPixmap::fromImage(image);
            }
        }
        // 读取失败返回空的 QPixmap
        return QPixmap();
    }

    QPixmap renderSVG(const QString &path, const QSize &size)
    {
        QImageReader reader;
        QPixmap pixmap;
        reader.setFileName(path);
        if (reader.canRead()) {
            const qreal ratio = qApp->devicePixelRatio();
            reader.setScaledSize(size * ratio);
            pixmap = QPixmap::fromImage(reader.read());
            pixmap.setDevicePixelRatio(ratio);
        } else {
            pixmap.load(path);
        }
        return pixmap;
    }


    QJsonObject QStringToJson(const QString &jsonString)
    {
        QJsonObject jsonObject;
        QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonString.toLocal8Bit().data());
        if (jsonDocument.isNull()) {
            return jsonObject;
        }
        jsonObject = jsonDocument.object();
        return jsonObject;

    }

    QString JsonToQString(const QJsonObject &jsonObject)
    {
        return QString(QJsonDocument(jsonObject).toJson());
    }

    const QJsonObject readPathfromJsonFile(const QString &path)
    {
        QFile file(path);
        if (!file.open( QIODevice::ReadOnly)) {
            return QJsonObject();
        }

        QJsonObject obj;
        QJsonParseError jsonParserError;
        QJsonDocument jsonDocument = QJsonDocument::fromJson( file.readAll(), &jsonParserError);
        if (!jsonDocument.isNull() && jsonParserError.error == QJsonParseError::NoError && jsonDocument.isObject()) {
            obj = jsonDocument.object();
        }

        file.close();

        return  obj;
    }

    const QPixmap hidpiPixmap(const QString &path, const QSize &sz)
    {
        const auto ratio = qApp->devicePixelRatio();
        QPixmap iconPix = Utils::renderSVG(path, sz);
        iconPix.setDevicePixelRatio(ratio);

        return iconPix;
    }

    bool calculateDirSize(const QString &dirPath, const QStringList &excludeDir, quint64 &totalSizeBytes,
                          QString &error, bool samePartition)
    {
        totalSizeBytes = 0;
        QStringList args;
        args <<"-sk" <<dirPath;
//        if (samePartition) {
//            args <<"-sk" <<dirPath;
//        } else {
//            args <<"-skl" <<dirPath; // 跨分区需要计算硬链接大小
//        }

        for (auto &exclude : excludeDir) {
            args << QString("--exclude=%1").arg(exclude);
        }

        QString out;
        if (!Process::spawnCmd("du", args, out, error)) {
            return false;
        }

        const int outColSize = 2; // size, dirPath
        foreach(QString line, out.split("\n")) {
            QStringList col = line.split("\t");
            if (outColSize != col.size()) {
                continue;
            }
            totalSizeBytes = col[0].trimmed().toULongLong();
            totalSizeBytes = totalSizeBytes * 1024;
            qInfo()<<"totalSizeBytes: "<<totalSizeBytes<<", du "<<args;
            return true;
        }

        return false;
    }

    QString getUserName()
    {
        // QString userPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
        // return userPath.section("/", -1, -1);
        QString output = "";
        QString err;
        if (Process::spawnCmd("whoami", {}, output, err)) {
            output = output.trimmed();
        } else {
            qWarning()<<"getUserName err: "<<err<<", output: "<<output;
        }
        return output;
    }

    bool authorization()
    {
        Authority::Result result;
        // 第一个参数是需要验证的action，和规则文件写的保持一致
        result = Authority::instance()->checkAuthorizationSync(
                "com.deepin.uosrecovery.checkAuthentication",
                UnixProcessSubject(getpid()),
                Authority::AllowUserInteraction);
        return result == PolkitQt1::Authority::Yes;
    }

    bool checkCommonUserAuthentication()
    {
        Authority::Result result;
        // 第一个参数是需要验证的action，和规则文件写的保持一致
        result = Authority::instance()->checkAuthorizationSync(
                "com.deepin.uosrecovery.checkCommonUserAuthentication",
                UnixProcessSubject(getpid()),
                Authority::AllowUserInteraction);
        return result == PolkitQt1::Authority::Yes;
    }

    bool readJsonFile(const QString &filepath, QJsonDocument &destdoc)
    {
        QFile file(filepath);
        if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) {
            return false;
        }
        QByteArray data = file.readAll();
        file.close();
        QJsonParseError jsonError;
        destdoc = QJsonDocument::fromJson(data, &jsonError);
        if (jsonError.error != QJsonParseError::NoError) {
            return false;
        }

        return true;
    }

    bool writeJsonFile(const QString &filepath, const QJsonDocument &destdoc)
    {
        QFile file(filepath);
        if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
            qInfo() << "writeJsonFile err: " << file.errorString();
            return false;
        }

        file.write(destdoc.toJson());
        file.close();
        return true;
    }

    QString byte2DisplaySize(quint64 size)
    {
        if (size >= KiB && size < MiB) {
            return QString().asprintf("%.1f KB", size * 1.0 / KiB);
        } else if (size >= MiB && size < GiB) {
            return QString().asprintf("%.1f MB", size * 1.0 / MiB);
        } else if (size >= GiB) {
            return QString().asprintf("%.1f GB", size * 1.0 / GiB);
        } else {
            return QString().asprintf("%llu Byte", size);
        }
    }

    quint64 parsePartitionSize(const QString& jsonString)
    {
        quint64 totalBytes = 0;
        if (jsonString.isEmpty()) {
            return totalBytes;
        }

        QString partitionString = jsonString;
        if (jsonString.contains("GB")) {
            totalBytes = partitionString.replace("GB", "").trimmed().toFloat() * GiB;
        } else if (jsonString.contains("MB")) {
            totalBytes = partitionString.replace("MB", "").trimmed().toFloat() * MiB;
        } else if (jsonString.contains("KB")) {
            totalBytes = partitionString.replace("KB", "").trimmed().toFloat() * KiB;
        } else if (jsonString.contains("Byte")) {
            totalBytes = partitionString.replace("Byte", "").trimmed().toUInt();
        }

        return totalBytes;
    }

    bool isAdminUserByDbus(const QString &userName, const QString &servicePath, const QString &accountPath,
                           const QString &accountInterfacePath, const QString &userInterfacePath)
    {
        QDBusInterface accountsInterface(servicePath, accountPath, accountInterfacePath,
                                         QDBusConnection::systemBus());
        if (!accountsInterface.isValid()) {
            return false;
        }

        QDBusReply<QString> userPathReply = accountsInterface.call("FindUserByName", userName);
        if (!userPathReply.isValid()) {
            return false;
        }

        QString userPath = userPathReply.value();
        QDBusInterface userInterface(servicePath, userPath, userInterfacePath,
                                     QDBusConnection::systemBus());
        if (!userInterface.isValid()) {
            return false;
        }

        QVariant userGroupProperty = userInterface.property("Groups");
        if (userGroupProperty.type() != QVariant::Type::StringList) {
            return false;
        }

        QStringList groupList = userGroupProperty.toStringList();
        if (groupList.contains("sudo")) {
            return true;
        } else {
            // 域管账户Groups有时获取到的是空，此时AccountType发现是1，否则域管账户这里值是2
            const int userTypeAdmin = 1; // 参考控制中心里的取值
            auto accountTypeReply = userInterface.property("AccountType");
            int accountType = accountTypeReply.toInt();
            if (userTypeAdmin == accountType) {
                return true;
            }
        }

        return false;
    }

    bool isAdminUser(const QString &userName)
    {
//        const QString servicePath = "com.deepin.daemon.Accounts";
//        const QString v20AccountPath = "/com/deepin/daemon/Accounts";
//        const QString v20AccountInterfacePath = "com.deepin.daemon.Accounts";
//        const QString v20UserInterfacePath = "com.deepin.daemon.Accounts.User";
//
//        bool isAdmin = isAdminUserByDbus(userName, servicePath, v20AccountPath, v20AccountInterfacePath,
//                                         v20UserInterfacePath);
//        if (!isAdmin) {
//            const QString v23AccountPath = "/org/deepin/daemon/Accounts1";
//            const QString v23AccountInterfacePath = "org.deepin.daemon.Accounts1";
//            const QString v23UserInterfacePath = "org.deepin.daemon.Accounts1.User";
//            isAdmin = isAdminUserByDbus(userName, servicePath, v23AccountPath, v23AccountInterfacePath,
//                                        v23UserInterfacePath);
//        }
        // 减少对dde dbus接口的依赖，改用系统调用的方式实现
        return isAdminUserBySysCall(userName);
    }

    bool isAdminUserBySysCall(const QString &userName)
    {
        bool isAdmin = false;
        struct passwd *pw = getpwnam(userName.toLocal8Bit().data());
        if (NULL == pw) {
            // invalid user
            return isAdmin;
        }

        int groupNum = 0;
        int retCode = getgrouplist(userName.toLocal8Bit().data(), pw->pw_gid, NULL, &groupNum);
        __gid_t *groups = static_cast<__gid_t *> (malloc (groupNum * sizeof(__gid_t)));
        if (NULL == groups) {
            qWarning()<<Q_FUNC_INFO<<"malloc failed, retCode = "<<retCode<<", userName = "<<userName;
            return false;
        }

        retCode = getgrouplist(userName.toLocal8Bit().data(), pw->pw_gid, groups, &groupNum);
        if (-1 == retCode) {
            qWarning()<<Q_FUNC_INFO<<"getgrouplist failed, groupNum = "<<groupNum<<", userName = "<<userName;
            if (NULL != groups) {
                free(groups);
                groups = NULL;
            }
            return false;
        }

        for (int i = 0; i < groupNum; ++i) {
            struct group *curGroup = getgrgid(groups[i]);
            if (curGroup != NULL) {
                QString groupName = curGroup->gr_name;
                if ("sudo" == groupName || "root" == groupName) {
                    isAdmin = true;
                    break;
                }
            }
        }

        if (NULL != groups) {
            free(groups);
            groups = NULL;
        }

        return isAdmin;
    }

    bool isUserExist(const QString &userName)
    {
        struct passwd *user = getpwnam(userName.toLocal8Bit().data());
        if (NULL != user) {
            return true;
        }
        return false;
    }

    bool isOStree()
    {
        if (Utils::isImmutableSystem()) {
            return false;
        }

        QString errMsg;
        QDBusInterface atomicInterface("org.deepin.AtomicUpgrade1",
                                       "/org/deepin/AtomicUpgrade1",
                                       "org.deepin.AtomicUpgrade1",
                                       QDBusConnection::systemBus());
        if (!atomicInterface.isValid()) {
            QDBusError dbusErr = atomicInterface.lastError();
            if (QDBusError::ErrorType::NoError != dbusErr.type()) {
                errMsg = dbusErr.message();
                qCritical() << Q_FUNC_INFO << "error: atomicInterface invalid, errMsg = " << errMsg;
                return false;
            }
        }

        QDBusReply<QStringList> listVersionReply = atomicInterface.call("ListVersion");
        if (!listVersionReply.isValid()) {
            errMsg = listVersionReply.error().message();
            qCritical() << Q_FUNC_INFO << "error: listVersionReply invalid, errMsg = "<<errMsg;
            return false;
        }

        QStringList versionList = listVersionReply.value();
        if (versionList.isEmpty()) {
            qCritical() << Q_FUNC_INFO << "error: ostree versionList isEmpty";
            return false;
        }

        // check SubmissionType
        QDBusReply<QStringList> subjectsReply = atomicInterface.call("QuerySubject", versionList);
        if (!subjectsReply.isValid()) {
            errMsg = subjectsReply.error().message();
            qCritical() << Q_FUNC_INFO << "error: subjectsReply invalid, errMsg = "<<errMsg;
            return false;
        }
        QStringList subjectList = subjectsReply.value();
        for (auto subject : subjectList) {
            QJsonObject jsonObj = Utils::QStringToJson(subject);
            if (jsonObj.contains("SubmissionType")) {
                CommitType type = static_cast<CommitType>(jsonObj.value("SubmissionType").toInt(-1));
                if (CommitType::InstallerCommit == type) {
                    return true;
                }
            }
        }

        return false;
    }

    //deepin-immutable-ctl -s 查询的结果是Immutable mode:false和Immutable mode:true
    //均视为Immutable系统
    bool isImmutableSystem()
    {
        QStringList args;
        args <<"-s";
        QString out;
        QString err;
        if (!Process::spawnCmd("deepin-immutable-ctl", args, out, err)) {
            return false;
        }

        return true;
    }

    quint64 getRemainSecond(const int &progress, QTime &baseTime)
    {
        if (progress <= 0) {
            return 1;
        }
        QTime currTime = QTime::currentTime();
        int elapsed = baseTime.msecsTo(currTime);
        double speed = (progress * 1000.0) / elapsed;
        return (100 - progress) / speed;
    }

    QString getOSVersion()
    {
        QString uosSysName = DSysInfo::uosSystemName(QLocale::English);
        DSysInfo::UosEdition edition = DSysInfo::uosEditionType();
        QString backupFlag = "UOS";
        QString version;
        if (DSysInfo::uosType() == DSysInfo::UosServer || DSysInfo::uosEditionType() == DSysInfo::UosEuler) {
            version = QString("%1-%2").arg(DSysInfo::majorVersion())
                    .arg(DSysInfo::minorVersion());
        } else if (DSysInfo::isDeepin()) {
            if (DSysInfo::UosEdition::UosCommunity == edition) {
                backupFlag = uosSysName;
            }
            version = QString("%1-V%2-%3").arg(backupFlag).arg(DSysInfo::majorVersion())
                    .arg(DSysInfo::minorVersion());
            QString buildVer = DSysInfo::buildVersion();
            if (!buildVer.isEmpty()) {
                version = QString("%1-%2").arg(version).arg(buildVer);
            }
        } else {
            version = QString("%1-%2").arg(DSysInfo::productVersion())
                    .arg(DSysInfo::productTypeString());
        }

        return version;
    }

    QString getOSVersionDisplay(bool localeEnglish)
    {
        QString versionDisplay;
        if (DSysInfo::uosType() == DSysInfo::UosServer || DSysInfo::uosEditionType() == DSysInfo::UosEuler) {
            versionDisplay = QString("%1 %2").arg(DSysInfo::majorVersion())
                    .arg(DSysInfo::minorVersion());
        } else if (DSysInfo::isDeepin()) {
            QLocale locale = localeEnglish ? QLocale::English : QLocale::system();
            QString uosSysName = DSysInfo::uosSystemName(locale);
            QString editionName = DSysInfo::uosEditionName(locale);
            versionDisplay = QString("%1 %2").arg(uosSysName).arg(DSysInfo::minorVersion());
        } else {
            versionDisplay = QString("%1 %2").arg(DSysInfo::productVersion())
                    .arg(DSysInfo::productTypeString());
        }

        return versionDisplay;
    }

    bool getDestPartInfoByDir(const QString &selectDir, QJsonObject &destInfo, QString &err)
    {
        // 找到目录所在分区
        destInfo = QJsonObject();
        QString destDir = selectDir;
        // if (destDir.contains(" ")) {
        //     destDir = QString("\"%1\"").arg(selectDir);
        // }

        QString output = "";
        QString devicePath;
        if (!Process::spawnCmd("df", {destDir}, output, err)) {
            qInfo()<<"getDestPartInfoByDir failed, err = "<<err<<", selectDir = "<<selectDir;
            return false;
        }

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
        QStringList outputList = output.split("\n", Qt::SkipEmptyParts);
#else
        QStringList outputList = output.split("\n", QString::SkipEmptyParts);
#endif
        for (QString &line : outputList) {
            if (!line.startsWith("/dev/")) {
                continue;
            }

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
            QStringList devItem = line.split(" ", Qt::SkipEmptyParts);
#else
            QStringList devItem = line.split(" ", QString::SkipEmptyParts);
#endif
            devicePath = devItem[0].trimmed();
            break;
        }

        QJsonArray destInfoArray;
        err = "";
        QStringList args;
        args<<devicePath<<"-pbJO";
        if (!getLsblkJsonReturn(args, destInfoArray, err)){
            return false;
        }

        if (destInfoArray.size() > 0) {
            destInfo = destInfoArray.first().toObject();
        } else {
            err = "no info get";
            return false;
        }

        return true;
    }

    int currentPlatformByString(const QString &platform)
    {
        QMap<QString, PlatformType> platformMap = {
            { "x86_64", PlatformType::X86_64 },
            { "i386", PlatformType::I386 },
            { "i686", PlatformType::I686 },
            { "amd64", PlatformType::AMD64 },
            { "x86", PlatformType::X86 },
            { "sw_64", PlatformType::SW_64 },
            { "mips64", PlatformType::MIPS64 },
            { "loongarch64", PlatformType::LOONGARCH64 },
            { "aarch64", PlatformType::AARCH64 }
        };

        if (!platformMap.keys().contains(platform)) {
            return PlatformType::UNKNOW;
        }

        return platformMap[platform];
    }

    bool isEFIMode()
    {
        if (QFile::exists("/sys/firmware/efi")) {
            return true;
        }

        QString out;
        QString err;
        if (!Process::spawnCmd("uname", {"-m"}, out, err)) {
            return false;
        }

        out = out.trimmed();
        if (out.contains("SW")) {
            return true;
        }

        return false;
    }

    bool isSystemUpgrade()
    {
        // 根据grub.cfg文件判断是否使用了ab-recovery升级了系统
        QString grubCfgPath = "/boot/grub/grub.cfg";
        //QString cmd = "cat /boot/grub/grub.cfg | grep deepin-ab-recovery";
        QString cmdLog = "";
        QString err = "";
        if (!Process::spawnSubCmd("grep", {"deepin-ab-recovery"},"cat", {grubCfgPath}, cmdLog, err)) {
            // err = "get " + selectDir + " device error cmd : " + cmd + " err : " + err;
            return false;
        }

        if ((cmdLog.contains("linux")) && (cmdLog.contains("initrd"))) {
            return true;
        }

        return false;
    }

    bool getLsblkJsonReturn(const QStringList &cmdArgs, QJsonArray &jsonOut, QString &err)
    {
        jsonOut = QJsonArray();

        QString cmdLog = "";
        if (!Process::spawnCmd("lsblk", cmdArgs, cmdLog, err)) {
            err = "getLsblkJsonReturn error: " + err;
            return false;
        }

        cmdLog.replace("0B", "0");
        cmdLog.replace("none", "0");
        cmdLog.replace("null", "0");

        QJsonParseError jsonError;
        QJsonObject cmdDestInfo = QJsonDocument::fromJson(cmdLog.toStdString().data(), &jsonError).object();
        if (jsonError.error != QJsonParseError::NoError) {
            err = "getLsblkJsonReturn read info json failed " + jsonError.errorString();
            return false;
        }

        jsonOut = cmdDestInfo.value("blockdevices").toArray();

        return true;
    }


    bool getDimFileJsonInfo(const QString &fileName, QJsonObject &fileInfo, QString &err)
    {
        fileInfo = QJsonObject();

        QString cmd = QString("deepin-clone -i %1").arg(fileName);
        QString cmdLog = "";
        err = "";
        if (!Process::spawnCmd("deepin-clone", {"-i", fileName}, cmdLog, err)) {
            err = "getDimFileTotalSize error cmd : " + cmd + "err : " + err;
            return false;
        }

        // 解析总大小的值
        QJsonParseError jsonError;
        QJsonDocument sizeDoc = QJsonDocument::fromJson(cmdLog.toLocal8Bit(), &jsonError);
        if (jsonError.error != QJsonParseError::NoError) {
            err = "getDimFileTotalSize read json error";
            return false;
        }

        fileInfo = sizeDoc.object();
        return true;
    }

    // 根据配置的设备类型过滤设备，返回过滤后剩余设备的数量
    int filterDevice(QJsonArray &deviceArray)
    {
        QStringList deviceTypes = {"disk"};
        QJsonArray::iterator iter = deviceArray.begin();
        for (;iter != deviceArray.end();) {
            QJsonObject deviceObj = iter->toObject();
            qint64 deviceSize = deviceObj.value("size").toVariant().toLongLong() / MiB;
            QString deviceType = deviceObj.value("type").toString();

            if ((!deviceTypes.contains(deviceType))// 过滤掉不在期望类型中的设备
                || (deviceSize <= 1)) {// 过滤掉于1M的设备
                iter = deviceArray.erase(iter);
                continue;
            } else {
                ++iter;
            }
        }

        return deviceArray.size();
    }

    bool getFormatPartitionCmd(const QString &label, const QString &filesystem, const QString &path, QString &cmd, QString &err)
    {
        cmd = "";

        if (filesystem.isEmpty()) {
            return false;
        }

        QMap<QString, QString> cmdMap;
        cmdMap.insert("btrfs", QString("mkfs.btrfs -f [-L %1] %2"));
        cmdMap.insert("efi", QString("mkfs.vfat -F32 -v -I [-n %1] %2"));
        cmdMap.insert("ext2", QString("mkfs.ext2 -F [-L %1] %2"));
        cmdMap.insert("ext3", QString("mkfs.ext3 -F [-L %1] %2"));
        cmdMap.insert("ext4", QString("mkfs.ext4 -F [-L %1] %2"));
        cmdMap.insert("f2fs", QString("mkfs.f2fs -f [-l %1] %2"));
        cmdMap.insert("fat16", QString("mkfs.fat -F16 -v -I [-n %1] %2"));
        cmdMap.insert("fat32", QString("mkfs.fat -F32 -v -I [-n %1] %2"));
        cmdMap.insert("hfs", QString("/usr/bin/hformat -f [-l %1] %2"));
        cmdMap.insert("hfsplus", QString("/usr/bin/hpfsck -f [-v %1] %2"));
        cmdMap.insert("jfs", QString("mkfs.jfs -q [-L %1] %2"));
        cmdMap.insert("nilfs2", QString("mkfs.nilfs2 -f [-L %1] %2"));
        cmdMap.insert("ntfs", QString("mkfs.ntfs -Q -v -F [-L %1] %2"));
        cmdMap.insert("reiser4", QString("mkfs.reiser4 --force --yes [--label %1] %2"));
        cmdMap.insert("reiserfs", QString("mkfs.reiserfs -f [--label %1] %2"));
        cmdMap.insert("xfs", QString("mkfs.xfs -f [-L %1] %2"));
        cmdMap.insert("recovery", QString("mkfs.ext4 -F [-L %1] %2"));
        cmdMap.insert("linux-swap", QString("mkswap -f [-L %1] %2"));
        cmdMap.insert("swap", QString("mkswap -f [-L %1] %2"));
        cmdMap.insert("vfat", QString("mkfs.vfat -F32 [-n %1] %2"));
        cmdMap.insert("ext4_ls", QString("mkfs.ext4 -O ^64bit -F [-L %1] %2"));
        cmdMap.insert("default", QString("mkfs -t %1 ").arg(filesystem) + "[-L %1] %2");

        QString key = filesystem;
        if (!cmdMap.contains(filesystem)) {
            key = "default";
        } else {
            // 针对ext4文件系统，申威，龙芯这些平台需要特殊处理
            if (filesystem == "ext4") {
                QString out;
                QString err;
                if (!Process::spawnCmd("uname", {"-m"}, out, err)) {
                    qCritical()<<"writeGhostDeviceInfo spawnCmd failed to get uname, err: "<<err;
                } else {
                    QString platform = out.trimmed();
                    if (platform.contains(QRegularExpression("sw|loongson|loongarch64"))) {
                        key = "ext4_ls";
                    }
                }
            }
        }

        cmd = cmdMap.value(key).arg(label, path);

        if (label.isEmpty()) {
            cmd.replace(QRegularExpression("\\[[^\\[\\]]*\\]"), "");
        } else {
            cmd.replace(QRegularExpression("[\\[|\\]]"), "");
        }

        return true;
    }

    int getCPUCores()
    {
        CpuInfo cpuInfo;
        bool retCode = getCpuInfo(cpuInfo);
        if (!retCode) {
            return 1;
        }

        return cpuInfo.cpuNum;
    }

    bool getProcessNameByPid(pid_t pid, QString &name)
    {
        //QString cmd = QString("cat /proc/%1/stat | awk -F\")\" '{print $1}' | awk -F\"(\" '{print $2}'").arg(pid);
        QString cmd = QString("/proc/%1/comm").arg(pid);
        QStringList args;
        args <<cmd;
        QString err;
        // process name (may be truncated by kernel if it's too long)
        if (!Process::spawnCmd("cat", args, name, err)) {
            return false;
        }

        name = name.trimmed();
        return true;
    }

    QString getKernelArch()
    {
        QString arch = "";
        //QString cmd = QString("dpkg --print-architecture");
        QStringList args;
        args <<"--print-architecture";
        QString out;
        QString err;
        if (!Process::spawnCmd("dpkg", args, out, err)) {
            return arch;
        }

        arch = out.trimmed();
        return arch;
    }

    QString getOSEditionType()
    {
        QString editionType = "";
        DSysInfo::UosEdition edition = DSysInfo::uosEditionType();
        switch (edition) {
            case DSysInfo::UosEdition::UosProfessional: {
                editionType = "Professional";
                break;
            }
            case DSysInfo::UosEdition::UosHome: {
                editionType = "Home";
                break;
            }
            case DSysInfo::UosEdition::UosCommunity: {
                editionType = "Community";
                break;
            }
            case DSysInfo::UosEdition::UosMilitary: {
                editionType = "Military";
                break;
            }
            case DSysInfo::UosEdition::UosEnterprise: {
                editionType = "Enterprise";
                break;
            }
            case DSysInfo::UosEdition::UosEnterpriseC: {
                editionType = "EnterpriseC";
                break;
            }
            case DSysInfo::UosEdition::UosEuler: {
                editionType = "Euler";
                break;
            }
            case DSysInfo::UosEdition::UosMilitaryS: {
                editionType = "MilitaryS";
                break;
            }
#if DTK_VERSION >= DTK_VERSION_CHECK(5, 4, 13, 0)
            case DSysInfo::UosEdition::UosDeviceEdition: {
                editionType = "DeviceEdition";
                break;
            }
            case DSysInfo::UosEdition::UosEducation: {
                editionType = "Education";
                break;
            }
#endif
            default: {
                editionType = "";
                break;
            }
        }

        return editionType;
    }

    bool isCommunity()
    {
        DSysInfo::UosEdition edition = DSysInfo::uosEditionType();
        return (DSysInfo::UosEdition::UosCommunity == edition);
    }

    QString getGhostFileName(const QString &curTime)
    {
        // uos_20_Professional_1050_109_amd64_efi_20221016_101734.uimg
        static QString osEditionType = Utils::getOSEditionType();
        static QString majorVer = DSysInfo::majorVersion();
        static QString minorVer = DSysInfo::minorVersion();
        static QString buildVer = DSysInfo::buildVersion();
        static QString arch = Utils::getKernelArch();
        static bool isEFI = Utils::isEFIMode();
        QString bootMode = isEFI ? "efi" : "legacy";

        QString name = "uos";
        if (Utils::isCommunity()) {
            name = "deepin";
        }

        QString ghostName = QString("%1_%2_%3_%4").arg(name).arg(majorVer).arg(osEditionType).arg(minorVer);
        if (!buildVer.isEmpty()) {
            ghostName += "_" + buildVer;
        }

        if (!arch.isEmpty()) {
            ghostName += "_" + arch;
            if ("arm64" == arch) {
                QString hardwareType;
                QString sysProductName;
                QString huaweiTypeAlias;
                bool isHuawei = false;
                Utils::getPlatformInfo(hardwareType, sysProductName, huaweiTypeAlias, isHuawei);
                if (isHuawei) {
                    ghostName += "_" + huaweiTypeAlias;
                }
            }
        }

        ghostName += "_" + bootMode;

        if (!curTime.isEmpty()) {
            ghostName += "_" + curTime;
        }

        ghostName += ".uimg";

        return ghostName;
    }

    bool isLVM(bool &isEnrypted)
    {
        isEnrypted = false;
        //QString cmd = QString("lsblk --output TYPE,UUID | grep -E 'crypt|lvm'");
        QStringList args;
        args <<"--output"<< "TYPE,UUID";
        QString out;
        QString err;
        if (!Process::spawnCmd("lsblk", args, out, err)) {
            return false;
        }

        if (!out.contains("lvm")) {
            return false;
        }

        if (out.contains("crypt")) {
            isEnrypted = true;
        }

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
        QStringList outList = out.split("\n", Qt::SkipEmptyParts);
#else
        QStringList outList = out.split("\n", QString::SkipEmptyParts);
#endif

        QString fsTabOut;
        QStringList uuidFsTabList;
        if (Process::spawnCmd("cat", {"/etc/fstab"}, fsTabOut, err)) {
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
            QStringList uuidOutList = fsTabOut.split("\n", Qt::SkipEmptyParts);
#else
            QStringList uuidOutList = fsTabOut.split("\n", QString::SkipEmptyParts);
#endif
            for (QString &item : uuidOutList) {
                item = item.trimmed();
                if (item.startsWith("#") || !item.startsWith("UUID=")) {
                    continue;
                }

                int index = item.indexOf("=");
                item = item.right(item.length() - index - 1);
                uuidFsTabList<<item;
            }
        }

        for (QString &line : outList) {
            QString fsType = line.left(line.indexOf(" "));
            if ("lvm" == fsType) {
                QString uuid = line.right(line.length() - line.lastIndexOf(" ")).trimmed();
                // check uuid in fstab
                for (QString &item : uuidFsTabList) {
                    if (item.startsWith(uuid)) {
                        return true;
                    }
                }
            }
        }

        isEnrypted = false;
        return false;
    }

    bool getCpuInfo(CpuInfo &cpu)
    {
        QStringList args;
        QString cpuOut;
        QString err;
        if (!Process::spawnCmd("lscpu", args, cpuOut, err)) {
            qInfo()<<"CpuInfo::init call lscpu failed, err = "<<err;
            return false;
        }

        QStringList cpuList;
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
        cpuList = cpuOut.split("\n", Qt::SkipEmptyParts);
#else
        cpuList = cpuOut.split("\n", QString::SkipEmptyParts);
#endif

        for (auto line : cpuList) {
            line = line.trimmed();
            if (line.startsWith("Architecture:")) {
                cpu.arch = line.right(line.length() - line.indexOf(":") - 1);
                cpu.arch = cpu.arch.trimmed();
            } else if (line.startsWith("Byte Order:")) {
                cpu.byteOrder = line.right(line.length() - line.indexOf(":") - 1);
                cpu.byteOrder = cpu.byteOrder.trimmed();
            } else if (line.startsWith("CPU(s):")) {
                QString cpus = line.right(line.length() - line.indexOf(":") - 1);
                cpu.cpuNum = cpus.trimmed().toInt();
            } else if (line.startsWith("Model name:")) {
                cpu.modelName = line.right(line.length() - line.indexOf(":") - 1);
                cpu.modelName = cpu.modelName.trimmed();
            }
        }
        return true;
    }

    static void getMapInfoFromLshw(const QString &info, QPair<QString, QString> &pairInfo, const QString &ch)
    {
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
        QStringList infoList = info.split(ch, Qt::SkipEmptyParts);
#else
        QStringList infoList = info.split(ch, QString::SkipEmptyParts);
#endif
        if (infoList.size() == 2) {
            QString key = infoList[0].trimmed();
            QString val = infoList[1].trimmed();
            pairInfo = qMakePair(key, val);
        }
    }

     bool getLshwInfo(QList<QPair<QString, QString>> &lshwList)
     {
        QString cmd = QString("lshw");
        QStringList args;
        QString out;
        QString err;
        if (!Process::spawnCmd(cmd, args, out, err)) {
            qInfo()<<"getLshwInfo call lshw failed, err = "<<err;
            return false;
        }

        QStringList itemList = out.split("*-");
        for (auto &item : itemList) {
            if (item.startsWith("display")) {
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
                QStringList displayList = item.split("\n", Qt::SkipEmptyParts);
#else
                QStringList displayList = item.split("\n", QString::SkipEmptyParts);
#endif
                for (auto &display : displayList) {
                    display = display.trimmed();
                    QPair<QString, QString> displayPair;
                    getMapInfoFromLshw(display, displayPair, ":");
//                    qInfo()<<"display = "<<display;
                    if (!displayPair.first.isEmpty()) {
                        lshwList.append(displayPair);
                    }
                }
            }
        }

        return true;
     }

    bool getVGAFromLshw(QString &display)
    {
        display = "";
        QList<QPair<QString, QString>> lshwList;
        if (!getLshwInfo(lshwList)) {
            return false;
        }

        QStringList displayList;
        for (auto &lshwListItem : lshwList) {
            if (lshwListItem.first == "product") {
                displayList.append(lshwListItem.second);
            }
        }

        if (!displayList.isEmpty()) {
            display = displayList.join("\n ");
            return true;
        }

        return false;
    }

    QStringList getAllSystemBackupUUID(const QString &recoveryConf)
    {
        QStringList uuidList;
        if (!recoveryConf.endsWith(".ini")) {
            return uuidList;
        }

        // get all system backup partition uuid
        const QString uosRecoveryConf = "/" + UOS_RECOVERY_INI;
        QSettings settings(uosRecoveryConf, QSettings::IniFormat);
        settings.beginGroup(BACKUP_GROUP);
        QString deviceUUID = settings.value(BACKUP_DEVICE_UUID_KEY).toString();
        QString historyDeviceUUID = settings.value(BACKUP_HISTORY_DEVICE_UUID_KEY).toString();
        settings.endGroup();

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
        uuidList = historyDeviceUUID.split(",", Qt::SkipEmptyParts);
#else
        uuidList = historyDeviceUUID.split(",", QString::SkipEmptyParts);
#endif
        uuidList.append(deviceUUID);
        uuidList.removeDuplicates();

        return uuidList;
    }

    bool getFsTypeAndDevicePathByDir(const QString &dir, QString &fsType, QString &devicePath)
    {
        fsType = "";
        QString dirPath = dir;
        // if (dirPath.contains(" ")) {
        //     dirPath = QString("\"%1\"").arg(dirPath);
        // }

    //    QString cmd = QString("df -T %1 | awk 'END {print $1\":\"$2}'").arg(dirPath);
        QString output = "";
        QString err;
        if (!Process::spawnCmd("df", {"-T", dirPath}, output, err)) {
            qInfo()<<"getFsTypeByDir failed, err = "<<err<<", dir = "<<dir;
            return false;
        }

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
        QStringList outputList = output.split("\n", Qt::SkipEmptyParts);
#else
        QStringList outputList = output.split("\n", QString::SkipEmptyParts);
#endif
        for (QString &line : outputList) {
            if (!line.startsWith("/dev/")) {
                continue;
            }

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
            QStringList devItem = line.split(" ", Qt::SkipEmptyParts);
#else
            QStringList devItem = line.split(" ", QString::SkipEmptyParts);
#endif
            devicePath = devItem[0].trimmed();
            fsType = devItem[1].trimmed();
            break;
        }

        return true;
    }

    bool isDeviceRemoveable(const QString &devicePath)
    {
        //QString cmd = QString("lsblk --output HOTPLUG,PATH | grep %1").arg(devicePath);
        QStringList args;
        args <<"--output"<<"PATH,HOTPLUG";
        QString output = "";
        QString err;
        if (!Process::spawnCmd("lsblk", args, output, err)) {
            qInfo()<<"isDeviceRemoveable failed, err = "<<err;
            return false;
        }

        output = output.trimmed();
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
        QStringList outputList = output.split("\n", Qt::SkipEmptyParts);
#else
        QStringList outputList = output.split("\n", QString::SkipEmptyParts);
#endif
        for (QString &line : outputList) {
            if (!line.startsWith(devicePath)) {
                continue;
            }
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
            QStringList devItem = line.split(" ", Qt::SkipEmptyParts);
#else
            QStringList devItem = line.split(" ", QString::SkipEmptyParts);
#endif
            QString devName = devItem[0];
            if (devName == devicePath) {
                output = devItem[1].trimmed();
                break;
            }
        }

        return output == "1";
    }

    int getGhostPolicy(const QString &recoveryConf)
    {
        bool ok = false;
        QSettings settings(recoveryConf, QSettings::IniFormat);
        settings.beginGroup(GHOST_GROUP);
        int ghostPolicyVal = settings.value(GHOST_POLICY).toInt(&ok);
        settings.endGroup();

        if (ok) {
            return ghostPolicyVal;
        }

        return GhostPolicy::GHOST_POLICY_UNKOWN;
    }

    int getUILayoutType(int module)
    {
        int defaultLayout = -1; // invalid
        const QMap<int, QPair<QString, QVector<int>>> moduleLayoutMap = {
                // QVector 字段里第一个值要求都是VERTICAL的
                { UI::UI_BACKUP_MODULE, {
                        UI_LAYOUT_BACKUP, {UI::UI_LAYOUT_BACKUP_VERTICAL, UI::UI_LAYOUT_BACKUP_HORIZONTAL}
                    }
                },
                { UI::UI_RESTORE_MODULE, {
                        UI_LAYOUT_RESTORE, {UI::UI_LAYOUT_RESTORE_VERTICAL, UI::UI_LAYOUT_RESTORE_HORIZONTAL}
                    }
                },
                { UI::UI_ADVANCE_MODULE, {
                        UI_LAYOUT_ADVANCE, {UI::UI_LAYOUT_ADVANCE_VERTICAL, UI::UI_LAYOUT_ADVANCE_HORIZONTAL}
                    }
                }
        };

        if (!moduleLayoutMap.contains(module)) {
            return defaultLayout;
        }

        const QPair<QString, QVector<int>> &layoutPair = moduleLayoutMap[module];
        QString rootPath = "/";
        QString recoveryConf = rootPath + UOS_RECOVERY_INI;
        QSettings settings(recoveryConf, QSettings::IniFormat);
        settings.beginGroup(LAYOUT_GROUP);
        int layoutVal = settings.value(layoutPair.first).toInt();
        settings.endGroup();

        if (layoutPair.second.contains(layoutVal)) {
            defaultLayout = layoutVal;
        } else {
            // UI调整后，默认采用VERTICAL布局
            defaultLayout = layoutPair.second.first();
        }

        return defaultLayout;
    }

    bool isDirectionRTL()
    {
        static QString locale = QLocale::system().name();
        if ("ug_CN" == locale) {
            return true;
        }

        return false;
    }

    int getOSMajorVersion(const QString &osVersionPath)
    {
        int osVersion = 0;
        QSettings versionFile(osVersionPath, QSettings::IniFormat);
        versionFile.beginGroup("Version");
        osVersion = versionFile.value("MajorVersion").toInt();
        versionFile.endGroup();

        return osVersion;
    }

    bool isDeepinBootKitExist()
    {
        //QString cmd = QString("type deepin-boot-kit");
        QString output = "";
        QString err;
        if (!Process::spawnCmd("which", {"deepin-boot-kit"}, output, err)) {
            qInfo()<<"isDeepinBootKitExist failed, err = "<<err;
            return false;
        }
        return true;
    }

    bool isGhostOnly()
    {
        bool ghostToolOnly = false;
        QString rootPath = "/";
        QString recoveryConf = rootPath + UOS_RECOVERY_INI;
        int ghostPolicy = getGhostPolicy(recoveryConf);
        if (GhostPolicy::GHOST_POLICY_GHOST_TOOL == ghostPolicy) {
            ghostToolOnly = true;
        }

        return ghostToolOnly;
    }

    bool reboot(bool ignoreOthers, bool hasRootPrivilege)
    {
        if (hasRootPrivilege) {
            return Process::spawnCmd("systemctl", {"reboot", "-f"});
        }

        if (ignoreOthers) {
            return Process::spawnCmd("systemctl", {"reboot", "-i"});
        }

        return Process::spawnCmd("systemctl", {"reboot"});
    }

    quint64 getAllSystemBackupSize()
    {
        quint64 totalSize = 0;
        QStringList excludes;
        QString backupPath = "/recovery/backup/savePath.json";
        auto &obj = Utils::readPathfromJsonFile(backupPath);
        for (const auto &key : obj.keys()) {
            const QJsonObject &subObj = obj[key].toObject();
            for (const auto &subKey : subObj.keys()) {
                QString backupFilePath = subObj[subKey].toString();
                if (backupFilePath.startsWith("/media/")) {
                    continue;
                }

                QDir dir(backupFilePath);
                if (!dir.exists()) {
                    qWarning()<<"getAllSystemBackupSize not exist backupFilePath:"<<backupFilePath;
                    continue;
                }
                excludes << backupFilePath;
            }
        }

        if (!excludes.isEmpty()) {
            QStringList args;
            args <<"du"<<"-sb" <<excludes;
            QString out;
            QString error;
            if (!Process::spawnCmd("sudo", args, out, error)) {
                qWarning()<<"getAllSystemBackupSize spawnCmd failed, error:"<<error<<", out: "<<out;
            }

            const int outColSize = 2; // size, dirPath
            foreach(QString line, out.split("\n")) {
                QStringList col = line.split("\t");
                if (outColSize != col.size()) {
                    continue;
                }
                totalSize += col[0].trimmed().toULongLong();
            }
        }

        return totalSize;
    }

    bool getPlatformInfo(QString &hardwareType, QString &sysProductName, QString &huaweiTypeAlias, bool &isHuawei)
    {
        isHuawei = false;
        hardwareType = "";
        sysProductName = "";
        huaweiTypeAlias = "";
        QString cmd = QString("dmidecode");
        QStringList args;
        args <<"-s"<< "system-product-name";
        QString out;
        QString err;
        if (!Process::spawnCmd(cmd, args, out, err)) {
            qInfo()<<"getPlatformInfo call dmidecode -s failed, err = "<<err;
            return false;
        }

        sysProductName = out.trimmed(); // eg: KLVV-W5821, "HUAWEI QingYun L420  KLVV-W5821";
        int index = sysProductName.lastIndexOf(" ");
        if (-1 != index) {
            sysProductName = sysProductName.right(sysProductName.length() - index);
        }
        qInfo()<<"sysProductName: "<<sysProductName<<", out: "<<out;

        //cmd = QString("dmidecode -t 11 | grep -i \"String 4\" | awk '{print $3}'");
        args.clear();
        out = "";
        err = "";
        args <<"-t"<< "11";
        if (!Process::spawnCmd(cmd, args, out, err)) {
            qInfo()<<"getPlatformInfo call dmidecode -t failed, err = "<<err;
            return false;
        }

#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
        QStringList outList = out.split("\n", Qt::SkipEmptyParts);
#else
        QStringList outList = out.split("\n", QString::SkipEmptyParts);
#endif
        for (QString &line : outList) {
            line = line.trimmed();
            if (!line.startsWith("String ")) {
                continue;
            }

            if (line.startsWith("String 4")) {
                int i = line.indexOf(":");
                if (-1 != i) {
                    hardwareType = line.right(line.length() - i - 1).trimmed();
                    break;
                }
            }
        }

        //hardwareType = out.trimmed(); // eg: KVKA0
        qInfo()<<"hardwareType: "<<hardwareType;
        if (hardwareType.contains("KVK90") || hardwareType.contains("KVK90A")) {
            huaweiTypeAlias = "L410"; // KLU: KVK90, KVK90A, 对外统一L410
            isHuawei = true;
        } else if (hardwareType.contains("KVKA0")) {
            huaweiTypeAlias = "L420"; // KLV: KVKA0, 对外统一L420
            isHuawei = true;
        } else if (hardwareType.contains("PGK90") || hardwareType.contains("PGKA0")) {
            huaweiTypeAlias = "W515"; // PGV: PGK90, PGKA0, 对外统一W515
            isHuawei = true;
        } else if (hardwareType.contains("PWC30")) {
            huaweiTypeAlias = "W525"; // PGW: PWC30, 外统一W525
            isHuawei = true;
        } else if (hardwareType.contains("PGUX")) {
            huaweiTypeAlias = "PGUX"; // PGUX
            isHuawei = true;
        } else {
            huaweiTypeAlias = sysProductName;
        }

        return true;
    }

    bool getSwapPartitionSize(const QString &path, qint64 &size)
    {
        size = 0;
        //QString cmd = QString("lsblk --bytes --output PATH,SIZE | grep %1 | awk '{print $2}'").arg(path);
        QStringList args;
        args <<"--bytes"<<"--output"<<"PATH,SIZE";
        QString out;
        QString err;
        if (!Process::spawnCmd("lsblk", args, out, err)) {
            qInfo()<<"getSwapPartitionSize call lsblk failed, err = "<<err<<", out: "<<out;
            return false;
        }

        const int outColSize = 2; // PATH,SIZE
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
        QStringList outList = out.split("\n", Qt::SkipEmptyParts);
#else
        QStringList outList = out.split("\n", QString::SkipEmptyParts);
#endif
        foreach(QString line, outList) {
#if QT_VERSION >= QT_VERSION_CHECK(5,15,0)
                QStringList col = line.split(" ", Qt::SkipEmptyParts);
#else
                QStringList col = line.split(" ", QString::SkipEmptyParts);
#endif
            int cloSize = col.size();
            if (cloSize < outColSize) {
                continue;
            }
            QString devPath = col[0].trimmed();
            if (path == devPath) {
                QString devSize = col[cloSize - 1].trimmed();
                size = devSize.toULongLong();
                break;
            }
        }

        return true;
    }

    bool isEnableImmutableSystemWritable()
    {
        QStringList args;
        args <<"status" << "-j";
        QString out;
        QString err;

        bool isWritableEnabled = false;
        QFile writableBootedFile("/run/deepin-immutable-writable/booted");
        if (writableBootedFile.exists()) {
            if (!writableBootedFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
                qWarning() << "failed to open /run/deepin-immutable-writable/booted: "
                           << writableBootedFile.errorString();
                return false;
            }

            QString fileContent = writableBootedFile.readAll().trimmed();
            if (fileContent == "overlay") {
                isWritableEnabled = true;
            }
        }

        qInfo()<<"isEnableImmutableSystemWritable, enable = "<<(isWritableEnabled ? "true" : "false");
        return isWritableEnabled;
    }
}
