/*
 * Copyright 2022 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 2 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 thi1s program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "procinfo.h"
#include <QApplication>
#include <QFile>
#include <QTimer>
#include <QThread>
#include "windowmanager/windowmanager.h"
#include <QProcess>
#include <QDebug>
#include <QDir>
#include <fstream>
#include <iostream>
using namespace kdk;

ProcInfo::ProcInfo(quint32 pid, QObject *parent)
    : QObject(parent)
    , m_pid(pid)
    , m_procPath("/proc/" + QString::number(m_pid))
{
}

bool ProcInfo::isShell(int pid)
{
    if (pid == -1) {
        qDebug() << "---isShell---" << pid;
    }
    QString exe = QFile::symLinkTarget("/proc/" + QString::number(pid) + "/exe");
    qDebug() << "---exe---" << exe;
    if (exe == "/usr/bin/bash" || exe == "/usr/bin/sh" || exe == "/usr/bin/gawk" ||
       (exe.contains("/usr/bin/python")) || exe == "/usr/bin/dash" ||
        exe == "/usr/bin/gjs-console" || exe == "/usr/bin/env" ||
        //针对软件商店做的单独处理
        exe == "/usr/share/kylin-software-center/kylin-software-center") {
        return true;
    }
    if (exe.isEmpty()) {
        std::ifstream inFile;
        std::string cmdline;
        std::string procCmdline = "/proc/" + std::to_string(pid) + ("/cmdline");
        inFile.open(procCmdline);
        if (!inFile.is_open()) {
            qWarning() << "Open file:" << procCmdline.c_str() << " failed";
            return false;
        }
        getline(inFile, cmdline);
        inFile.close();
        qDebug() << "---cmdline---" << cmdline.c_str();

        if (cmdline.find("/usr/sbin") == std::string::npos) {
            qDebug() << "---cmdline.find(/usr/bin)---不是脚本";
            return false;
        } else {
            if(cmdline.find("/bin/sh") != std::string::npos) {
                qDebug() << "---!cmdline.find(/usr/bin)---是脚本";
                return true;
            }
        }
    }
    return false;
}

bool ProcInfo::isKmreApp(int pid)
{
    QString exe = QFile::symLinkTarget("/proc/" + QString::number(pid) + "/exe");
    qDebug() << "检验是否为kmre应用ProcInfo::isKmreApp---exe---" << exe;
    QString appCmdline = getCmdlineFromPid(pid);
    if (exe.contains("/usr/bin/startapp") ||
        exe.contains("/usr/bin/kylin-kmre-window") ||
        appCmdline.contains("/usr/bin/kylin-kmre-window")) {
        qDebug () << __FUNCTION__ << "确实是kmre应用";
        return true;
    } else {
        return false;
    }
}

bool ProcInfo::isTerminalApp(int pid)
{
    qDebug() << "检验是否为终端应用ProcInfo::isTerminalApp";
    if (pid <= 0) {
        qWarning() << __FUNCTION__ << "!!!!!!pid不合法！！！！！！";
        return false;
    }
    if (getCmdlineFromPid(pid) == ("/usr/bin/mate-terminal-e")) {
       qDebug() << __FUNCTION__ << " is Terminal App!";
       return true;
    }
    return false;
}

bool ProcInfo::isTencentApp(int pid)
{
//    qDebug() << "检验是否为腾讯应用ProcInfo::isTencentApp";
    if (pid <= 0) {
        qWarning() << __FUNCTION__ << "!!!!!!pid不合法！！！！！！";
        return false;
    }
    if (getCmdlineFromPid(pid).contains("TXEduPlatformRuntime")) {
       qDebug() << __FUNCTION__ << " is isTencent App!";
       return true;
    }
    return false;
}

QString ProcInfo::getCmdlineFromPid(int pid)
{
    if (pid <= 0) {
        qWarning() << __FUNCTION__ << "!!!!!!pid不合法！！！！！！";
        return QString();
    }
    std::ifstream inFile;
    std::string cmdline;
    std::string procCmdline = "/proc/" + std::to_string(pid) + ("/cmdline");
    inFile.open(procCmdline);
    if (!inFile.is_open()) {
        qWarning() << "Open file:" << procCmdline.c_str() << " failed";
        return QString();;
    }
    getline(inFile, cmdline);
    inFile.close();
//    qDebug() << __FUNCTION__ << "---cmdline---" << cmdline.c_str();
    if (cmdline.find("-e") != std::string::npos) {
        qDebug() << "---/usr/bin/mate-terminal-e---" << QString("/usr/bin/mate-terminal-e");
        return QString("/usr/bin/mate-terminal-e");
    } else {
        return cmdline.c_str();
    }
}

int ProcInfo::getPid(int pid)
{
    qDebug() << "---getPid---" << pid;
    QString childrenFilename = "/proc/" + QString::number(pid) + "/task/" + QString::number(pid) + "/children";
    qDebug() << "open guiAppPid children file " << childrenFilename;
    QFile childrenFile(childrenFilename);
    if (!childrenFile.open(QFile::ReadOnly)) {
        qWarning() << "open children file " << childrenFilename << "failed: " << childrenFile.errorString();
    }

    QString symLinkTargetPPidExe;
    QStringList childrenPPids;
    int guiPPid;
    int counter = 0;

    do {
        QThread::msleep(1);
        childrenFile.seek(0);
        childrenPPids = QString(childrenFile.readAll()).split(QChar::Space);
        symLinkTargetPPidExe = QFile::symLinkTarget("/proc/" + childrenPPids.first() + "/exe");

        guiPPid = childrenPPids.first().toUInt();
        qDebug() << "---symLinkTargetPPidExe---" << symLinkTargetPPidExe << "---guiPPid---" << guiPPid;

        ++ counter;
        if (counter > 5000) {
            return -1;
        }

        if (symLinkTargetPPidExe.isEmpty() && (guiPPid != 0)) {

            if (isWindow(guiPPid) && !isShell(guiPPid)) {
                qDebug() << "---guiPPid--是窗口,不是脚本" << guiPPid;
                return guiPPid;
            } else {
                if (isWindow(guiPPid) && isShell(guiPPid)) {
                    qDebug() << "-迭代寻找到-guiPPid--是脚本";
                    return getPid(guiPPid);
                }
            }
        }
        // 确保 KWindowSystem::windows() 能实时拿到所有的 wid
        qApp->processEvents();
        // to do (判断guiPPid是0还是null)
    } while ((symLinkTargetPPidExe == "/usr/bin/bash"
             || symLinkTargetPPidExe == "/usr/bin/sh"
             || (symLinkTargetPPidExe.contains("/usr/bin/python"))
             || symLinkTargetPPidExe == "/usr/bin/gjs-console"
             || symLinkTargetPPidExe == "/usr/bin/dash"
             || symLinkTargetPPidExe == "/usr/bin/startapp"
             || symLinkTargetPPidExe == "/usr/bin/ps"
             || symLinkTargetPPidExe.isEmpty()
             || !isWindow(guiPPid)
             || (symLinkTargetPPidExe.contains("/usr/bin/gawk"))) &&
              childrenFile.seek(0));
    return guiPPid;
}

int ProcInfo::getTerAppPPid(int pid)
{
    qDebug() << "---getPid---" << pid;
    QString childrenFilename = "/proc/" + QString::number(pid) + "/task/" + QString::number(pid) + "/children";
    qDebug() << "open guiAppPid children file " << childrenFilename;
    QFile childrenFile(childrenFilename);
    if (!childrenFile.open(QFile::ReadOnly)) {
        qWarning() << "open children file " << childrenFilename << "failed: " << childrenFile.errorString();
    }
    QString symLinkTargetPPidExe;
    QStringList childrenPPids;
    int guiPPid;
    int counter = 0;

    do {
        QThread::msleep(1);
        childrenFile.seek(0);
        childrenPPids = QString(childrenFile.readAll()).split(QChar::Space);
        symLinkTargetPPidExe = QFile::symLinkTarget("/proc/" + childrenPPids.first() + "/cmdline");

        ++ counter;
        if (counter > 5000) {
            return -1;
        }

        guiPPid = childrenPPids.first().toUInt();
        if (guiPPid == 0) {
            continue;
        }
        qDebug() << "---symLinkTargetPPidExe---" << symLinkTargetPPidExe << "---guiPPid---" << guiPPid;

        if (isWindow(guiPPid)) {
            return guiPPid;
        }
        // 确保 KWindowSystem::windows() 能实时拿到所有的 wid
        qApp->processEvents();
        // to do (判断guiPPid是0还是null)
    } while ((!isWindow(guiPPid)) && guiPPid == 0);
    return guiPPid;
}

QString ProcInfo::status()
{
    QFile file(m_procPath + "/status");
    if (!file.open(QFile::ReadOnly)) {
        qWarning() << "open children file " << file.fileName() << "failed: " << file.errorString();
        return QString();
    }
    return file.readAll();
}

int ProcInfo::guiAppPid()
{
    qDebug() << "---m_pid---" << m_pid;
    // 第一次判断是否为脚本方式启动
    if (!isShell(m_pid)) {
        return m_pid;
    }

    QString statusFile = m_procPath + "/status";
    QFile file(statusFile);
    if (!file.open(QFile::ReadOnly)) {
        qWarning() << "-1-open children file " << statusFile << "failed: " << file.errorString();
        return -1;
    }

    QString stateInfo;
    stateInfo = file.readLine();
    while (file.canReadLine() && !stateInfo.contains("State")) {
        stateInfo = file.readLine();
    }
    int counter = 0;
    bool isExistChirdPid = false;
    // 如果是脚本启动的话，先保证脚本已经运行完成，即处于'sleeping'的状态
    do {
        QThread::msleep(1);
        stateInfo = file.readLine();
        while (file.canReadLine() && !stateInfo.contains("State")) {
            stateInfo = file.readLine();
        }
        ++ counter;
        if (counter > 5000) {
            return -1;
        }
    } while(!stateInfo.contains("sleeping") && file.seek(0));

    QString childrenFilename = m_procPath + "/task/" + QString::number(m_pid) + "/children";
    qDebug() << "open children file " << childrenFilename;
    QFile childrenFile(childrenFilename);
    if (!childrenFile.open(QFile::ReadOnly)) {
        isExistChirdPid = true;
        qWarning() << "-2-open children file " << childrenFilename << "failed: " << childrenFile.errorString();
//        return -1;
    }
    if (isExistChirdPid) {
        QList<quint64> appPidList;
        qint64 appPid;
        appPidList = ProcInfo::getChildrenPid(m_pid);
        do {
            ++ counter;
            isExistChirdPid = false;
            if (counter > 5000) {
                return -1;
            }
            if (ProcInfo::getChildrenPid(m_pid).count() <= 0) {
                return -1;
            }
            for (int i=0; i <= appPidList.count(); ++i) {
                appPid = appPidList.at(i);
                if (appPid <= 0) {
                    continue;
                }
                if (!isShell(appPid)) {
                    return appPid;
                }
            }
        } while(isShell(appPid));
    }

    QString symLinkTarget;
    QStringList childrenPids;
    int guiAppPid;
    do {
        // 第二次判断是否为脚本(针对脚本启动后被二进制进程顶替掉的方式例如奇安信浏览器)
        if(!isShell(m_pid)){
           return m_pid;
        }

        QThread::msleep(1);
        childrenFile.seek(0);
        childrenPids = QString(childrenFile.readAll()).split(QChar::Space);
        ++ counter;

        //判断新打开的应用进程文件路径是否存在
        QDir dir(m_procPath);
        if (!dir.exists()) {
            return -1;
        }
        if (counter > 5000) {
            qDebug() << "time ount: " << guiAppPid;
            return -1;
        }
        if (childrenPids.isEmpty()) {
            continue;
        }
        if (childrenPids.endsWith("")) {
            childrenPids.removeLast();
        }
        if (childrenPids.isEmpty()) {
            continue;
        }
        symLinkTarget = QFile::symLinkTarget("/proc/" + childrenPids.first() + "/exe");
        guiAppPid = childrenPids.first().toUInt();

        qDebug() << "---symLinkTarget---" << symLinkTarget << "---childrenPids.first()---" << childrenPids.first();
        // 第三次判断应用是否为脚本启动，(针对脚本启动获取涉及进程PPid的children的pid)
        if (!isWindow(guiAppPid)) {
           qDebug() << "---guiAppPid--不是窗口还得继续找";
           if (symLinkTarget.isEmpty() && isShell(guiAppPid)) {
               qDebug() << "---symLinkTarget--为NULL " << guiAppPid;
               return getPid(guiAppPid);
           }
        } else {
            qDebug() << "---guiAppPid--是窗口";
            return guiAppPid;
        }

        // 确保 KWindowSystem::windows() 能实时拿到所有的 wid
        qApp->processEvents();
    } while ((symLinkTarget == "/usr/bin/bash"
             || symLinkTarget == "/usr/bin/sh"
             || (symLinkTarget.contains("/usr/bin/python"))
             || symLinkTarget == "/usr/bin/gjs-console"
             || symLinkTarget == "/usr/bin/dash"
             || symLinkTarget == "/usr/bin/startapp"
             || !isWindow(guiAppPid)) &&
             childrenFile.seek(0));

    qDebug() << "---guiAppPid--为：" << guiAppPid;
    return guiAppPid;
}

bool ProcInfo::isWindow(int pid)
{
    qDebug() << " --isWindow--" << pid;
    if (pid <= 0) {
        return false;
    }

    // wayland 适配代码，目前kysdk中的windows()接口有问题，已提bug，待解决可正常使用
    QList<WindowId> windows = WindowManager::windows();
    for (int i = 0; i < windows.size(); ++i) {
         if (WindowManager::getPid(windows.at(i)) == pid) {
             qDebug()<<"---windowInfo.wid == wid---";
             return true;
         }
    }
    return false;
}

bool ProcInfo::isXorg(int pid)
{
    if (pid <= 0) {
        return false;
    }
    QFile file(QString("/proc/%1/comm").arg(pid));
    if (!file.open(QIODevice::ReadOnly)) {
        qWarning() << "open proc info file failed: " << file.fileName();
        return false;
    }
    QString commInfo = file.readAll();
    if (commInfo == "Xorg\n") {
        return true;
    }
    return false;
}

QList<quint32> ProcInfo::pidOfCmdName(const QString &cmdName)
{
    QProcess p;
    int count = 0;
    for (;;) {
        ++ count;
        if (count > 5000) {
            break;
        }
        p.start("ps", QStringList() << "-C" << cmdName << "-o" << "pid=");
        if (p.waitForFinished()) {
            QList<quint32> rtPids;
            QString strPids = p.readAll();
            if (strPids.isEmpty()) {
                continue;
            }
            QStringList strListPids = strPids.split("\n");
            for (auto &pid : strListPids) {
                pid.remove(QChar::Space);
                if (pid.isEmpty()) {
                    continue;
                }
                rtPids.push_back(pid.toUInt());
            }
            return rtPids;
        }
        break;
    }
    return QList<quint32>();
}

QList<quint64> ProcInfo::getChildrenPid(const quint64 &pid)
{
    QList<quint64> 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'));
        childrenPids << QString::fromStdString(appChildrenInfo).toInt();
//        qDebug() << __FUNCTION__ << "---" << childrenPids[i];
    }
    pclose(fp);
    return childrenPids;
}

QList<quint64> ProcInfo::getTasksFromPid(const quint64 &appPid)
{
//    qDebug() << __FUNCTION__ << appPid;
    QString tasksFilePath = "/proc/" + QString::number(appPid) + "/task";

    QDir dir(tasksFilePath);

    //取到所有的文件和文件名，但是去掉.和..的文件夹
    dir.setFilter(QDir::Dirs|QDir::Files|QDir::NoDotAndDotDot);
    if (!dir.exists()) {
        qWarning() << "exists";
        return QList<quint64>();
    }

    QFileInfoList list = dir.entryInfoList();
    if(list.size() < 1 ) {
        qWarning() << "error";
        return QList<quint64>();
    }

    QStringList string_list;
    QList<quint64> appTasksList;
    for (int i = 0; i < dir.count(); ++i) {
        //文件名称
        QString file_name = dir[i];
        string_list.append(file_name);
//        qDebug() << "--string_list--" << string_list[i].toInt();
        appTasksList << string_list[i].toInt();
    }
    return appTasksList;
}

