/*
 * 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 "pressurewatcher.h"
#include <QMetaEnum>
#include <QDebug>

#include <sys/epoll.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

#include "common.h"

PressureWatcher::PressureWatcher(QObject *parent)
    : ResourceWatcher{parent}
    , m_stop(false)
{
    initTriggers();
}

void PressureWatcher::start()
{
    if (m_triggers.isEmpty()) {
        qWarning() << "PressureWatcher: pressure triggers is empty.";
        return;
    }

    struct epoll_event events[1024];
    int epfd = epoll_create(1);
    if (epfd < 0) {
        qWarning() << "epoll_create failed, " << strerror(errno);
        return;
    }

    qDebug() << "m_triggers" << m_triggers.first().resource;

    for (auto &trigger : m_triggers) {
        int fd = open(trigger.fileInterface.toLocal8Bit().data(), O_RDWR | O_NONBLOCK);
        if (fd < 0) {
            qWarning() << "open file " << trigger.fileInterface << " failed, " << strerror(errno);
            continue;
        }

        QByteArray ba = trigger.trigger.toLocal8Bit();
        const char *trigger2write = ba.data();
        if (write(fd, trigger2write, strlen(trigger2write) + 1) < 0) {
            qWarning() << "write failed, " << strerror(errno);
            continue;
        }   
        epoll_event ev;
        ev.data.fd = fd;
        ev.events = EPOLLPRI;
        ev.events |= EPOLLET;
        int result = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
        if (result < 0) {
            qWarning() << "epoll_ctl failed, " << strerror(errno);
            continue;
        }  
        trigger.fd = fd;
    }

    while (!m_stop) {
        qDebug() << "epoll wait ";
        int n = epoll_wait(epfd, events, 1024, -1);
        qDebug() << "epoll wait " << n;
        QMap<ConfManager::Resource, ConfManager::ResourceUrgency> triggersUrgency;
        for (int i=0; i<n; ++i) {
            if (events[i].events & EPOLLPRI) {
                qDebug() << "fd " << events[i].data.fd;
                PressureTrigger trig = trigger(events[i].data.fd);
                if (triggersUrgency.contains(trig.resource)) {
                    if (triggersUrgency.value(trig.resource) < trig.urgency) {
                        triggersUrgency[trig.resource] = trig.urgency;
                    }
                    continue;
                }
                
                triggersUrgency[trig.resource] = trig.urgency;
            }
        }
        handleWarningMessage(triggersUrgency);
        sleep(1);
    }
    for (auto const &trigger : qAsConst(m_triggers)) {
        close(trigger.fd);
    }
}

void PressureWatcher::stop()
{
    m_stop = true;
}

void PressureWatcher::initTriggers()
{
    ConfManager &confMgr = common::Singleton<ConfManager>::GetInstance();
    auto initTrigger = [this, &confMgr](ConfManager::Resource resouce) {    
        auto trigger = confMgr.pressureTriggers(resouce);
        auto triIt = trigger.constBegin();
        QMetaEnum meta = QMetaEnum::fromType<ConfManager::Resource>();
        QString strResouce(meta.valueToKey(resouce));
        QString fileInterface = QString("/proc/pressure/%1").arg(strResouce.toLower());
        while (triIt != trigger.constEnd()) {
            m_triggers.push_back({ resouce, triIt.key(), triIt.value(), fileInterface, -1 });
            ++ triIt;
        }
    };

    auto pressureResources = confMgr.pressureResouces();
    for (const auto &resource : pressureResources) {
        initTrigger(resource);
    }
}

PressureWatcher::PressureTrigger PressureWatcher::trigger(int fd)
{
    for (const auto &trigger : qAsConst(m_triggers)) {
        if (trigger.fd == fd) {
            return trigger;
        }
    }
    return PressureWatcher::PressureTrigger {};
}

void PressureWatcher::handleWarningMessage(QMap<ConfManager::Resource, ConfManager::ResourceUrgency> triggerUrgency)
{
    ConfManager &confMgr = common::Singleton<ConfManager>::GetInstance();
    QMetaEnum meta = QMetaEnum::fromType<ConfManager::ResourceUrgency>();
    auto it = triggerUrgency.constBegin();
    while (it != triggerUrgency.constEnd()) {
        int level = confMgr.resourceUrgencyIndex(it.value());
        if (level == -1) {
            qWarning() << "Get urgency index error, urgency is" << it.value();
            ++ it;
            continue;
        }
        ++ level;
        qDebug() << "level " << level << it.key();
        switch (it.key())
        {
        case ConfManager::CPU:
            Q_EMIT ResourceThresholdWarning("CPU", level);
            break;

        case ConfManager::IO:
            Q_EMIT ResourceThresholdWarning("IO", level);
            break;       
        
        case ConfManager::Memory:
            Q_EMIT ResourceThresholdWarning("Memory", level);
            break;  
        }
        ++ it;
    }
}
