// SPDX-FileCopyrightText: 2020 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "universalutils.h"
#include "fileutils.h"

#include <dfm-base/dfm_event_defines.h>
#include <dfm-base/base/schemefactory.h>

#include <DDBusSender>

#include <QDir>
#include <QUrl>
#include <QCoreApplication>
#include <QApplication>
#include <QThread>
#include <QDebug>
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
#    include <QX11Info>
#endif
#include <QFile>
#include <QProcess>
#include <QDBusConnectionInterface>
#include <QRegularExpression>
#include <DUtil>

#include <cmath>
#include <QDBusConnection>
#include <QDBusPendingCallWatcher>
#include <QDBusMessage>

#define SYSTEM_SYSTEMINFO_SERVICE "org.deepin.dde.SystemInfo1"
#define SYSTEM_SYSTEMINFO_PATH "/org/deepin/dde/SystemInfo1"
#define SYSTEM_SYSTEMINFO_INTERFACE "org.deepin.dde.SystemInfo1"

#define DEAMON_DOCK_SERVICE "org.deepin.dde.daemon.Dock1"
#define DEAMON_DOCK_PATH "/org/deepin/dde/daemon/Dock1"
#define DEAMON_DOCK_INTERFACE "org.deepin.dde.daemon.Dock1"

#define DDE_LOCKSERVICE_SERVICE "org.deepin.dde.LockService1"
#define DDE_LOCKSERVICE_PATH "/org/deepin/dde/LockService1"
#define DDE_LOCKSERVICE_INTERFACE "org.deepin.dde.LockService1"

#define DESKTOP_FILEMONITOR_SERVICE "org.deepin.dde.desktop.filemonitor"
#define DESKTOP_FILEMONITOR_PATH "/org/deepin/dde/desktop/filemonitor"
#define DESKTOP_FILEMONITOR_INTERFACE "org.deepin.dde.desktop.filemonitor"

#define IDLE_SCREEN_SAVER_SERVICE "org.freedesktop.ScreenSaver"
#define IDLE_SCREEN_SAVER_PATH "/org/freedesktop/ScreenSaver"
#define IDLE_SCREEN_SAVER_INTERFACE "org.freedesktop.ScreenSaver"

