/*
 * Copyright 2023 KylinSoft Co., Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */

#include "appinfomanager.h"
#include <QtConcurrent>
#include <KWindowSystem>
#include <KDesktopFile>
#include <windowmanager/windowmanager.h>
#include "utils/misc.h"
#include "core/appstatusmanager.h"
#include "common.h"

AppInfoManager::AppInfoManager(QObject *parent) 
    : QObject(parent)
    , m_appStatusManager(new AppStatusManager(this))
    , m_desktopFileWatcher(new QFileSystemWatcher(this))
{
    initDesktopExecsInfoWatcher();
    initConnections();
}

QString AppInfoManager::newAppInstance(const QString &desktopFile,
                                       const QStringList &args,
                                       const QList<int> &pids,
                                       appinfo::AppType type)
{
    qDebug() << current_func << desktopFile << pids;
    if (desktopFile.isEmpty() || pids.isEmpty()) {
        qWarning() << current_func << "desktopFile or forkPid is inValid!" << desktopFile << " " << pids;
        return QString();
    }
    QString appId = appIdByDesktopFile(desktopFile);
    if (appId.isEmpty()) {
        qWarning() << current_func << "desktopFile or forkPid is inValid!" << desktopFile << " " << pids;
        return QString();
    }

    QString cgroupName = (type == appinfo::Normal && !kNoCgroupApps.contains(desktopFileName(desktopFile))) ?
                m_appStatusManager->newAppInstance(appId, pids) : "";
    appinfo::AppInstance appInstance;
    appInstance.pids = pids;
    appInstance.args = args;
    appInstance.cgroupName = cgroupName;
    appInstance.instName = cgroupName.isEmpty() ?
                QString("kylin-app-manager/%1/instance_%2").arg(appId).arg(pids.last()) :
                cgroupName;
    appInstance.appRunningStaus = appinfo::Launching;
    appInstance.appStatus = Policy::ActiveAppAdj;
    appInstance.launchTimestamp = QDateTime::currentSecsSinceEpoch();
    if (appInfos.contains(appId)) {
        appInfos[appId].appendAppInstance(appInstance);
    } else {
        appinfo::AppInfo appInfo(appId, desktopFile, type);
        appInfo.appendAppInstance(appInstance);
        appInfos[appId] = appInfo;
    }
    return appInstance.instName;
}

QString AppInfoManager::desktopFile(int pid)
{
    auto appInfo = iterateThroughAppInstances([pid](const appinfo::AppInstance &instance)->bool {
        return instance.pids.contains(pid);
    });
    return appInfos.value(appInfo.first).desktopFile();
}

QString AppInfoManager::desktopFile(quint32 wid)
{
    auto appInfo = iterateThroughAppInstances([wid](const appinfo::AppInstance &instance)->bool {
        return instance.wids.contains(wid);
    });
    return appInfos.value(appInfo.first).desktopFile();
}

bool AppInfoManager::thawApp(int pid)
{
    auto appInfo = iterateThroughAppInstances([pid](const appinfo::AppInstance &instance)->bool {
        return instance.pids.contains(pid);
    });

    return m_appStatusManager->thawApp(appInfo.first, appInfo.second.cgroupName);
}

bool AppInfoManager::thawApp(quint32 wid)
{
    auto appInfo = iterateThroughAppInstances([wid](const appinfo::AppInstance &instance)->bool {
        return instance.wids.contains(wid);
    });

    return m_appStatusManager->thawApp(appInfo.first, appInfo.second.cgroupName);
}

QList<quint32> AppInfoManager::wids(const QString &desktopFile)
{
    if (desktopFile.isEmpty()) {
        qWarning() << current_func << "desktopFile is empty!";
        return QList<quint32> ();
    }
    QString appId = appIdByDesktopFile(desktopFile);
    if (appId.isEmpty() || !appInfos.contains(appId)) {
        qWarning() << current_func << "appId is empty!" << desktopFile;
        return QList<quint32> ();
    }
    QList<quint32> appWids;
    auto appInfo = appInfos.value(appId);
    for (int i=0; i < appInfo.instances().count(); ++i) {
        if (!appInfo.instances().at(i).wids.isEmpty()) {
            appWids << appInfo.instances().at(i).wids;
        }
    }    
    return appWids;
}

QList<quint32> AppInfoManager::wids(const QString &desktopFile, const QString &instName)
{
    if (desktopFile.isEmpty()) {
        qWarning() << current_func << "desktopFile is empty!";
        return QList<quint32> ();
    }
    QString appId = appIdByDesktopFile(desktopFile);
    if (appId.isEmpty() || !appInfos.contains(appId)) {
        qWarning() << current_func << "appId is empty!" << desktopFile;
        return QList<quint32> ();
    }
    auto appInfo = appInfos.value(appId);
    for (int i=0; i < appInfo.instances().count(); ++i) {
        if (appInfo.instances().at(i).instName == instName) {
            return appInfo.instances().at(i).wids;
        }
    }
    return QList<quint32> ();
}

