#include <winps.hpp>
#include <rlib/stdio.hpp>
#include <chrono>
#include <thread>
#include <rlib/opt.hpp>
#include <rlib/sys/sio.hpp>
#include <cassert>

#include <fcntl.h>
using namespace std::literals;
using rlib::println;

constexpr uint32_t DATAFILE_HEAD_MAGIC = 0x107ebeef;
struct round_record_t {
    uint32_t time_offset; // in seconds
    uint8_t stats_count[3];
    uint8_t _;
} __attribute__((packed));
static_assert(sizeof(round_record_t) == sizeof(uint64_t));

#ifndef NO_RECORDER
inline void writeback_record(fd_t fd, const round_record_t &rr) {
#ifdef DEBUG
    rlib::printfln("DEBUG: writing record time_offset={}, stat counts= {} {} {}", rr.time_offset, (int)rr.stats_count[0], (int)rr.stats_count[1], (int)rr.stats_count[2]);
#endif
    rlib::fdIO::writen_ex(fd, &rr, sizeof(rr));
}

int check_once() {
    // Check if MonAgentXxx.exe exists and is 4. return 0 for success, 1 for failure, 2 for half-failure (less than 4 processes are up, maybe unhealthy). 
    auto mon_proc_count = 0;
    for(auto && pname : win32_ps()) {
        std::transform(pname.begin(), pname.end(), pname.begin(),[](unsigned char c){ return std::tolower(c); });
        if(pname.substr(0, 8) == "monagent")
            mon_proc_count++;
    }
    if(mon_proc_count >= 4) return 0;
    if(mon_proc_count == 0) return 1;
    return 2;
}

void one_round(fd_t fd, uint32_t time_offset) {
    // Check status every 2 seconds, and every round is 64 check, which is 2 minutes. 
    // Write-back to log file after every round. 
    round_record_t record {time_offset, 0};
    for(auto i = 0; i < 64; ++i) {
        record.stats_count[check_once()]++;
        std::this_thread::sleep_for(2s);
    }
    writeback_record(fd, record);
}
#endif

void help_exit() {
    println("Usage: ./this-tool --role recorder/player --db log-file.log");
    exit(1);
}
int main(int argc, char **argv) {
    rlib::opt_parser args(argc, argv);
    if(argc == 1) help_exit();
    const auto role = args.getValueArg("--role");
    const auto fname = args.getValueArg("--db");

    if(role == "recorder") {
#ifndef NO_RECORDER
        const auto fd = open(fname.c_str(), O_APPEND | O_CREAT | O_WRONLY, 0777);
        assert(fd != -1);

        // Write initial head record
        const uint32_t start_time = std::time(nullptr);
        const uint64_t start_record_payload = (uint64_t)start_time + ((uint64_t)DATAFILE_HEAD_MAGIC << 32);
        writeback_record(fd, *(const round_record_t *)&start_record_payload);

        // Write subsequent records
        while(true) {
            try {
                one_round(fd, std::time(nullptr) - start_time);
            }
            catch(std::exception e) {
                println("Exception caught: ", e.what());
                std::this_thread::sleep_for(1s);
            }
        }
#endif
    }
    else if(role == "player") {
        const auto fd = open(fname.c_str(), O_RDONLY);
        assert(fd != -1);

        uint32_t current_timebase = 0;
        while(true) {
            round_record_t record_buf;
            rlib::fdIO::readn_ex(fd, &record_buf, sizeof(record_buf));

            if(((const uint32_t *)&record_buf)[1] == DATAFILE_HEAD_MAGIC)
                current_timebase = record_buf.time_offset;
            else
                rlib::printfln("TIME {} SUCC COUNT {} FAIL COUNT {} UNHEALTHY COUNT {}", record_buf.time_offset + current_timebase, (int)record_buf.stats_count[0], (int)record_buf.stats_count[1], (int)record_buf.stats_count[2]);
        }
    }
    else
        help_exit();
}


