/*
 * All Rights Reserved (C) 2021-2025
 * Exoligent SARL (Colombes, France)
 * Website: www.exoligent.com
 * Author : Maxime Coroyer <maxime.coroyer@exoligent.com>
 */

#include "common.h"

static char date[100];
bool dbg_coloriz = DGB_COLORIZ;

/****************************************************************************************
 * STATISTICS
 ***************************************************************************************/
void update_min_max(uint64_t current_value, uint64_t *min_value, uint64_t *max_value)
{
    if ((min_value == NULL) || (max_value == NULL))
        return;

    if (current_value > *max_value)
        *max_value = current_value;

    if ((*min_value == 0) || (current_value < *min_value))
        *min_value = current_value;
}

#if defined(__QNX__)
int _usleep(uint32_t usec)
{
    uint64_t start_clk =  ClockCycles();
    uint64_t elapsed_us = 0;
    while (elapsed_us < usec)
        elapsed_us = clock_diff_ns(start_clk, ClockCycles()) / 1000;
    return 0;
}

int _gettime(uint64_t *when)
{
    if (when == NULL)
        return -1;
    *when = ClockCycles();
    return 0;
}
#elif defined(_WIN32) || defined(_WIN64)
int _usleep(uint32_t usec)
{
    return usleep(usec);
}

int _gettime(uint64_t *when)
{
    int err = 0;
    LARGE_INTEGER ticks;
    err = !QueryPerformanceCounter(&ticks);
    if (!err)
        *when = (uint64_t) (ticks.QuadPart);
    return err;
}
#else
int _usleep(uint32_t usec)
{
    return usleep(usec);
}

int _gettime(struct timeval *when)
{
    return gettimeofday(when, NULL);
}
#endif

#if defined(__QNX__) || defined(_WIN32) || defined(_WIN64)
void update_stats(uint64_t *start, uint64_t *stop, uint64_t *cp,
    uint64_t *cumul, uint64_t *min, uint64_t *max)
#else
void update_stats(struct timeval *start, struct timeval *stop, uint64_t *cp,
    uint64_t *cumul, uint64_t *min, uint64_t *max)
#endif
{
    unsigned long elapsed_us = get_elapsed_us(*stop, *start);
    *cp += 1;
    *cumul += elapsed_us;
    update_min_max(elapsed_us, min, max);
}

/****************************************************************************************
 * TIME/DATE FUNCTIONS
 ***************************************************************************************/
char *get_date()
{
    struct timeval tv;
    struct timezone tz;
    struct tm *tm;
    time_t tv_sec;

    gettimeofday(&tv, &tz);
    tv_sec = (time_t) tv.tv_sec;
    tm = localtime(&tv_sec);

    memset(date, 0, sizeof(date));
    sprintf(date, "%02d-%02d %02d:%02d:%02d.%06ld",
        tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min,
        tm->tm_sec, tv.tv_usec);

    return date;
}

struct timespec timespec_diff(struct timespec start, struct timespec end)
{
     struct timespec result;

     if (end.tv_nsec < start.tv_nsec)
     {
        result.tv_nsec = 1000000000 + end.tv_nsec - start.tv_nsec;
        result.tv_sec = end.tv_sec - 1 - start.tv_sec;
     }
     else
     {
        result.tv_nsec = end.tv_nsec - start.tv_nsec;
        result.tv_sec = end.tv_sec - start.tv_sec;
     }

    return result;
}

struct timespec timespec_add_ns(struct timespec ref, long ns)
{
    struct timespec result;
    result = ref;
    if ((result.tv_nsec + ns) >= 1000000000) {
        result.tv_sec += 1;
        result.tv_nsec = result.tv_nsec + ns - 1000000000;
    }
    else
        result.tv_nsec = result.tv_nsec + ns;
    return result;
}

bool b_then_a(struct timespec a, struct timespec b)
{
    /* returns true if b happened first -- b will be "lower". */
    if (a.tv_sec == b.tv_sec)
        return a.tv_nsec > b.tv_nsec;
    else
        return a.tv_sec > b.tv_sec;
}