QList<int> AppInfoManager::pids(const QString &desktopFile, const QString &instName)
{
    if (desktopFile.isEmpty()) {
        qWarning() << current_func << "desktopFile is empty!";
        return QList<int> ();
    }
    QString appId = appIdByDesktopFile(desktopFile);
    if (appId.isEmpty() || !appInfos.contains(appId)) {
        qWarning() << current_func << "appId is empty!" << desktopFile;
        return QList<int> ();
    }
    auto appInfo = appInfos.value(appId);
    for (int i=0; i < appInfo.instances().count(); ++i) {
        if (appInfo.instances().at(i).instName == instName) {
            return appInfo.instances().at(i).pids;
        }
    }
    return QList<int> ();
}

QString AppInfoManager::cmdline(int pid)
{
    if (pid <= 0) {
        qWarning() << current_func << "pid is inValid!" << pid;
        return QString();
    }
    QString cmdLineFile = QString("/proc/%1/cmdline").arg(pid);
    QFile file(cmdLineFile);
    if (!file.open(QIODevice::ReadOnly)) {
        qWarning() << "Get cmdline failed, " << pid << file.errorString();
        return QString();
    }
    QByteArray cmd = file.readAll();
    QString cmdLine;
    int startIndex = 0;
    for (int i=0; i<cmd.size(); ++i) {
        if (cmd.at(i) == '\0') {
            if (!cmdLine.isEmpty()) {
                cmdLine.push_back(" ");
            }
            cmdLine.push_back(cmd.mid(startIndex, i-startIndex));
            startIndex = i + 1;
        }
    }
    return cmdLine;
}

AppInfoManager::DesktopFileExecInfo AppInfoManager::desktopFilesExec() const
{
    return m_desktopsExec;
}

std::tuple<appinfo::RunningStatus, QString, bool> AppInfoManager::runningStatus(
        const QString &desktopFile, const QStringList &args) const
{
    if (desktopFile.isEmpty()) {
        return std::make_tuple(appinfo::RunningStatus::None, "", false);
    }

    QString appId = appIdByDesktopFile(desktopFile);
    if (!appInfos.contains(appId)) {
        return std::make_tuple(appinfo::RunningStatus::None, "", false);
    }

    bool hasLaunchingStatus = false;
    bool launchingTimeout = false;
    QString launchingInstName;
    auto appInfo = appInfos.value(appId);
    auto instances = appInfo.instances();
    for (const auto &instance : qAsConst(instances)) {
        if (args.isEmpty()) {
            if (instance.appRunningStaus == appinfo::RunningStatus::Running) {
                return std::make_tuple(appinfo::RunningStatus::Running, instance.instName, false);
            }
            if (instance.appRunningStaus == appinfo::RunningStatus::Launching) {
                if (instance.appRunningStaus == appinfo::RunningStatus::Launching) {
                    launchingTimeout = (QDateTime::currentSecsSinceEpoch() - instance.launchTimestamp > 5);
                }
                launchingInstName = instance.instName;
                hasLaunchingStatus = true;
            }
        } else if (instance.args == args) {
            if (instance.appRunningStaus == appinfo::RunningStatus::Launching) {
                launchingTimeout = (QDateTime::currentSecsSinceEpoch() - instance.launchTimestamp > 5);
            }
            return std::make_tuple(instance.appRunningStaus, instance.instName, launchingTimeout);
        }
    }

    if (hasLaunchingStatus) {
        return std::make_tuple(appinfo::RunningStatus::Launching, launchingInstName, launchingTimeout);
    }

    return std::make_tuple(appinfo::RunningStatus::None, "", false);
}

QString AppInfoManager::inhibit(const QString &desktopFile,
                                uint pid,
                                const QString &reason,
                                uint flags)
{
    QString appId = appIdByDesktopFile(desktopFile);
    if (appId.isEmpty()) {
        return QString();
    }

    if (flags & inhibitor::FrozenInhibitor) {
        thawApp((int)pid);
        QByteArray srcData = QString(appId +
            QString::number(QDateTime::currentMSecsSinceEpoch()) + QString::number(pid)).toLocal8Bit();
        QString cookie = QCryptographicHash::hash(srcData, QCryptographicHash::Md5).toHex().constData();
        qInfo() << "Add frozen inhibitor," << desktopFile << pid << reason;
        m_inhibitors.push_back({ appId, cookie, (int)pid, inhibitor::FrozenInhibitor });
        return cookie;
    }

    return QString();
}

