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

ConfManager::ConfManager(QObject *parent)
    : QObject{parent}
{
    confFileWatcher = new QFileSystemWatcher(QStringList() << kConfFilename, this);
    connect(confFileWatcher, &QFileSystemWatcher::fileChanged, this, &ConfManager::readConf);
    readConf();
}

QStringList ConfManager::features() const
{
    return m_features; 
}

QList<ConfManager::Resource> ConfManager::pressureResouces() const
{
    QList<ConfManager::Resource> resouces = m_pressureTriggers.keys();
    for (auto &resource : qAsConst(resouces)) {
        switch (resource)
        {
        case CPU:
            if (!m_features.contains("cpu")) {
                resouces.removeOne(resource);
            }
            break;
        case IO: 
            if (!m_features.contains("io")) {
                resouces.removeOne(resource);
            }
            break;
        case Memory: 
            if (!m_features.contains("memory")) {
                resouces.removeOne(resource);
            }
            break;
        }
    }

    return resouces;
}

QMap<ConfManager::ResourceUrgency, QString> ConfManager::pressureTriggers(Resource resource) const
{
    if (!m_pressureTriggers.contains(resource)) {
        return QMap<ConfManager::ResourceUrgency, QString>();
    }
    QMetaEnum meta = QMetaEnum::fromType<ResourceUrgency>();
    ResourceUrgencyThrehold threholds = m_pressureTriggers.value(resource);
    if (threholds.size() != meta.keyCount()) {
        return QMap<ConfManager::ResourceUrgency, QString>();
    }
    QMap<ConfManager::ResourceUrgency, QString> result;
    for (int i=0; i<meta.keyCount(); ++i) {
        int value = threholds.value(ResourceUrgency(meta.value(i)));
        qDebug() << value << meta.value(i) << threholds.contains(ConfManager::Low) << ResourceUrgency(meta.value(i)) << threholds;
        QString trigger = QString("some %1 1000000").arg(value / (double)100 * 1000000);
        result[ResourceUrgency(meta.value(i))] = trigger;
    }
    qDebug() << "result" << result;
    return result;
}

ConfManager::ResourceUrgencyThrehold ConfManager::resourceThreshold(Resource resource) const
{
    if (!m_resourceThresholds.contains(resource)) {
        return ResourceUrgencyThrehold();
    }
    return m_resourceThresholds[resource];
}

int ConfManager::detectEffectiveNumber(Resource resource) const
{
    if (!m_detectEffectiveNumbers.contains(resource)) {
        return 0;
    }
    return m_detectEffectiveNumbers.value(resource);
}

int ConfManager::resourceUrgencyIndex(ResourceUrgency urgency) const
{
    QMetaEnum urgencyMeta = QMetaEnum::fromType<ResourceUrgency>();
    for (int i=0; i<urgencyMeta.keyCount(); ++i) {
        if (urgencyMeta.value(i) == urgency) {
            return i;
        }
    }
    return -1;
}

void ConfManager::readConf()
{
    QFile file(kConfFilename);
    if (!file.open(QIODevice::ReadOnly)) {
       qWarning() << "open json file failed: " << kConfFilename << file.errorString();
       return;
    }

    QJsonDocument jdc(QJsonDocument::fromJson(file.readAll()));
    auto jsonObj = jdc.object();
    if (jsonObj.isEmpty() || !jsonObj.contains("rulesets")) {
       qWarning() << "Config file error, rulesets is empty.";
       return;
    }

    auto features = jsonObj["features"];
    updateFeatures(features.toObject());

    auto rulesets = jsonObj["rulesets"].toArray();
    if (rulesets.isEmpty()) {
       qWarning() << "Config file error, rulesets is empty.";
       return;
    }

    auto detectors = rulesets.first().toObject().value("detectors").toArray();
    if (detectors.isEmpty()) {
       qWarning() << "Config file error, no detector defined.";
       return;
    }
    for (const auto &detector : qAsConst(detectors)) {
        auto detectorObj = detector.toObject();
        if (detector.toObject().isEmpty()) {
            qWarning() << "Config file error, detectors is empty.";
            continue;
        }
        bool ok;
        QString resource = detectorObj.value("resource").toString();
        auto thresholds = detectorObj.value("threshold").toArray();
        int effectiveNumber = detectorObj.value("effectiveNumber").toInt();
        QString api = detectorObj.value("api").toString();
        qDebug() << "thresholds" << thresholds;
        updateResourceConfig(resource, api, thresholds, effectiveNumber);
    }
    qDebug() << "read conf" << m_pressureTriggers << m_resourceThresholds;
}

void ConfManager::updateFeatures(const QJsonObject &jsonObj)
{
    m_features.clear();
    if (jsonObj.isEmpty()) {
        qWarning() << "Features config is empty.";
        return;
    }
    auto it = jsonObj.constBegin();
    while (it != jsonObj.constEnd()) {
        if (it.value().toBool()) {
            m_features.push_back(it.key());
        }
        ++ it;
    }
    qDebug() << "features " << m_features;
}

void ConfManager::updateResourceConfig(const QString &resource,
                                       const QString &api,
                                       const QJsonArray &thresholds,
                                       const int &effectiveNumber)
{
    QMetaEnum urgencyMeta = QMetaEnum::fromType<ResourceUrgency>();
    QMetaEnum resouceMeta = QMetaEnum::fromType<Resource>();
    if (thresholds.size() != urgencyMeta.keyCount()) {
        qWarning() << "Config file thresholds error, " << thresholds << " " << urgencyMeta.keyCount();
        return;
    }
    int enumValue = resouceMeta.keyToValue(resource.toLocal8Bit().data());
    bool isResourcePressure = api.startsWith("/proc/pressure") ? true : false;

    QMap<ResourceUrgency, int> resources;
    for (int i=0; i<urgencyMeta.keyCount(); ++i) {
        resources[ResourceUrgency(urgencyMeta.value(i))] = thresholds.at(i).toInt();
    }

    m_detectEffectiveNumbers[Resource(enumValue)] = effectiveNumber;
    if (isResourcePressure) {
        m_pressureTriggers[Resource(enumValue)] = resources;
        // return;
    }

    m_resourceThresholds[Resource(enumValue)] = resources;
}
