#include "timing.h"
#include "runtime.h"

#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

static uint64_t _QPC_freq = 0u;

rt_result InitTiming(void) {
    LARGE_INTEGER qpc_freq;
    if (!QueryPerformanceFrequency(&qpc_freq)) {
        return RT_UNKNOWN_ERROR;
    }
    _QPC_freq         = (uint64_t)qpc_freq.QuadPart;
    double resolution = 1e6 * 1.0 / (double)_QPC_freq;
    rtLog("TIMING",
          "QPC Frequency: %llu ticks per second     Resolution: %.2lf us",
          _QPC_freq,
          resolution);

    return RT_SUCCESS;
}

RT_DLLEXPORT rt_timestamp rtTimeNow(void) {
    LARGE_INTEGER qpc;
    QueryPerformanceCounter(&qpc);
    return (rt_timestamp){.ticks = qpc.QuadPart, .ticks_per_second = _QPC_freq};
}

#elif defined(__linux__)

#include <time.h>

static uint64_t _gettime_freq = 0u;
static clockid_t _clock_id = CLOCK_REALTIME;

rt_result InitTiming(void) {

    struct timespec res;
    _clock_id = CLOCK_PROCESS_CPUTIME_ID;
    if (clock_getres(CLOCK_PROCESS_CPUTIME_ID, &res) != 0){
        /* Fallback to CLOCK_REALTIME */
        _clock_id = CLOCK_REALTIME;
        if (clock_getres(CLOCK_REALTIME, &res) != 0) {
            return RT_UNKNOWN_ERROR;
        }
    }

    if (res.tv_sec > 0) {
        rtReportError("TIMING", "Clock reports resolution greater than 1 second.");
        return RT_INVALID_VALUE;
    }
    _gettime_freq = 1000000000;
    double us_res = (double)res.tv_nsec / 1e3;
    
    rtLog("TIMING", "clock_gettime resolution: %.3lf us.", us_res);
        return RT_SUCCESS;
}

RT_DLLEXPORT rt_timestamp rtTimeNow(void) {
    struct timespec ts;
    clock_gettime(_clock_id, &ts );
    return (rt_timestamp){.ticks = ts.tv_nsec, .ticks_per_second = _gettime_freq};    
}

#endif

RT_DLLEXPORT rt_time_delta rtTimeBetween(rt_timestamp a, rt_timestamp b) {
    double a_secs, b_secs;
    a_secs = (double)a.ticks / (double)a.ticks_per_second;
    b_secs = (double)b.ticks / (double)b.ticks_per_second;
    return b_secs - a_secs;
}