void AppInfoManager::unInhibit(const QString &cookie)
{
    QString appId;
    appinfo::AppInstance appInstance;
    for (int i=0; i<m_inhibitors.count(); ++i) {
        if (m_inhibitors.at(i).cookie == cookie) {
            appId = m_inhibitors.at(i).appId;
            appInstance = appInstanceWithPid(m_inhibitors.at(i).pid);
            m_inhibitors.removeAt(i);
            break;
        }
    }
    if (appId.isEmpty()) {
        return;
    }
    m_appStatusManager->appStatusTimeOutCallback({ appId, appInstance.instName, appInstance.appStatus });
}

bool AppInfoManager::isInhibited(const QString &appId, const QString &instName)
{
    for (auto &inhibitor : qAsConst(m_inhibitors)) {
        if (appId != inhibitor.appId) {
            continue;
        }
        int pid = inhibitor.pid;
        auto ret = iterateThroughAppInstances([pid](const appinfo::AppInstance &instance)->bool {
            return instance.pids.contains(pid);
        });
        if (ret.first.isEmpty()) {
            continue;
        }

        if (ret.second.instName == instName) {
            return true;
        }
    }
    return false;
}

bool AppInfoManager::ThawApps()
{
    return m_appStatusManager->ThawApps();
}

void AppInfoManager::onActiveWindowChanged(quint32 currentWid, 
                                           int currentPid,
                                           quint32 preWid, 
                                           int prePid)
{
    Q_UNUSED(prePid)
    auto currentApp = appInstanceInfoWithWindowId(currentWid);
    auto preApp = appInstanceInfoWithWindowId(preWid);
    if (currentApp.second == preApp.second) {
        return;
    }
    bool preAppIsHidden = appInfos.value(preApp.first).appInstanceIsHidden(preApp.second);
    m_appStatusManager->activeAppChanged(currentApp.first, currentApp.second, 
                                         preApp.first, preApp.second,
                                         preAppIsHidden);
}

void AppInfoManager::onWindowAdded(quint32 wid, int pid)
{
    KWindowInfo winfo(wid, NET::WMAllProperties);
    qDebug() << current_func << wid << pid << winfo.desktopFileName() << winfo.visibleName();
    if (wid <= 0 || pid <= 0) {
        qWarning() << current_func << "wid or pid is inValid!" << wid << pid;
        return;
    }
    // kmre应用窗口添加时将应用desktop名称添加到map中
    qDebug() << current_func << wid << pid;
    QString appCmdline = cmdline(pid);
    // 过滤peony-qt-desktop桌面
    if (appCmdline.contains("peony-qt-desktop")) {
        return;
    }
    if (appCmdline.startsWith(kKmreAppMainWindow)) {
        QString androidAppName = findAndroidNameByWid(wid);
        if (androidAppName.isEmpty()) {
            androidAppName = findAndroidNameByPid(pid);
        }
        if (androidAppName.isEmpty()) {
            qWarning() << current_func << "androidAppName is empty!" << wid << pid;
            return;
        }

        QString fullDesktopName = desktopFullFilename(androidAppName);
        QString appId = appIdByDesktopFile(fullDesktopName);
        // kmre应用主进程被gio拉起来后就finish掉了，只留下kmre窗口进程
        addAppInfomations(fullDesktopName, appCmdline, wid, pid);
        return;
    }

    // 匹配现有保存的pid
    auto appIdInstance = appInstanceInfoWithPid(pid);
    if (!appIdInstance.first.isEmpty()) {
        updateInstanceInfos(appIdInstance, wid, pid);
        return;
    }

    // 从cgroup组内找pid
    QString cgroupName = m_appStatusManager->cgroupWithPid(pid);
    if (!cgroupName.isEmpty()) {
        appIdInstance = appInstanceInfoWithInstName(cgroupName);
        if (!appIdInstance.first.isEmpty()) {
            updateInstanceInfos(appIdInstance, wid, pid);
            return;
        }
    }

    // 匹配cmdline
    appIdInstance = findDesktopCmdline(pid);
    if (!appIdInstance.first.isEmpty()) {
        updateInstanceInfos(appIdInstance, wid, pid);
        return;
    }

    // 匹配全局desktop
    QString desktopFile = findGlobalDesktopFile(appCmdline);
    if (!desktopFile.isEmpty()) {
        addAppInfomations(desktopFile, appCmdline, wid, pid);
        return;
    }

    // dpkg匹配
    auto deskfilesExec = m_desktopsExec;
    desktopFile = desktopFileFromCmdline(appCmdline);
    if (desktopFile.isEmpty()) {
        qWarning() << current_func << "desktopFile is empty!" << appCmdline;
        return;
    }
    if (!deskfilesExec.contains(desktopFile)) {
        qWarning() << current_func << "deskfilesExec is not contains " << desktopFile;
        deskfilesExec[desktopFile] = {appCmdline, QStringList() << appCmdline};
    } else {
        deskfilesExec[desktopFile].first = appCmdline;
    }
    addAppInfomations(desktopFile, appCmdline, wid, pid);
}

