/******************************************************************************
 * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved.
 * libkperf licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v2 for more details.
 * Author: Mr.Ye
 * Create: 2024-04-03
 * Description: definitions of interfaces of querying and freeing pmu event list
 ******************************************************************************/
#include <iostream>
#include <cstring>
#include <dirent.h>
#include <functional>
#include <unordered_map>
#include <vector>
#include <mutex>
#include <fstream>
#include "core.h"
#include "evt.h"
#include "pcerr.h"
#include "pmu.h"
#include "common.h"

using namespace pcerr;
using namespace std;
using EvtQueryer = function<const char**(unsigned*, bool eventCheck)>;

static const string SLASH = "/";
static const string COLON = ":";
static const string EVENT_DIR = "/events/";

static std::mutex pmuEventListMtx;

#ifdef IS_X86
static vector<const char*> supportDevPrefixs = {"uncore_iio", "uncore_imc", "cpu", "amd_iommu", "power", "msr"};
#else
static vector<const char*> supportDevPrefixs = {"hisi", "smmuv3", "hns3", "armv8"};
#endif

static vector<const char*> uncoreEventList;
static vector<const char*> traceEventList;
static vector<const char*> coreEventList;
static vector<const char*> checkCoreEventList;

static void GetEventName(const string& devName, vector<const char*>& eventList)
{
    DIR* dir;
    struct dirent* entry;
    auto path = SYS_DEVICE_PATH + devName + EVENT_DIR;
    dir = opendir(path.c_str());
    if (dir == nullptr) {
        return;
    }
    while ((entry = readdir(dir)) != nullptr) {
        if (entry->d_type != DT_REG) { // Check if it is a regular file
            continue;
        }
        string fileName(entry->d_name);
#ifdef IS_X86
        // Included in x86 .scale .unit files not for events
        if (fileName.find('.') != string::npos) {
            continue;
        }
#endif
        auto eventName = devName;
        eventName += SLASH + fileName;
        eventName +=  SLASH;
        char* eventNameCopy = new char[eventName.length() + 1];
        strcpy(eventNameCopy, eventName.c_str());
        eventList.emplace_back(eventNameCopy);
    }
    closedir(dir);
}

static void GetTraceSubFolder(const std::string& traceFolder, const string& devName, vector<const char*>& eventList)
{
    struct dirent* entry;
    auto path = traceFolder + devName;
    DIR *dir = opendir(path.c_str());
    if (dir == nullptr) {
        return;
    }
    while ((entry = readdir(dir)) != nullptr) {
        if (entry->d_type != DT_DIR) { // Check if it is a regular folder
            continue;
        }
        string folderName(entry->d_name);
        if (folderName.find('.') == string::npos) {
            auto eventName = devName;
            eventName += COLON + folderName;
            char* eventNameCopy = new char[eventName.length() + 1];
            strcpy(eventNameCopy, eventName.c_str());
            eventList.emplace_back(eventNameCopy);
        }
    }
    closedir(dir);
}

static bool PerfEventSupported(__u64 type, __u64 config)
{
    perf_event_attr attr{};
    memset(&attr, 0, sizeof(attr));
    attr.size = sizeof(struct perf_event_attr);
    attr.type = type;
    attr.config = config;
    attr.disabled = 1;
    attr.inherit = 1;
    attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING | PERF_FORMAT_ID;
    int fd = KUNPENG_PMU::PerfEventOpen(&attr, -1, 0, -1, 0);
    if (fd < 0 && errno == ENOENT) {
        return false;
    }
    close(fd);
    return true;
}

const char **GetCoreEventList(unsigned *numEvt, bool eventCheck)
{
    if (eventCheck) {
        *numEvt = checkCoreEventList.size();
        return checkCoreEventList.data();
    }
    *numEvt = coreEventList.size();
    return coreEventList.data(); 
}

const char** QueryCoreEvent(unsigned *numEvt, bool eventCheck)
{
    if (!coreEventList.empty() && !eventCheck) {
        *numEvt = coreEventList.size();
        return coreEventList.data();
    }

    if (!checkCoreEventList.empty() && eventCheck) {
        *numEvt = checkCoreEventList.size();
        return checkCoreEventList.data();
    }

    auto coreEventMap = KUNPENG_PMU::CORE_EVENT_MAP.at(GetCpuType());
    for (auto& pair : coreEventMap) {
        auto eventName = pair.first;
        char* eventNameCopy = new char[eventName.length() + 1];
        strcpy(eventNameCopy, eventName.c_str());
        if (eventCheck && PerfEventSupported(pair.second.type, pair.second.config)) {
            checkCoreEventList.emplace_back(eventNameCopy);
        } else {
            coreEventList.emplace_back(eventNameCopy);
        }
    }
    DIR* dir;
    struct dirent* entry;
    auto pmuDevPath = GetPmuDevicePath();
    if (pmuDevPath.empty()) {
        return GetCoreEventList(numEvt, eventCheck);
    }
    string path = pmuDevPath + "/events/";
    dir = opendir(path.c_str());
    if (dir == nullptr) {
        return GetCoreEventList(numEvt, eventCheck);
    }
    while ((entry = readdir(dir)) != nullptr) {
        if (entry->d_type == DT_REG) {
            string evtName = entry->d_name;
            char* eventNameCopy = new char[evtName.length() + 1];
            strcpy(eventNameCopy, evtName.c_str());
            if (eventCheck) {
                checkCoreEventList.emplace_back(eventNameCopy);
            } else {
                coreEventList.emplace_back(eventNameCopy);
            }
        }
    }
    closedir(dir);
    return GetCoreEventList(numEvt, eventCheck);
}