namespace dfmbase {

/*!
 * \brief send a messsage to Notification Center
 * \param msg
 */
void UniversalUtils::notifyMessage(const QString &msg)
{
    DDBusSender()
            .service("org.freedesktop.Notifications")
            .path("/org/freedesktop/Notifications")
            .interface("org.freedesktop.Notifications")
            .method(QString("Notify"))
            .arg(QObject::tr("dde-file-manager"))
            .arg(static_cast<uint>(0))
            .arg(QString("dde-file-manager"))
            .arg(msg)
            .arg(QString())
            .arg(QStringList())
            .arg(QVariantMap())
            .arg(5000)
            .call();
}

/*!
 * \brief send a messsage to Notification Center
 * \param title
 * \param msg
 */
void UniversalUtils::notifyMessage(const QString &title, const QString &msg)
{
    DDBusSender()
            .service("org.freedesktop.Notifications")
            .path("/org/freedesktop/Notifications")
            .interface("org.freedesktop.Notifications")
            .method(QString("Notify"))
            .arg(QObject::tr("dde-file-manager"))
            .arg(static_cast<uint>(0))
            .arg(QString("dde-file-manager"))
            .arg(title)
            .arg(msg)
            .arg(QStringList())
            .arg(QVariantMap())
            .arg(5000)
            .call();
}

/*!
 * \brief send a notification with action buttons to Notification Center
 * \param title the notification title (summary)
 * \param msg the notification body
 * \param actions alternating action id and label pairs, e.g. {"ok", "OK", "cancel", "Cancel"}
 * \param callback invoked when an action button is clicked; receives the action id
 */
void UniversalUtils::notifyMessage(const QString &title, const QString &msg,
                                   const QStringList &actions, const QVariantMap &hints)
{
    if (actions.isEmpty()) {
        notifyMessage(title, msg);
        return;
    }

    QDBusPendingCall pending = DDBusSender()
                                       .service("org.freedesktop.Notifications")
                                       .path("/org/freedesktop/Notifications")
                                       .interface("org.freedesktop.Notifications")
                                       .method(QString("Notify"))
                                       .arg(QObject::tr("dde-file-manager"))
                                       .arg(static_cast<uint>(0))
                                       .arg(QString("dde-file-manager"))
                                       .arg(title)
                                       .arg(msg)
                                       .arg(actions)
                                       .arg(hints)
                                       .arg(5000)
                                       .call();
}

/*!
 * \brief Determine if the current user is active or not
 * \return "State" property, we need it's "active" usunally
 */
QString UniversalUtils::userLoginState()
{
    QString state;
    QDBusInterface loginManager("org.freedesktop.login1",
                                "/org/freedesktop/login1/user/self",
                                "org.freedesktop.login1.User",
                                QDBusConnection::systemBus());
    QVariant replay = loginManager.property(("State"));
    if (replay.isValid())
        state = replay.toString();
    return state;
}

quint32 UniversalUtils::currentLoginUser()
{
    quint32 uid = UINT32_MAX;
    QDBusInterface loginManager("org.freedesktop.login1",
                                "/org/freedesktop/login1/user/self",
                                "org.freedesktop.login1.User",
                                QDBusConnection::systemBus());
    QVariant replay = loginManager.property(("UID"));
    if (replay.isValid())
        uid = replay.toULongLong();
    return uid;
}

bool UniversalUtils::isLogined()
{
    return userLoginState() == "active";
}

/*!
 * \brief UniversalUtils::inMainThread
 * \return true if in main thread
 */
bool UniversalUtils::inMainThread()
{
    return QThread::currentThread() == QCoreApplication::instance()->thread();
}

/*!
 * \brief FileUtils::blockShutdown 调用dbus去设置阻塞睡眠
 * \param replay 输入参数，dbus回复
 */
void UniversalUtils::blockShutdown(QDBusReply<QDBusUnixFileDescriptor> &replay)
{
    qCInfo(logDFMBase) << "Creating DBus inhibitor to block system shutdown";
    if (replay.value().isValid()) {
        qCWarning(logDFMBase) << "Shutdown block already active, skipping new request";
        return;
    }

    QDBusInterface loginManager("org.freedesktop.login1",
                                "/org/freedesktop/login1",
                                "org.freedesktop.login1.Manager",
                                QDBusConnection::systemBus());

    QList<QVariant> arg;
    arg << QString("shutdown:sleep")   // what
        << qApp->applicationDisplayName()   // who
        << QObject::tr("Files are being processed")   // why
        << QString("block");   // mode

    replay = loginManager.callWithArgumentList(QDBus::Block, "Inhibit", arg);
    qCInfo(logDFMBase) << "System shutdown block created successfully";
}

qint64 UniversalUtils::computerMemory()
{
    //! 从com.deepin.system.SystemInfo中获取实际安装的内存的大小
    QDBusInterface deepinSystemInfo(SYSTEM_SYSTEMINFO_SERVICE,
                                    SYSTEM_SYSTEMINFO_PATH,
                                    SYSTEM_SYSTEMINFO_INTERFACE,
                                    QDBusConnection::systemBus());
    // 部分数据优先从dbus读取
    // 获取安装的内存总量
    if (deepinSystemInfo.isValid())
        return static_cast<qint64>(deepinSystemInfo.property("MemorySize").toULongLong());

    return -1;
}

void UniversalUtils::computerInformation(QString &cpuinfo, QString &systemType, QString &edition, QString &version)
{
    QDBusInterface systemInfo(SYSTEM_SYSTEMINFO_SERVICE,
                              SYSTEM_SYSTEMINFO_PATH,
                              SYSTEM_SYSTEMINFO_INTERFACE,
                              QDBusConnection::sessionBus());

    if (systemInfo.isValid()) {
        //! 获取cpu信息
        cpuinfo = qvariant_cast<QString>(systemInfo.property("Processor"));
        //! 获取系统是64位还是32位
        systemType = QString::number(qvariant_cast<qint64>(systemInfo.property("SystemType"))) + QObject::tr("Bit");

        if (edition.isEmpty()) {
            edition = qvariant_cast<QString>(systemInfo.property("Version"));
            QStringList temp = edition.split(' ');
            if (temp.size() > 1) {
                version = temp[0];
                edition = temp[1];
            } else if (!temp.isEmpty()) {
                edition = temp[0];
            }
        }
    }
}

double UniversalUtils::sizeFormat(qint64 size, QString &unit)
{
    static const double kSizeStep = 1024.0;
    static const QStringList kUnits { "B", "KB", "MB", "GB", "TB", "PB" };

    double formatedSize = size;
    int loopCount = 0;
    while (formatedSize >= kSizeStep && loopCount < kUnits.count() - 1) {
        formatedSize /= kSizeStep;
        loopCount += 1;
    }
    unit = kUnits[loopCount];
    return formatedSize;
}

/*!
 * \brief UniversalUtils::sizeFormat, returns the most appropriate size string. the return's number part is less than 1024.
 * \param size
 * \param percision, how many bit after dot.
 * \return
 */
QString UniversalUtils::sizeFormat(qint64 size, int percision)
{
    QString unit;
    double numberPart = sizeFormat(size, unit);
    return QString("%1 %2").arg(QString::number(numberPart, 'f', percision)).arg(unit);
}

bool UniversalUtils::runCommand(const QString &cmd, const QStringList &args, const QString &wd)
{
    qCDebug(logDFMBase) << "Running command via Qt process (V2X mode):" << cmd << args;
    return QProcess::startDetached(cmd, args, wd);
}

int UniversalUtils::dockHeight()
{
    QDBusInterface deepinDockInfo(DEAMON_DOCK_SERVICE,
                                  DEAMON_DOCK_PATH,
                                  DEAMON_DOCK_INTERFACE,
                                  QDBusConnection::sessionBus());

    int dockHeight = 0;
    if (deepinDockInfo.isValid()) {
        QVariant temp = deepinDockInfo.property("WindowSizeEfficient");
        dockHeight = temp.toInt();
    }
    return dockHeight;
}

QMap<QString, QString> UniversalUtils::getKernelParameters()
{
    QFile cmdline("/proc/cmdline");
    cmdline.open(QIODevice::ReadOnly);
    QByteArray content = cmdline.readAll();

    QByteArrayList paraList(content.split(' '));

    QMap<QString, QString> result;
    result.insert("_ori_proc_cmdline", content);

    for (const QByteArray &onePara : paraList) {
        int equalsIdx = onePara.indexOf('=');
        QString key = equalsIdx == -1 ? onePara.trimmed() : onePara.left(equalsIdx).trimmed();
        QString value = equalsIdx == -1 ? QString() : onePara.right(equalsIdx).trimmed();
        result.insert(key, value);
    }

    return result;
}

bool UniversalUtils::isInLiveSys()
{
    bool ret = false;
    static const QMap<QString, QString> &cmdline = getKernelParameters();
    if (cmdline.value("boot", "") == QStringLiteral("live"))
        ret = true;
    return ret;
}

QVariantHash UniversalUtils::convertFromQMap(const QVariantMap map)
{
    QVariantHash ret;
    auto iter = map.cbegin();
    while (iter != map.cend()) {
        ret.insert(iter.key(), iter.value());
        iter += 1;
    }
    return ret;
}

bool UniversalUtils::urlEquals(const QUrl &url1, const QUrl &url2)
{
    if (!url1.isValid() || !url2.isValid())
        return false;
    if (url1 == url2)
        return true;

    auto path1 { url1.path() }, path2 { url2.path() };
    if (!path1.endsWith("/"))
        path1.append("/");
    if (!path2.endsWith("/"))
        path2.append("/");

    if (url1.scheme() == url2.scheme() && path1 == path2 && url1.host() == url2.host())
        return true;
    return false;
}

bool UniversalUtils::urlEqualsWithQuery(const QUrl &url1, const QUrl &url2)
{
    if (!url1.isValid() || !url2.isValid())
        return false;
    if (url1 == url2)
        return true;

    auto path1 { url1.path() }, path2 { url2.path() };
    if (!path1.endsWith("/"))
        path1.append("/");
    if (!path2.endsWith("/"))
        path2.append("/");

    if (url1.scheme() == url2.scheme() && path1 == path2 && url1.host() == url2.host() && url1.query() == url2.query())
        return true;
    return false;
}

bool UniversalUtils::urlsTransformToLocal(const QList<QUrl> &sourceUrls, QList<QUrl> *targetUrls)
{
    Q_ASSERT(targetUrls);
    bool ret { false };

    for (const auto &url : sourceUrls) {
        if (url.scheme() == Global::Scheme::kFile) {
            targetUrls->append(url);
            continue;
        }

        auto info { InfoFactory::create<FileInfo>(url) };
        if (info && info->canAttributes(FileInfo::FileCanType::kCanRedirectionFileUrl)) {
            ret = true;
            targetUrls->append(info->urlOf(UrlInfoType::kRedirectedFileUrl));
        } else {
            targetUrls->append(url);
        }
    }

    return ret;
}

bool UniversalUtils::urlTransformToLocal(const QUrl &sourceUrl, QUrl *targetUrls)
{
    Q_ASSERT(sourceUrl.isValid());
    Q_ASSERT(targetUrls);
    bool ret { false };

    if (sourceUrl.scheme() == Global::Scheme::kFile) {
        *targetUrls = sourceUrl;
        return ret;
    }

    auto info { InfoFactory::create<FileInfo>(sourceUrl) };
    if (info && info->canAttributes(FileInfo::FileCanType::kCanRedirectionFileUrl)) {
        ret = true;
        *targetUrls = info->urlOf(UrlInfoType::kRedirectedFileUrl);
    } else {
        *targetUrls = sourceUrl;
    }

    return ret;
}

QString UniversalUtils::getCurrentUser()
{
    QString user;

    // 检查是否有这个系统dbus
    if (!checkDbusService(DDE_LOCKSERVICE_SERVICE, true))
        return user;

    QDBusInterface sessionManagerIface(DDE_LOCKSERVICE_SERVICE,
                                       DDE_LOCKSERVICE_PATH,
                                       DDE_LOCKSERVICE_INTERFACE,
                                       QDBusConnection::systemBus());

    if (sessionManagerIface.isValid()) {
        QDBusPendingCall call = sessionManagerIface.asyncCall("CurrentUser");
        call.waitForFinished();
        if (!call.isError()) {
            QDBusReply<QString> reply = call.reply();
            user = reply.value();
        }
    }

    return user;
}

void UniversalUtils::userChange(QObject *obj, const char *cslot)
{
    QDBusConnection::systemBus().connect(
            DDE_LOCKSERVICE_SERVICE,
            DDE_LOCKSERVICE_PATH,
            DDE_LOCKSERVICE_INTERFACE,
            "UserChanged",
            obj,
            cslot);
}

void UniversalUtils::prepareForSleep(QObject *obj, const char *cslot)
{
    QDBusConnection::systemBus().connect(
            "org.freedesktop.login1",
            "/org/freedesktop/login1",
            "org.freedesktop.login1.Manager",
            "PrepareForSleep",
            obj,
            cslot);
}

bool UniversalUtils::isNetworkRoot(const QUrl &url)
{
    return urlEquals(url, QUrl("network:///"));
}

bool UniversalUtils::isParentUrl(const QUrl &child, const QUrl &parent)
{
    auto parentStr = parent.toString();
    parentStr = parentStr.endsWith(QDir::separator()) ? parentStr : parentStr + QDir::separator();
    return child.toString().startsWith(parentStr);
}

bool UniversalUtils::isParentOnly(const QUrl &child, const QUrl &parent)
{
    auto childPath = child.path();
    childPath.chop(childPath.length() - childPath.lastIndexOf(QDir::separator()));
    auto parentUrl = child;
    parentUrl.setPath(childPath);

    return urlEquals(parent, parentUrl);
}

QString UniversalUtils::covertUrlToLocalPath(const QString &url)
{
    if (url.startsWith("/"))
        return url;
    else
        return QUrl(QUrl::fromPercentEncoding(url.toUtf8())).toLocalFile();
}

void UniversalUtils::boardCastPastData(const QUrl &sourcPath, const QUrl &targetPath, const QList<QUrl> &files)
{
    // 检查是否有这个系统dbus
    if (!checkDbusService(DESKTOP_FILEMONITOR_SERVICE, false))
        return;

    QDBusInterface fileMonitor(DESKTOP_FILEMONITOR_SERVICE,
                               DESKTOP_FILEMONITOR_PATH,
                               DESKTOP_FILEMONITOR_INTERFACE,
                               QDBusConnection::sessionBus());

    QList<QVariant> data;
    data.append(sourcPath.toString());
    data.append(targetPath.toString());
    QStringList fileNames;
    for (const auto &file : files) {
        fileNames.append(file.fileName());
    }

    data.append(fileNames);
    fileMonitor.asyncCallWithArgumentList(QStringLiteral("PrepareSendData"), data);
}

int UniversalUtils::getTextLineHeight(const QModelIndex &index, const QFontMetrics &fontMetrics)
{
    auto text = index.data(Global::ItemRoles::kItemFileDisplayNameRole).toString();
    return getTextLineHeight(text, fontMetrics);
}

int UniversalUtils::getTextLineHeight(const QString &text, const QFontMetrics &fontMetrics)
{
    // Use QFontMetricsF to avoid int truncation under fractional scaling (e.g. 125%).
    // QFontMetrics returns int which discards sub-pixel metrics, causing line spacing
    // to be too small and descenders (g, p, y, q) of the upper line to be clipped
    // by the next line's background/text.
    QFontMetricsF fmF(fontMetrics);
    const int fontHeight = static_cast<int>(std::ceil(fmF.height()));
    if (text.isEmpty())
        return fontHeight;

    const QRectF textRectF = fmF.boundingRect(text);
    const int tightHeight = static_cast<int>(std::ceil(textRectF.height()));
    if (tightHeight <= 0)
        return fontHeight;

    return qMax(fontHeight, tightHeight);
}

uint32_t UniversalUtils::lockScreenSaver()
{
    qCInfo(logDFMBase) << "UniversalUtils::lockScreenSaver create dbus to block computer screen saver!!!";

    // 检查是否有这个系统dbus
    if (!checkDbusService(IDLE_SCREEN_SAVER_SERVICE, false))
        return 0;

    QDBusInterface screenSaverManager(IDLE_SCREEN_SAVER_SERVICE,
                                      IDLE_SCREEN_SAVER_PATH,
                                      IDLE_SCREEN_SAVER_INTERFACE,
                                      QDBusConnection::sessionBus());

    QList<QVariant> arg;
    arg << qApp->applicationDisplayName()   // who
        << QObject::tr("Files are being processed");   // why;

    QDBusReply<uint32_t> reply = screenSaverManager.callWithArgumentList(QDBus::AutoDetect, "Inhibit", arg);
    if (reply.isValid()) {
        qCInfo(logDFMBase) << "Inhibition cookie:" << reply.value();
        return reply.value();
    }

    qCWarning(logDFMBase) << "UniversalUtils::lockScreenSaver Failed to inhibit screensaver:" << reply.error().message();

    return 0;
}

bool UniversalUtils::unlockScreenSaver(const uint32_t cookie)
{
    qCInfo(logDFMBase) << "UniversalUtils::unlockScreenSaver create dbus to unblock computer screen saver!!!";
    // 检查是否有这个系统dbus
    if (!checkDbusService(IDLE_SCREEN_SAVER_SERVICE, false))
        return false;

    QDBusInterface screenSaverManager(IDLE_SCREEN_SAVER_SERVICE,
                                      IDLE_SCREEN_SAVER_PATH,
                                      IDLE_SCREEN_SAVER_INTERFACE,
                                      QDBusConnection::sessionBus());

    QList<QVariant> arg;
    arg << cookie;   // cookie

    QDBusReply<void> reply = screenSaverManager.callWithArgumentList(QDBus::Block, "UnInhibit", arg);
    if (reply.isValid()) {
        qCInfo(logDFMBase) << "UniversalUtils::unlockScreenSaver call UnInhibit finished! ";
        return true;
    }

    qCWarning(logDFMBase) << "UniversalUtils::unlockScreenSaver Failed to uninhibit screensaver:" << reply.error().message();

    return false;
}

bool UniversalUtils::checkDbusService(const QString &service, bool isSystemDbus)
{
    QDBusConnectionInterface *interface { nullptr };
    if (isSystemDbus) {
        interface = QDBusConnection::systemBus().interface();
    } else {
        interface = QDBusConnection::sessionBus().interface();
    }

    if (!interface) {
        qCWarning(logDFMBase) << "UniversalUtils::checkDbusService"
                              << (isSystemDbus ? "system" : "session")
                              << "dbus interface is null, service = "
                              << service;
        return false;
    }

    if (!interface->isServiceRegistered(service).value()) {
        qCWarning(logDFMBase) << "UniversalUtils::checkDbusService"
                              << (isSystemDbus ? "system" : "session")
                              << "dbus have not sevice : "
                              << service;
        return false;
    }

    return true;
}

/**
 * @brief FileUtils::setDockDnDMimeData
 * Writes dock drag-and-drop metadata into \a mimeData for a single .desktop file.
 *
 * Sets "text/x-dde-dock-dnd-appid" to the application ID derived from \a url,
 * and "text/x-dde-dock-dnd-source" to \a source.
 * Does nothing if \a url does not refer to a .desktop file or the app ID cannot
 * be determined.
 *
 * @param mimeData  The QMimeData object to populate. Must not be null.
 * @param url       A local file URL pointing to a .desktop file.
 * @param source    The drag source identifier (e.g. "dde-desktop").
 */
void UniversalUtils::setDockDnDMimeData(QMimeData *mimeData, const QUrl &url, const QString &source)
{
    Q_ASSERT(mimeData);

    if (!FileUtils::isDesktopFile(url))
        return;

    if (auto info = InfoFactory::create<FileInfo>(url)) {
        const auto &appId = info->nameOf(NameInfoType::kCompleteBaseName);
        if (appId.isEmpty())
            return;

        mimeData->setData(QStringLiteral("text/x-dde-dock-dnd-appid"), appId.toUtf8());
        mimeData->setData(QStringLiteral("text/x-dde-dock-dnd-source"), source.toUtf8());
    }
}

}

#include "universalutils.moc"