void AppInfoManager::onWindowRemoved(quint32 wid)
{
    qWarning() << current_func << wid;
    if (wid == 0) {
        qWarning() << current_func << "wid or pid is inValid!" << wid;
        return;
    }

    auto ret = iterateThroughAppInstances([wid](const appinfo::AppInstance &instance)->bool {
        return instance.wids.contains(wid);
    });

    auto appId = ret.first;
    auto appInstance = ret.second;
    if (!appId.isEmpty()) {
        appInfos[appId].removeAppWid(appInstance.instName, wid);
        if (appInfos.value(appId).appInstanceWids(appInstance.instName).isEmpty()) {
            appInfos[appId].setInstanceRunningStatus(appInstance.instName, appinfo::Finished);
            m_appStatusManager->appWidsIsEmpty(appId, appInstance.instName);
        }
        return;
    }

    qWarning() << current_func << "Can not find the removed window: " << wid;
}

void AppInfoManager::onMprisDbusAdded(int pid, const QString &dbusService)
{
    if (pid <= 0 || dbusService.isEmpty()) {
        qWarning() << current_func << "pid or mpris dbus service is inValid!" << pid << dbusService;
        return;
    }
    // 应用启动就创建该dbus服务的话,通常情况下,该接口会先于onWindowAdded被调用
    auto appIdInstance = appInstanceInfoWithPid(pid);
    if (appIdInstance.first.isEmpty()) {
        m_tmpMprisDbus[pid] = dbusService;
        return;
    }
    appInfos[appIdInstance.first].setMprisDbus(appIdInstance.second, dbusService);
}

void AppInfoManager::updateDesktopExecsInfo(const QString &path)
{
    QDir dir(path);
    if (!dir.exists()) {
        return;
    }

    QStringList desktopFiles = dir.entryList(QStringList() << "*.desktop");
    for (const auto &desktopFileName : qAsConst(desktopFiles)) {
        QString deskopFile = path + "/" + desktopFileName;
        auto exec = desktopFileExec(deskopFile);
        {
            if (exec.first.isEmpty()) {
                qWarning() << current_func << "exec is empty!" << desktopFileName;
                continue;
            }
            QMutexLocker locker(&m_mutex);
            if (!m_desktopsExec.contains(deskopFile)) {
                m_desktopsExec[deskopFile] = exec;
            }
        }
    }
}

void AppInfoManager::onResourceThresholdWarning(Policy::Feature resource, Policy::ResourceUrgency level)
{
    m_appStatusManager->resourceThresholdWarning(resource, level);
}

void AppInfoManager::onTabletModeChanged(bool tabletMode)
{
    m_appStatusManager->tabletModeChanged(tabletMode);
}

void AppInfoManager::onChildPidFinished(const int &pid)
{
    qDebug() << current_func << pid;
    if (pid <= 0) {
        qWarning() << current_func << "pid is not valid!";
        return;
    }
    if (appInfos.isEmpty()) {
        qWarning() << current_func << "m_appsInfo is empty!" << pid;
        return;
    }
    auto appInfo = iterateThroughAppInstances([pid](const appinfo::AppInstance &instance)->bool {
        return instance.pids.contains(pid);
    });
    auto appId = appInfo.first;
    auto instanceName = appInfo.second.instName;
    if (appId.isEmpty()) {
        return;
    }
    appInfos[appId].removePid(instanceName, pid);
    qDebug() << " app finished.... " << instanceName << appInfos[appId].pids(instanceName).isEmpty();
    if (appInfos[appId].pids(instanceName).isEmpty()) {
        appInfos[appId].removeAppInstance(instanceName);
        m_appStatusManager->appFinished(appId, instanceName);
        qDebug() << " app finished.... " << instanceName;
    }
}

void AppInfoManager::onAboutToQuitApp()
{
    m_appStatusManager->aboutToQuitApp();
}

QString AppInfoManager::appIdByDesktopFile(const QString &desktopFile) const
{
    if (desktopFile.isEmpty()) {
        qWarning() << current_func << "desktopFile is empty!";
        return QString();
    }
    QString desktopFileName = desktopFullFilename(desktopFile);
    QFile file(desktopFileName);
    if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) {
        qWarning() << "file open error!" << desktopFile;
        file.close();
        return QString();
    }
    QByteArray hashData = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5);
    if (hashData.isEmpty()) {
        qWarning() << "hashData is empty!" << desktopFile;
        file.close();
        return QString();
    }
    file.close();
    return QString(hashData.toHex().constData());
}

