/*
 * 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 <http://www.gnu.org/licenses/>.
 */

#include <QDir>
#include <QFileInfo>
#include <QString>
#include <QStringList>
#include <QDebug>

#include "pcidevice.h"

static struct pci_access *g_pciAccess = NULL;

PciDevice::PciDevice(QObject *parent) : QObject(parent)
{
    m_controlFile = QString("/power/control");

    getPCIDevicePowerInfo();
    getBlockDevicePowerInfo();
}

void PciDevice::getAtaDevicePowerInfo(const QString &parentDir)
{
    QDir ataDeviceDir(parentDir);
    if(!ataDeviceDir.exists()) {
        return;
    }

    QStringList ataDeviceDirs = ataDeviceDir.entryList(QDir::Dirs).filter("ata");

    for (int i = 0; i < ataDeviceDirs.size(); ++i) {
        QString devicePath = parentDir + '/' + ataDeviceDirs.at(i);

        if (true == deviceHasRuntimePM(devicePath)) {
            QString devName = lookupPciDeviceName(devicePath);
            m_devicePowerInfo.push_back(new DevicePowerInfo("pci-ata", devicePath, devName, m_controlFile, this));
        }
    }
}

void PciDevice::getPCIDevicePowerInfo()
{
    QDir deviceDir(PCI_DEVICE_PATH);
    if(!deviceDir.exists()) {
        return;
    }

    QStringList deviceDirs = deviceDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
    for (int i = 0; i < deviceDirs.size(); ++i) {
        QString devicePath = PCI_DEVICE_PATH + deviceDirs.at(i);

        if (true == deviceHasRuntimePM(devicePath)) {
            QString devName = lookupPciDeviceName(devicePath);
            m_devicePowerInfo.push_back(new DevicePowerInfo("pci", devicePath, devName, m_controlFile, this));
        }

        getAtaDevicePowerInfo(devicePath);
    }
}

void PciDevice::getBlockDevicePowerInfo()
{
    QDir blockDeviceDir(PCI_BLOCK_DEVICE_PATH);
    if(!blockDeviceDir.exists()) {
        return;
    }

    QStringList blockDeviceDirs = blockDeviceDir.entryList(QDir::Dirs).filter("sd");
    for (int i = 0; i < blockDeviceDirs.size(); ++i) {
        QString controlFilePath = PCI_BLOCK_DEVICE_PATH + blockDeviceDirs.at(i) + "/device";

        if (true == deviceHasRuntimePM(controlFilePath)) {
            m_devicePowerInfo.push_back(new DevicePowerInfo("pci-block", controlFilePath, blockDeviceDirs.at(i), m_controlFile, this));
        }
    }
}

bool PciDevice::deviceHasRuntimePM(const QString &devPath)
{
    QFile pmFile(devPath + m_controlFile);
    if (!pmFile.exists()) {
        return false;
    }

    pmFile.setFileName(devPath + "/power/runtime_suspended_time");
    if (!pmFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qCritical() << "open file error:" << pmFile.fileName();
        return false;
    }

    ulong value = pmFile.readLine().toULong();
    pmFile.close();
    if (value) {
        return true;
    }

    pmFile.setFileName(devPath + "/power/runtime_active_time");
    if (!pmFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qCritical() << "open file error:" << pmFile.fileName();
        return false;
    }

    value = pmFile.readLine().toULong();
    pmFile.close();
    if (value) {
        return true;
    }

    return false;
}

QString PciDevice::lookupPciDeviceName(const QString &devPath)
{
    ulong vendorId = 0;
    ulong deviceId = 0;
    bool ok;

    QFile file(devPath + "/vendor");
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        vendorId = file.readLine().toULong(&ok, 16);
        file.close();
    }

    file.setFileName(devPath + "/device");
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        deviceId = file.readLine().toULong(&ok, 16);
        file.close();
    }

    char devName[512] = {0};
    char *pDevName = NULL;

    if (NULL == g_pciAccess) {
        g_pciAccess = pci_alloc();
        pci_init(g_pciAccess);
    }

    pDevName = pci_lookup_name(g_pciAccess, devName, 512, PCI_LOOKUP_VENDOR | PCI_LOOKUP_DEVICE, vendorId, deviceId);
    return QString(pDevName);
}

int PciDevice::getPCIDeviceNum()
{
    return m_devicePowerInfo.count();
}

QString PciDevice::getDeviceName(int deviceIndex)
{
    if (deviceIndex < m_devicePowerInfo.count()) {
        return m_devicePowerInfo[deviceIndex]->getDeviceName();
    }
    return "False";
}

QString PciDevice::getCurrentPowerStat(int deviceIndex)
{
    if (deviceIndex < m_devicePowerInfo.count()) {
        return m_devicePowerInfo[deviceIndex]->getCurrentPowerStat();
    }
    return "False";
}

QString PciDevice::getDefaultPowerStat(int deviceIndex)
{
    if (deviceIndex < m_devicePowerInfo.count()) {
        return m_devicePowerInfo[deviceIndex]->getDefaultPowerStat();
    }
    return "False";
}

bool PciDevice::setPowerStat(int deviceIndex, const QString &stat)
{
    if (stat == "auto" || stat == "on" || stat == "default") {
        if (deviceIndex < m_devicePowerInfo.count()) {
            return m_devicePowerInfo[deviceIndex]->setDevicePowerStat(stat);
        }
    }
    return false;
}

bool PciDevice::setAllDevicePowerStat(const QString &stat)
{
    if (stat == "auto" || stat == "on" || stat == "default") {
        for (int i = 0; i < m_devicePowerInfo.count(); ++i) {
            m_devicePowerInfo[i]->setDevicePowerStat(stat);
        }
        return true;
    }
    return false;
}

void PciDevice::printAllDeviceInfo()
{
    qDebug() << "pci device";
    for (int i = 0; i < m_devicePowerInfo.count(); ++i) {
        qDebug() << "name:" << m_devicePowerInfo[i]->getDeviceName()
                 << "path:" << m_devicePowerInfo[i]->getDevicePath()
                 << "stat:" << m_devicePowerInfo[i]->getDefaultPowerStat();
    }
}
