/*
 * 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 "policy.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QFile>
#include <QMetaEnum>
#include <QFileSystemWatcher>
#include <QApplication>
#include <QtMath>
#include <QDebug>

Policy::Policy(QObject *parent) 
    : QObject(parent)
    , m_statusSwitchInterval(1800)
    , m_confFileWatcher(new QFileSystemWatcher(this))
{
    m_confFileWatcher->addPath(kConfFilename);
    readConf();
    connect(m_confFileWatcher, &QFileSystemWatcher::fileChanged, this, &Policy::readConf);
}

bool Policy::isWaylandPlatform()
{
    return QGuiApplication::platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive);
}

QList<Policy::Feature> Policy::features()
{

}

bool Policy::featureEnabled(bool tablet, Policy::Feature feat)
{

}

int Policy::statusSwitchInterval() const
{
    return m_statusSwitchInterval;
}

Policy::AppStatus Policy::appStatus(bool appAvtived, bool appMinimized)
{
    if (appAvtived) {
        return ActiveAppAdj;
    }

    if (!appMinimized) {
        return ForegroundAppAdj;
    }

    return BackendAppAdj;
}

Policy::AppStatus Policy::nextAppStatus(Policy::AppStatus currentStatus)
{
    QMetaEnum meta = QMetaEnum::fromType<AppStatus>();
    for (int i=0; i<meta.keyCount(); ++i) {
        if (currentStatus == (AppStatus)meta.value(i)) {
            int retIndex = (i+1) < meta.keyCount() ? i+1 : 0;
            return (AppStatus)meta.value(retIndex);
        }
    }
    return ActiveAppAdj;
}

Policy::AppStatus Policy::frozenStatus() const
{
    return CachedAppAdj;
}

Policy::AppStatus Policy::lastStatus() const
{
    return CachedAppAdj;
}

Policy::AppStatus Policy::suspendStatus() const
{
    return SuspendedAppAdj;
}

QList<QMap<QString, int>> Policy::actions(Feature feat, ResourceUrgency urgency)
{
    if (!m_actions.contains(feat)) {
        return QList<QMap<QString, int>>();
    }

    QList<QMap<QString, int>> actions;
    int nUrg = urgency;
    do {
       actions.push_back(m_actions.value(feat).value((ResourceUrgency)nUrg));
    } while(--nUrg >= 0);

    return actions;
}

int Policy::defaultCGroupVale(const QString &name)
{
    if (name == CGROUP_FREEZE) {
        return 0;
    }

    if (name == CPU_MAX) {
        return 100000;
    }

    if (name.endsWith("weight")) {
        return 100;
    }

    return -1;
}

void Policy::readConf()
{
    QFile confFile(kConfFilename);
    if (!confFile.open(QIODevice::ReadOnly)) {
        qWarning() << "Open config file failed, " << confFile.errorString();
        return;
    }
    QJsonDocument jsonDoc = QJsonDocument::fromJson(confFile.readAll());
    QJsonObject jsonObj = jsonDoc.object();
    if (jsonObj.isEmpty()) {
        qWarning() << "Config file error, the json object is empty. " << jsonObj;
        return;
    }

    m_statusSwitchInterval = jsonObj.value("statusSwitchInterval").toInt();
    if (m_statusSwitchInterval == 0) {
        m_statusSwitchInterval = 1800;
    }

    auto ruleSets = jsonObj.value("rulesets").toObject();
    if (ruleSets.isEmpty()) {
        qWarning() << "Config file error, the 'rulesets' is empty.";
        return;
    }

    if (!ruleSets.contains("actions")) {
        qWarning() << "Config file error, there is no 'actions' key.";
        return;
    }
    auto actions = ruleSets.value("actions").toArray();
    if (actions.isEmpty()) {
        qWarning() << "Config file error, 'actions' is empty.";
        return;
    }

    QMetaEnum featureMeta = QMetaEnum::fromType<Feature>();
    QMetaEnum urgencyMeta = QMetaEnum::fromType<ResourceUrgency>();
    for (auto const &action : qAsConst(actions)) {
        auto actionObj = action.toObject();
        if (actionObj.isEmpty()) {
            qWarning() << "Config file error, action " << action << " is empty.";
            continue;
        }

        if (!actionObj.contains("resource")) {
            qWarning() << "Config file error, there is no 'resource' key.";
            continue;
        }
        auto resource = actionObj.value("resource");
        int enumResource = featureMeta.keyToValue(resource.toString().toLocal8Bit().data());
        if (enumResource == -1) {
            qWarning() << "Config file error, cat not detect the 'resource' key: " << resource;
            continue;
        }

        auto it = actionObj.constBegin();

        CGroupUrgencyStatValue urgencyStatValue;
        while (it != actionObj.constEnd()) {
            if (it.key() == "resource") {
                ++ it;
                continue;
            }
            auto urgencyValus = it.value().toArray();
            if (urgencyValus.size() != urgencyMeta.keyCount()) {
                qWarning() << "Config file error, urgemcy values count error, " << urgencyValus;
                ++ it;
                continue;
            }

            for (int i=0; i<urgencyMeta.keyCount(); ++i) {
                StatNameValue statValue;
                statValue[it.key()] = urgencyValus.at(i).toInt();
                urgencyStatValue[(ResourceUrgency)urgencyMeta.value(i)].insert(it.key(), urgencyValus.at(i).toInt());
            }
            ++ it;

        }
        m_actions[(Feature)enumResource] = urgencyStatValue;
    }
}