QString AppInfoManager::desktopFileName(const QString &desktopFile)
{
    if (!desktopFile.endsWith(".desktop")) {
        return QString();
    }

    if (!QFile::exists(desktopFile)) {
        return desktopFile;
    }

    auto items = desktopFile.split("/");
    if (items.isEmpty()) {
        return desktopFile;
    }

    return items.last();
}

QPair<QString, QStringList> AppInfoManager::desktopFileExec(const QString &desktopFileName)
{
    if (desktopFileName.isEmpty()) {
        qWarning() << current_func << "desktopFileName is NULL.";
        return QPair<QString, QString>();
    }
    QString fullDesktopName = desktopFullFilename(desktopFileName);
    KDesktopFile desktop(fullDesktopName);
    QString exec = desktop.entryMap("Desktop Entry").value("Exec");
    if (exec.isEmpty()) {
        qWarning() << current_func << "Exec is empty!" << desktopFileName;
        return {QString(), QStringList()};
    }

    exec.remove(QRegExp("%."));
    exec.remove(QRegExp("^\""));
    exec.remove(QRegExp(" *$"));
    exec.remove(QRegExp("\""));

    QStringList execArgs = exec.split(" ");
    if (execArgs.count() > 0) {
        exec = execArgs.first();
    }
    return { exec, execArgs };
}

QString AppInfoManager::desktopFullFilename(const QString &desktopFileName) const
{
    QFile desktopFile(desktopFileName);
    if (desktopFile.exists()) {
        return desktopFileName;
    }
    QStringList desktopfilePaths = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
    for (auto &path : qAsConst(desktopfilePaths)) {
        if (desktopFileName.contains(path)) {
            return desktopFileName;
        } else if (QFile::exists(path + "/" + desktopFileName)) {
            return path + "/" + desktopFileName;
        }
    }
    return QString();
}

AppInfoManager::AppIdInstanceName AppInfoManager::appInstanceInfoWithWindowId(quint32 wid)
{
    auto ret = iterateThroughAppInstances([wid] (const appinfo::AppInstance &instance)->bool {
        return instance.wids.contains(wid);
    });
    return { ret.first, ret.second.instName };
}

AppInfoManager::AppIdInstanceName AppInfoManager::appInstanceInfoWithPid(int pid)
{
    auto ret = iterateThroughAppInstances([pid] (const appinfo::AppInstance &instance)->bool {
        return instance.pids.contains(pid);
    });
    return { ret.first, ret.second.instName };
}

AppInfoManager::AppIdInstanceName AppInfoManager::appInstanceInfoWithInstName(const QString &instName)
{
    auto ret = iterateThroughAppInstances([instName] (const appinfo::AppInstance &instance)->bool {
        return instance.instName == instName;
    });
    return { ret.first, ret.second.instName };
}

appinfo::AppInstance AppInfoManager::appInstanceWithPid(int pid)
{
    auto ret = iterateThroughAppInstances([pid] (const appinfo::AppInstance &instance)->bool {
        return instance.pids.contains(pid);
    });
    return ret.second;
}

QString AppInfoManager::findAndroidNameByWid(const quint32 &wid)
{
    if (wid <= 0) {
        qWarning() << current_func << "wid is not valid!";
        return QString();
    }
    if (!QDBusConnection::sessionBus().isConnected()) {
        qWarning() << current_func << "Cannot connect to the D-Bus session bus." << wid;
        return QString();
    }

   QDBusInterface kmreIface("cn.kylinos.Kmre.Window",
                        "/cn/kylinos/Kmre/Window",
                        "cn.kylinos.Kmre.Window",
                        QDBusConnection::sessionBus());
    if (!kmreIface.isValid()) {
       qWarning() << current_func << "kmreIface is not valid!" << wid;
       return QString();
    }
    QDBusReply<QString> reply = kmreIface.call("getAppNameFromWid", wid);
    if (!reply.isValid()) {
        qWarning() << current_func << "reply is not valid!" << wid;
        return QString();
    }
    if (!reply.value().endsWith(".desktop")) {
        return reply.value().append(".desktop");
    }
    return reply.value();
}

