/*
 * 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 "processinfo.h"
#include <QDateTime>
#include <QDebug>
#include <QApplication>
#include <QFile>
#include <QTimer>
#include <QThread>
#include <QDir>
#include <fstream>
#include <iostream>

bool ProcessInfo::operator==(const ProcessInfo &other)
{
    return  other.m_pid == this->m_pid &&
            other.m_childrenPid == this->m_childrenPid &&
            other.m_cgroup == this->m_cgroup &&
            other.m_wids == this->m_wids &&
            other.m_args == this->m_args &&
            other.m_tasks == this->m_tasks &&
            other.m_exe == this->m_exe &&
            other.m_cmdline == this->m_cmdline &&
            other.m_appType == this->m_appType &&
            other.m_desktopFileName == this->m_desktopFileName &&
            this->m_lockingCgroup == other.m_lockingCgroup &&
            this->m_launchPid == other.m_launchPid &&
            this->m_launchTimestamp == other.m_launchTimestamp;
}

ProcessInfo &ProcessInfo::operator=(const ProcessInfo &other)
{
    if (this != &other) {
        this->m_pid = other.m_pid;
        this->m_childrenPid = other.m_childrenPid;
        this->m_cgroup = other.m_cgroup;
        this->m_wids = other.m_wids;
        this->m_args = other.m_args;
        this->m_tasks = other.m_tasks;
        this->m_exe = other.m_exe;
        this->m_cmdline = other.m_cmdline;
        this->m_appType = other.m_appType;
        this->m_desktopFileName = other.m_desktopFileName;
        this->m_lockingCgroup = other.m_lockingCgroup;
        this->m_launchPid = other.m_launchPid;
        this->m_launchTimestamp = other.m_launchTimestamp;
    }
    return *this;
}

ProcessInfo::ProcessInfo(const ProcessInfo &proc)
{
    this->m_pid = proc.m_pid;
    this->m_childrenPid = proc.m_childrenPid;
    this->m_cgroup = proc.m_cgroup;
    this->m_wids = proc.m_wids;
    this->m_args = proc.m_args;
    this->m_tasks = proc.m_tasks;
    this->m_exe = proc.m_exe;
    this->m_cmdline = proc.m_cmdline;
    this->m_appStatus = proc.m_appStatus;
    this->m_appType = proc.m_appType;
    this->m_desktopFileName = proc.m_desktopFileName;
    this->m_lockingCgroup = proc.m_lockingCgroup;
    this->m_launchPid = proc.m_launchPid;
    this->m_launchTimestamp = proc.m_launchTimestamp;
}

ProcessInfo::ProcessInfo()
    : m_pid(-1)
    , m_cgroup(ProcessInfo::ForegroundProcess)
    , m_appStatus(ProcessInfo::Launching)
    , m_appType(ProcessInfo::Normal)
    , m_needingPolkitWindow(false)
    , m_lockingCgroup(false)
    , m_launchPid(-1)
    , m_launchTimestamp(0)
{

}

qint64 ProcessInfo::pid() const
{
    return m_pid;
}

void ProcessInfo::setPid(const qint64 &appPid)
{
    m_pid = appPid;
    setChildrenPid(m_pid);
}

ProcessInfo::AppCGroup ProcessInfo::cgroup() const
{
    return m_cgroup;
}

void ProcessInfo::setCGroup(const AppCGroup &appStatus)
{
    m_cgroup = appStatus;
}

QList<uint> ProcessInfo::wids() const
{
    return m_wids;
}

void ProcessInfo::setWids(const QList<uint> &appWids)
{
    m_wids = appWids;
}

void ProcessInfo::setAppStatus(AppStatus status)
{
    m_appStatus = status;
    if (status == ProcessInfo::Launching) {
        m_launchTimestamp = QDateTime::currentSecsSinceEpoch();
    }
}

void ProcessInfo::setLaunchPid(int pid)
{
    m_launchPid = pid;
}

int ProcessInfo::launchPid() const
{
    return m_launchPid;
}

void ProcessInfo::setLockCgroup(bool locking)
{
    m_lockingCgroup = locking;
#if 0
    if (locking) {
        QTimer::singleShot(15000, [this] {
            m_lockingCgroup = false;
        });
    }
#endif
}

bool ProcessInfo::lockingCgroup() const
{
    return m_lockingCgroup;
}

ProcessInfo::AppStatus ProcessInfo::appStatus() const
{
    return m_appStatus;
}

QString ProcessInfo::cmdline(int pid)
{
    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;
}

qint64 ProcessInfo::parentId(int pid)
{
    QString procStatus = QString("/proc/%1/status").arg(pid);
    QFile file(procStatus);
    if (!file.open(QIODevice::ReadOnly)) {
        qWarning() << "Get parent id failed, " << pid << file.errorString();
        return -1;
    }
    QTextStream ts(&file);
    QString line;
    while (ts.readLineInto(&line)) {
        if (line.contains("PPid:")) {
            QString strPpid = line.split(":").last();
            strPpid.remove(QChar::Space);
            return strPpid.toUInt();
        }
    }
    return -1;
}

void ProcessInfo::pushWid(const uint &wid)
{
    if (m_wids.contains(wid)) {
        return;
    }
    m_wids.push_back(wid);
}

void ProcessInfo::removeWid(const uint &wid)
{
    m_wids.removeOne(wid);
}

QStringList ProcessInfo::getArgs() const
{
    return m_args;
}

void ProcessInfo::setArgs(const QStringList &appArgs)
{
    m_args = appArgs;
}

QList<qint64> ProcessInfo::getTasks()
{
    setTasks(m_pid);
    return m_tasks;
}

void ProcessInfo::setTasks(const qint64 &appPid)
{
    m_tasks.clear();
    qDebug() << __FUNCTION__ << appPid;
    QList<qint64> appAllPids;
    appAllPids << appPid << m_childrenPid;
    for (auto pid : appAllPids) {
        QString tasksFilePath = "/proc/" + QString::number(pid) + "/task";
        QDir dir(tasksFilePath);
        // 取到所有的文件和文件名，但是去掉.和..的文件夹
        dir.setFilter(QDir::Dirs|QDir::Files|QDir::NoDotAndDotDot);
        if (!dir.exists()) {
            qWarning() << tasksFilePath << "is not exists!";
            continue;
        }
        // 判断目录中的信息是否为空
        QFileInfoList fileLists = dir.entryInfoList();
        if (fileLists.count() <= 0) {
            qWarning() << tasksFilePath << "info is entry!";
            continue;
        }
        // 将线程Id存放进m_tasks列表中
        for (int i=0; i<fileLists.count(); ++i) {
            m_tasks << fileLists.at(i).fileName().toInt();
        }
    }
}

QList<qint64> ProcessInfo::childrenPids()
{
    setChildrenPid(m_pid);
    return m_childrenPid;
}

void ProcessInfo::setChildrenPid(const qint64 &appPid)
{
    m_childrenPid.clear();
    findChildrenPids(appPid);
    qDebug() << "child child pids" << m_childrenPid;
}

void ProcessInfo::findChildrenPids(qint64 pid)
{
    QList<qint64> pids;
    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;
        }
        m_childrenPid << QString::fromStdString(appChildrenInfo).toInt();
        pids << QString::fromStdString(appChildrenInfo).toInt();
    }
    pclose(fp);
    if (pids.empty()) {
        return;
    }

    for (const auto &pid : pids) {
        findChildrenPids(pid);
    }
}

QDebug &operator<<(QDebug &dbg, const ProcessInfo &info)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "pid:" << info.pid()
                  << " wids:" << info.wids()
                  << " appType:" << info.appType()
                  << " args:" << info.getArgs()
                  << " cgroup:" << info.cgroup()
                  << " status:" << info.appStatus()
                  << " desktop:" << info.desktopFileName()
                  << " launchingTimestamp: " << info.launchTimestamp()
                  << " lockingCgroup: " << info.lockingCgroup();
    return dbg;
}
