#include <string>
#include <array>
#include <iostream>
#include <filesystem>
#include <thread>
#include <unistd.h>
#include "utils.h"

using std::cerr;
using std::cout;
using std::string;

namespace fs = std::filesystem;

class PidClosedError : public std::runtime_error
{
public:
    PidClosedError(const std::string &message)
        : std::runtime_error(message) {}
};

void printCpuStats(std::vector<string> pids)
{
    for (string pid : pids)
    {
        string path = "/proc/" + pid + "/task";

        if (!fs::exists(path))
        {
            throw PidClosedError("Directory does not exist: " + path);
        }

        for (const auto &entry : fs::directory_iterator(path))
        {
            string subProcessPath = entry.path().string() + "/stat";
            readFile(subProcessPath);
        }
    }
}

void printMemoryStats(std::vector<string> pids)
{
    for (string pid : pids)
    {
        string memoryFilePath = "/proc/" + pid + "/statm";
        readFile(memoryFilePath);
    }
}

long long totalDurationSum = 0;
long long measureCount = 0;

long long printPerformanceMeasure(std::vector<string> pids)
{
    auto start = std::chrono::system_clock::now();

    string separator = "=SEPARATOR=";
    log("=START MEASURE=");
    // Log the first pid as the main pid
    log(pids[0]);
    log(separator);
    printCpuStats(pids);
    auto cpuEnd = std::chrono::system_clock::now();
    log(separator);
    printMemoryStats(pids);
    auto memoryEnd = std::chrono::system_clock::now();
    log(separator);
    logTimestamp();

    auto end = std::chrono::system_clock::now();

    auto cpuDuration = std::chrono::duration_cast<std::chrono::milliseconds>(cpuEnd - start);
    auto memoryDuration = std::chrono::duration_cast<std::chrono::milliseconds>(memoryEnd - cpuEnd);
    auto totalDuration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

    long long totalDurationMs = totalDuration.count();

    cout << "TOTAL EXEC TIME: " << totalDuration.count() << "|";
    cout << "CPU TIME: " << cpuDuration.count() << "|";
    cout << "MEMORY TIME: " << memoryDuration.count() << "|";

    measureCount++;
    totalDurationSum += totalDurationMs;

    log(separator);
    cout << "AVERAGE TOTAL EXEC TIME: " << totalDurationSum / measureCount << "\n";

    log("=STOP MEASURE=");

    cout << std::flush;

    return totalDuration.count();
}

std::vector<string> pidOf(string bundleId)
{
    auto result = executeCommand("pidof " + bundleId);
    return split(result, ' ');
}

void pollPerformanceMeasures(std::string bundleId, int interval)
{
    pthread_setname_np(pthread_self(), "FL-Main");

    std::vector<string> pids;

    cout << "Waiting for process to start..."
         << "\n";

    while (pids.empty())
    {
        pids = pidOf(bundleId);
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }

    try
    {
        while (true)
        {
            auto duration = printPerformanceMeasure(pids);
            std::this_thread::sleep_for(std::chrono::milliseconds(interval - duration));
        }
    }
    catch (const PidClosedError &e)
    {
        cerr << "CPP_ERROR_MAIN_PID_CLOSED " << e.what() << "\n";
        pollPerformanceMeasures(bundleId, interval);
        return;
    }
}

void printCpuClockTick()
{
    cout << sysconf(_SC_CLK_TCK) << "\n";
}

void printRAMPageSize()
{
    cout << sysconf(_SC_PAGESIZE) << "\n";
}

int main(int argc, char **argv)
{
    string methodName = argv[1];

    if (methodName == "pollPerformanceMeasures")
    {
        string bundleId = argv[2];
        int interval = std::stoi(argv[3]);

        pollPerformanceMeasures(bundleId, interval);
    }
    else if (methodName == "printPerformanceMeasure")
    {
        std::vector<string> pids;
        pids.push_back(argv[2]);
        printPerformanceMeasure(pids);
    }
    else if (methodName == "printCpuClockTick")
    {
        printCpuClockTick();
    }
    else if (methodName == "printRAMPageSize")
    {
        printRAMPageSize();
    }
    else
    {
        cout << "Unknown method name: " << methodName << "\n";
        return 1;
    }

    return 0;
}