QPair<QString, QString> AppInfoManager::findDesktopCmdline(const int &pid)
{
    if (pid <= 0) {
        qWarning() << current_func << "pid is not valid!";
        return QPair<QString, QString>();
    }
    QString appCmdLine = cmdline(pid);
    if (appCmdLine.isEmpty()) {
        qWarning() << current_func << "appCmdLine is empty!" << pid;
        return QPair<QString, QString>();
    }
    if (appInfos.isEmpty()) {
        qWarning() << current_func << "appInfos is empty!" << pid;
        return QPair<QString, QString>();
    }
    auto it = appInfos.constBegin();
    while (it != appInfos.constEnd()) {
        auto appList = it.value();
        for (const auto &appInfo : appList.instances()) {
            QStringList execArgs = appInfo.args;
            QString exec;
            for (auto &subStr : execArgs) {
                // 网页版应用Exec中带"xdg-open",appCmdLine中不带需要去除
                if (subStr != "xdg-open") {
                    exec.append(subStr);
                    exec.append(" ");
                }
            }
            if (exec.endsWith(" ")) {
                exec.remove(exec.size() - 1, 1);
            }
            // 移除字符串首尾的空格
            exec = exec.trimmed();

            if (appCmdLine.contains(exec)) {
                return {it.key(), appInfo.instName};
            }
            // software center
            appCmdLine.remove("./");
            if (exec.endsWith(appCmdLine)) {
                return {it.key(), appInfo.instName};
            }
        }
        ++ it;
    }
    return QPair<QString, QString>();
}

QString AppInfoManager::findGlobalDesktopFile(const QString &cmdline)
{
    if (cmdline.isEmpty()) {
        qWarning() << current_func << "cmdline is empty!";
        return QString();
    }
    auto desktopsExec = m_desktopsExec;
    auto desktopExecIt = std::find_if(desktopsExec.constBegin(), desktopsExec.constEnd(),
                                      [cmdline](const QPair<QString, QStringList> &exec) {
        QString strExec;
        for (const auto &str : exec.second) {
            strExec.push_back(str);
            strExec.push_back(" ");
        }
        if (strExec.endsWith(" ")) {
            strExec.remove(strExec.size()-1, 1);
        }
        // eg. peony-qt-desktop vs peony
        // return cmdline.contains(strExec);
        return cmdline == strExec;
    });
    if (desktopExecIt != desktopsExec.constEnd()) {
        return desktopExecIt.key();
    }

    return QString();
}

QString AppInfoManager::desktopFileFromCmdline(QString cmdline)
{
    if (cmdline.isEmpty()) {
        qWarning() << current_func << "cmdline is empty!";
        return QString();
    }
    if (cmdline.contains("./")) {
        cmdline.remove("./");
    }
    QString packageName;
    if (!QDir::isAbsolutePath(cmdline)) {
        QString path = QProcessEnvironment::systemEnvironment().value("PATH");
        QStringList pathLists;
        pathLists << path.split(':');
        QString filePath;
        QString appCmdline = cmdline;
        for (int i=0; i<pathLists.size(); ++i) {
            filePath = pathLists.at(i) + "/" + appCmdline;
            QFileInfo file(filePath);
            if (!file.isFile()) {
                continue;
            }
            if (!pkgNameFromCmdline(cmdline).isEmpty()) {
                cmdline = filePath;
                packageName = pkgNameFromCmdline(cmdline).constFirst();
                break;
            }
        }
    } else {
        if (pkgNameFromCmdline(cmdline).isEmpty()) {
            qWarning() << current_func << "packageName is empty!" << cmdline;
            return QString();
        }
        packageName = pkgNameFromCmdline(cmdline).constFirst();
    }

    if (packageName.isEmpty()) {
        qWarning() << current_func << "packageName is empty!" << cmdline;
        return QString();
    }

    QStringList appDesktopName;
    std::string appPackageFiles = "dpkg -L " + packageName.toStdString();
    FILE *pfile = popen(appPackageFiles.c_str(), "r");
    char nameBuff[200] = "";
    for (int i = 0; fgets(nameBuff, 200, pfile) != NULL; ++i) {
        std::string appFileInfo = nameBuff;
        if (appFileInfo.empty()) {
            continue;
        }
        if (!QString::fromStdString(appFileInfo).trimmed().endsWith(".desktop")) {
            continue;
        }
        QStringList desktopDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
        for (auto &desktopPath : desktopDirs) {
            if (!QString::fromStdString(appFileInfo).trimmed().contains(desktopPath)) {
                continue;
            }
            if (isDesktopFileNoDisplay(QString::fromStdString(appFileInfo).trimmed())) {
                continue;
            }
            appDesktopName << QString::fromStdString(appFileInfo).trimmed();
        }
    }
    pclose(pfile);
    if (!appDesktopName.isEmpty() && appDesktopName.count() < 2) {
        return appDesktopName.first();
    }
    int cmdlineFirst = cmdline.lastIndexOf("/");
    QString appCmdline  = cmdline.right(cmdline.length()-cmdlineFirst-1);
    for (auto &desktopName : appDesktopName) {
        auto exec = desktopFileExec(desktopName);
        if (!exec.first.contains(appCmdline)) {
            continue;
        }
        if (exec.second.isEmpty()) {
            return desktopName;
        }

        for (QString itExec : exec.second) {
            if (itExec.contains(appCmdline)) {
                return desktopName;
            }
        }
        return desktopName;
    }
    return QString();
}