const char** QueryUncoreEvent(unsigned *numEvt, bool eventCheck)
{
    if (!uncoreEventList.empty()) {
        *numEvt = uncoreEventList.size();
        return uncoreEventList.data();
    }
    DIR* dir;
    struct dirent* entry;
    dir = opendir(SYS_DEVICE_PATH.c_str());
    if (dir == nullptr) {
        return nullptr;
    }
    while ((entry = readdir(dir)) != nullptr) {
        if (entry->d_type != DT_DIR && entry->d_type != DT_LNK) {
            continue;
        }
        string folderName = entry->d_name;
        for (auto devPrefix: supportDevPrefixs) {
            if (folderName.find(devPrefix) == 0) {
                GetEventName(folderName, uncoreEventList);
                break;
            }
        }
    }
    closedir(dir);
    *numEvt = uncoreEventList.size();
    return uncoreEventList.data();
}

const char** QueryTraceEvent(unsigned *numEvt, bool eventCheck)
{
    if (!traceEventList.empty()) {
        *numEvt = traceEventList.size();
        return traceEventList.data();
    }
    struct dirent *entry;
    const string &traceFolder = GetTraceEventDir();
    if (traceFolder.empty()) {
        if (errno == EACCES) {
            New(LIBPERF_ERR_NO_PERMISSION, "no permission to access '/sys/kernel/tracing/events/' or '/sys/kernel/debug/tracing/events/'");
        } else {
            New(LIBPERF_ERR_INVALID_EVENT, "can't find '/sys/kernel/tracing/events/' or '/sys/kernel/debug/tracing/events/'");
        }
        return traceEventList.data();
    }
    DIR *dir = opendir(traceFolder.c_str());
    if (dir == nullptr) {
        return nullptr;
    }
    while ((entry = readdir(dir)) != nullptr) {
        if (entry->d_type == DT_DIR) {
            string folderName = entry->d_name;
            if (folderName.find('.') == string::npos) {
                string devName(entry->d_name);
                GetTraceSubFolder(traceFolder, devName, traceEventList);
            }
        }
    }
    closedir(dir);
    *numEvt = traceEventList.size();
    return traceEventList.data();
}

const char** QueryAllEvent(unsigned *numEvt, bool eventCheck) {
    unsigned coreNum, uncoreNum, traceNum;
    const char** coreList = QueryCoreEvent(&coreNum, eventCheck);
    const char** uncoreList = QueryUncoreEvent(&uncoreNum, eventCheck);
    const char** traceList = QueryTraceEvent(&traceNum, eventCheck);

    unsigned totalSize = 0;
    if (coreList != nullptr) {
        totalSize += coreNum;
    }
    if (uncoreList != nullptr) {
        totalSize += uncoreNum;
    }
    if (traceList != nullptr) {
        totalSize += traceNum;
    }

    const char** combinedList = new const char*[totalSize];
    unsigned index = 0;

    if (coreList != nullptr) {
        memcpy(combinedList, coreList, coreNum * sizeof(const char*));
        index += coreNum;
    }
    if (uncoreList != nullptr) {
        memcpy(combinedList + index, uncoreList, uncoreNum * sizeof(const char*));
        index += uncoreNum;
    }
    if (traceList != nullptr) {
        memcpy(combinedList + index, traceList, traceNum * sizeof(const char*));
    }

    *numEvt = totalSize;
    return combinedList;
}

static const unordered_map<int, EvtQueryer> QueryMap{
        {PmuEventType::CORE_EVENT, QueryCoreEvent},
        {PmuEventType::UNCORE_EVENT, QueryUncoreEvent},
        {PmuEventType::TRACE_EVENT, QueryTraceEvent},
        {PmuEventType::ALL_EVENT, QueryAllEvent},
};

const char** PmuEventList(enum PmuEventType eventType, unsigned *numEvt, bool eventNeedCheck) 
{
    lock_guard<mutex> lg(pmuEventListMtx);
    const char** eventList;
    if (QueryMap.find(eventType) == QueryMap.end()) {
        New(LIBPERF_ERR_QUERY_EVENT_TYPE_INVALID, "Event type is invalid.");
        return nullptr;
    }
    try {
        eventList = QueryMap.at(eventType)(numEvt, eventNeedCheck);
    } catch (...) {
        New(LIBPERF_ERR_QUERY_EVENT_LIST_FAILED, "Query event failed.");
        return nullptr;
    }
    return eventList;
}

const char** PmuEventList(enum PmuEventType eventType, unsigned *numEvt)
{
    return PmuEventList(eventType, numEvt, true);
}

static void PmuEventListFreeSingle(vector<const char*>& eventList)
{
    for (auto evt : eventList) {
        if (evt != nullptr && evt[0] != '\0')
            delete[] evt;
    }
    eventList.clear();
}

void PmuEventListFree()
{
    lock_guard<mutex> lg(pmuEventListMtx);
    PmuEventListFreeSingle(coreEventList);
    PmuEventListFreeSingle(checkCoreEventList);     
    PmuEventListFreeSingle(uncoreEventList);
    PmuEventListFreeSingle(traceEventList);
    New(SUCCESS);
}