#if defined(__QNX__)
uint64_t clock_diff_ns(uint64_t start_clk_cycl, uint64_t end_clk_cycl)
{
    const uint64_t cycl_per_sec = SYSPAGE_ENTRY(qtime)->cycles_per_sec;
    const uint64_t max_clk_cycl = ~(uint64_t) 0;
    uint64_t elapsed_clk_cycl = 0;

    elapsed_clk_cycl = (end_clk_cycl < start_clk_cycl) ? \
        ((max_clk_cycl - start_clk_cycl) + end_clk_cycl) : (end_clk_cycl - start_clk_cycl);

    return ((elapsed_clk_cycl * 1000000000) / cycl_per_sec);
}

unsigned long get_elapsed_us(uint64_t end, uint64_t start) {
    return (clock_diff_ns(start, end) / 1000);
}
#elif defined(_WIN32) || defined(_WIN64)
unsigned long get_elapsed_us(uint64_t end, uint64_t start) {
    LARGE_INTEGER frequency;
    uint64_t elapsed_us = end - start;
    QueryPerformanceFrequency(&frequency);
    elapsed_us = (elapsed_us * 1000000) / ((uint64_t) frequency.QuadPart);
    return elapsed_us;
}
#else
unsigned long get_elapsed_us(struct timeval end, struct timeval start) {
    return (((end.tv_sec - start.tv_sec) * 1000000L + end.tv_usec) - start.tv_usec);
}
#endif

/****************************************************************************************
 * CUSTOMIZED PRINT FUNCTION
 ***************************************************************************************/
int _print(msg_src src, msg_type type, const char *fmt, ...)
{
    va_list args;
    char *type_str;
    char src_str[255];
    int ret;

    memset(src_str, 0, sizeof(src_str));
    if (src >= 0 && src <= 255)
        sprintf(src_str, "sta%03d", src);
    else if (src == app)
        sprintf(src_str, "app   ");
    else
        sprintf(src_str, "???   ");

    switch (type) {
    case info: type_str = "[info] "; break;
    case warn: type_str = "[warn] "; break;
    case err:  type_str = "[error]"; break;
    case evt:  type_str = "[event]"; break;
    default:   type_str = "[????] "; break;
    }

    if ((type == info) && !DBG_PRINT_INFO)
        return 0;

    if ((type == warn) && !DBG_PRINT_WARN)
        return 0;

    if ((type == err) && !DBG_PRINT_ERR)
        return 0;

    if ((type == evt) && !DBG_PRINT_EVT)
        return 0;

    if (dbg_coloriz) {
        printf("\033[0m"); // reset
        printf("\033[0;32m"); // green
    }

    printf("[%s] ", get_date());

    if (dbg_coloriz) {
        printf("\033[0m");
    }

    printf("%s ", src_str);

    if (dbg_coloriz)
        printf("\033[0m");

    printf("=> ");

    if (dbg_coloriz) {
        if (type == warn)
            printf("\033[0;33m"); // yellow

        if (type == err)
            printf("\033[0;31m"); // red

        if (type == evt)
            printf("\033[0;36m"); // cyan
    }

    printf("%s ", type_str);

    va_start(args, fmt);
    ret = vprintf(fmt, args);
    va_end(args);

    if (dbg_coloriz)
        printf("\033[0m"); //reset

    return ret;
}

#if defined (_WIN32) || defined (_WIN64)
typedef NTSTATUS (WINAPI*RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
static bool have_console_colorization_support() {
    DWORD dwMajor = 10, dwMinor = 0, dwBuild = 10586;
    RTL_OSVERSIONINFOW rovi = { 0 };

    HMODULE hMod = GetModuleHandle(TEXT("ntdll.dll"));
    if (!hMod)
        return false;

    RtlGetVersionPtr fn = (RtlGetVersionPtr) \
                            GetProcAddress(hMod, "RtlGetVersion");
    if (!fn)
        return false;

    rovi.dwOSVersionInfoSize = sizeof(rovi);
    if (!fn(&rovi)) {
        if ((rovi.dwMajorVersion > dwMajor) ||
            ((rovi.dwMajorVersion == dwMajor) &&
             ((rovi.dwMinorVersion > dwMinor) ||
              ((rovi.dwMinorVersion == dwMinor) &&
               (rovi.dwBuildNumber >= dwBuild)))))
            return true;
    }
    return false;
}

void enable_console_colorization() {
    DWORD dwMode = 0;
    if (have_console_colorization_support()) {
        HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
        GetConsoleMode(hOut, &dwMode);
        dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
        SetConsoleMode(hOut, dwMode);
    } else {
        dbg_coloriz = false;
    }
}
#endif