QStringList AppInfoManager::pkgNameFromCmdline(const QString &cmdline)
{
    if (cmdline.isEmpty()) {
        qWarning() << current_func << "cmdline is empty!";
        return QStringList();
    }
    QStringList packageName;
    std::string appPackageName = "dpkg -S " + cmdline.toStdString();
    FILE *pkgNameFp = popen(appPackageName.c_str(), "r");
    char pkgNameBuff[200] = "";
    for (int i = 0; fgets(pkgNameBuff, 200, pkgNameFp) != NULL; ++i) {
        std::string packNameInfo = pkgNameBuff;
        if (packNameInfo.empty()) {
            continue;
        }
        QStringList sListCode;
        sListCode << QString::fromStdString(packNameInfo).split(':');
        packageName << sListCode.first().trimmed();
    }
    pclose(pkgNameFp);
    return packageName;
}

bool AppInfoManager::isDesktopFileNoDisplay(const QString &desktopFileName)
{
    if (desktopFileName.isEmpty()) {
        qWarning() << current_func << "desktopFileName is empty!";
        return false;
    }
    QString fullDesktopName = desktopFullFilename(desktopFileName);
    KDesktopFile desktop(fullDesktopName);
    QString exec = desktop.entryMap("Desktop Entry").value("NoDisplay");
    if (exec.isEmpty()) {
        qWarning() << current_func << "NoDisplay is empty!";
        return false;
    }

    exec.remove(QRegExp("%."));
    exec.remove(QRegExp("^\""));
    exec.remove(QRegExp(" *$"));
    exec.remove(QRegExp("\""));
    if (exec == "true") {
        return true;
    }
    return false;
}

QList<int> AppInfoManager::findChildrenPids(const int &pid)
{
    if (pid <= 0) {
        qWarning() << current_func << "pid is invalid!";
        return QList<int> ();
    }
    QList<int> pids;
    QList<int> childrenPids;
    std::string appChildrenPid = "pgrep -P " + QString::number(pid).toStdString();
    FILE *fp = popen(appChildrenPid.c_str(), "r");
    char nameBuff[200] = "";
    for (int i = 0; fgets(nameBuff, 200, fp) != NULL; ++i) {
        std::string childrenInfo = nameBuff;
        std::string appChildrenInfo = std::string(childrenInfo.begin(), childrenInfo.begin() + childrenInfo.find('\n'));
        if (appChildrenInfo.empty()) {
            continue;
        }
        childrenPids << QString::fromStdString(appChildrenInfo).toInt();
        pids << QString::fromStdString(appChildrenInfo).toInt();
    }
    pclose(fp);
    if (pids.empty()) {
        qDebug() << current_func << "child pid is " << childrenPids;
        return childrenPids;
    }

    for (const auto &pid : qAsConst(pids)) {
        findChildrenPids(pid);
    }
    return QList<int> ();
}

void AppInfoManager::updateAppInfomations(const QString &fullDesktopName, const quint32 &wid, const int &pid)
{ 
    if (fullDesktopName.isEmpty() || wid <= 0 || pid <= 0) {
        qWarning() << current_func << "fullDesktopName wid or pid is inValid!" << fullDesktopName << wid << pid;
        return;
    }
    QString appId = appIdByDesktopFile(fullDesktopName);
    if (!appInfos.contains(appId)) {
        qWarning() << "appInfos is not contains " << appId;
        return;
    }
    QString desktopName = fullDesktopName.trimmed().split("/").last();
    auto appIdInstance = appInstanceInfoWithPid(pid);
    if (appIdInstance.first.isEmpty()) {
        qWarning() << current_func << "appId is empty!" << fullDesktopName;
        return;
    }
    QString appInstName = appIdInstance.second;
    appInfos[appId].appendAppWid(appInstName, wid);
    appInfos[appId].setInstanceRunningStatus(appInstName, appinfo::Running);
    if (isKmreApp(fullDesktopName)) {
        appInfos[appId].setInstanceAppType(appInstName, appinfo::Kmre);
        Q_EMIT appLaunched(desktopName, pid, uint(wid));
        qDebug() << current_func << "appLaunched signal " << desktopName << wid << pid;
        return;
    }
    appInfos[appId].setInstanceAppType(appInstName, appinfo::Normal);
    Q_EMIT appLaunched(desktopName, pid, wid);
    qDebug() << current_func << "appLaunched signal " << desktopName << wid << pid;
}

void AppInfoManager::addAppInfomations(const QString &fullDesktopName, const QString &cmdline, const quint32 &wid, const int &pid)
{
    if (fullDesktopName.isEmpty() || cmdline.isEmpty() || wid <= 0 || pid <= 0) {
        qWarning() << current_func << "fullDesktopName cmdline wid or pid is inValid!" << fullDesktopName << cmdline << wid << pid;
        return;
    }
    QString appId = appIdByDesktopFile(fullDesktopName);
    QList<int> childrenPids = findChildrenPids(pid);
    childrenPids.append(pid);
    QString instName;
    QString desktopName = fullDesktopName.trimmed().split("/").last();
    if (isKmreApp(fullDesktopName)) {
        instName = newAppInstance(fullDesktopName, QStringList() << cmdline, QList<int>() << childrenPids, appinfo::Kmre);
    } else {
        instName = newAppInstance(fullDesktopName, QStringList() << cmdline, QList<int>() << childrenPids, appinfo::Normal);
    }
    appInfos[appId].appendAppWid(instName, wid);
    appInfos[appId].setInstanceRunningStatus(instName, appinfo::Running);
    Q_EMIT appLaunched(desktopName, pid, wid);
    qDebug() << current_func << "appLaunched signal " << desktopName << wid << pid;
}

bool AppInfoManager::isKmreApp(const QString &fullDesktopName)
{
    if (fullDesktopName.isEmpty()) {
        qWarning() << current_func << "fullDesktopName is empty!";
        return false;
    } else {
        QScopedPointer<QSettings> desktop(new QSettings(fullDesktopName, QSettings::IniFormat));
        desktop->setIniCodec(QTextCodec::codecForName("UTF-8"));
        QString categories = desktop->value("Desktop Entry/Categories").toString();
        categories.remove(QRegExp("%."));
        categories.remove(QRegExp("^\""));
        categories.remove(QRegExp(" *$"));
        return categories.contains("Android") || categories.contains("Apk");
    }
    return false;
}

void AppInfoManager::updateInstanceInfos(const AppIdInstanceName &appIdInstance, const quint32 &wid, const int &pid)
{
    if (wid <= 0 || pid <= 0) {
        qWarning() << current_func << "wid or pid is inValid!" << wid << pid;
        return;
    }
    QString appId = appIdInstance.first;
    QString instName = appIdInstance.second;
    if (!appId.isEmpty() && !instName.isEmpty()) {
        QString desktopName = appInfos[appId].desktopFile().trimmed().split("/").last();
        appInfos[appId].appendAppWid(instName, wid);
        appInfos[appId].setInstanceRunningStatus(instName, appinfo::Running);
        if (m_tmpMprisDbus.contains(pid)) {
            appInfos[appId].setMprisDbus(instName, m_tmpMprisDbus.value(pid));
            m_tmpMprisDbus.remove(pid);
        }
        appInfos[appId].appendPid(instName, pid);
        Q_EMIT appLaunched(desktopName, pid, wid);
        qDebug() << current_func << "appLaunched signal " << desktopName << wid << pid;
    }
}

QString AppInfoManager::findAndroidNameByPid(const quint32 &pid)
{
    if (pid <= 0) {
        qWarning() << current_func << "pid is inValid!";
        return QString();
    }
    QString appCmdline = cmdline(pid);
    QString startStr = "-p";
    QString endStr = "-f";
    int startIndex = appCmdline.indexOf(startStr);
    int endIndex = appCmdline.indexOf(endStr);
    if (startIndex == -1 || endIndex == -1 || startIndex+2 > appCmdline.size() || endIndex-startIndex-2 > appCmdline.size()) {
        qWarning() << current_func << "appCmdline index is inValid!" << pid << appCmdline;
        return QString();
    }
    QString androidName = appCmdline.mid(startIndex + 2, endIndex - startIndex - 2).trimmed().split("/").last();
    if (!androidName.endsWith(".desktop")) {
         androidName.push_back(".desktop");
    }
    return androidName;
}

QPair<QString, appinfo::AppInstance> AppInfoManager::iterateThroughAppInstances(
        const std::function<bool (const appinfo::AppInstance &)> &callback)
{
    for (auto &appInfo : qAsConst(appInfos)) {
        const auto &instances = appInfo.instances();
        for (auto &instance : qAsConst(instances)) {
            if (callback(instance)) {
                return { appInfo.appId(), instance };
            }
        }
    }
    return QPair<QString, appinfo::AppInstance>();
}

void AppInfoManager::initDesktopExecsInfoWatcher()
{
    QStringList desktopDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
    qDebug() << desktopDirs;
    auto appExecFunction = [desktopDirs, this]() {
        for (const auto &desktopDir : desktopDirs) {
            updateDesktopExecsInfo(desktopDir);
        }
    };
    QtConcurrent::run(appExecFunction);
    m_desktopFileWatcher->addPaths(desktopDirs);
}

void AppInfoManager::initConnections()
{
    connect(m_desktopFileWatcher, &QFileSystemWatcher::directoryChanged,
            this, &AppInfoManager::updateDesktopExecsInfo);
}
