// calogTime.c -- calog time library (see calogTime.h). Three stateless natives over the
// POSIX clocks: a wall-clock read, a monotonic read, and a millisecond sleep. No shared
// state and no timers/threads -- each native is a pure function of its arguments (or a
// plain block of the calling context thread).

#define _POSIX_C_SOURCE 200809L

#include "calogTime.h"

#include <errno.h>
#include <time.h>

// Conversion factors shared by timeSleepNative and timeToSeconds.
#define TIME_NS_PER_MS  1000000.0
#define TIME_NS_PER_SEC 1000000000

static int32_t timeMonotonicNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t timeNowNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static int32_t timeSleepNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);
static double  timeToSeconds(const struct timespec *ts);


int32_t calogTimeRegister(CalogT *calog) {
    calogRegisterInline(calog, "timeNow", timeNowNative, NULL);
    calogRegisterInline(calog, "timeMonotonic", timeMonotonicNative, NULL);
    calogRegisterInline(calog, "timeSleep", timeSleepNative, NULL);
    return calogOkE;
}


static int32_t timeMonotonicNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    struct timespec ts;

    (void)args;
    (void)userData;
    calogValueNil(result);
    if (argCount != 0) {
        return calogFail(result, calogErrArgE, "timeMonotonic expects ()");
    }
    if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
        return calogFail(result, calogErrUnsupportedE, "timeMonotonic: clock_gettime failed");
    }
    calogValueReal(result, timeToSeconds(&ts));
    return calogOkE;
}


static int32_t timeNowNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    struct timespec ts;

    (void)args;
    (void)userData;
    calogValueNil(result);
    if (argCount != 0) {
        return calogFail(result, calogErrArgE, "timeNow expects ()");
    }
    if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
        return calogFail(result, calogErrUnsupportedE, "timeNow: clock_gettime failed");
    }
    calogValueReal(result, timeToSeconds(&ts));
    return calogOkE;
}


static int32_t timeSleepNative(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData) {
    struct timespec request;
    struct timespec remaining;
    double          ms;
    int64_t         nanos;

    (void)userData;
    calogValueNil(result);
    if (argCount != 1 || (args[0].type != calogIntE && args[0].type != calogRealE)) {
        return calogFail(result, calogErrArgE, "timeSleep expects (milliseconds)");
    }
    ms = (args[0].type == calogIntE) ? (double)args[0].as.i : args[0].as.r;
    if (!(ms >= 0.0)) {
        return calogFail(result, calogErrRangeE, "timeSleep: milliseconds must be non-negative");
    }
    // Reject ms large enough that ms * TIME_NS_PER_MS would overflow int64_t --
    // the cast below is UB otherwise (C11 6.3.1.4p1).
    if (ms > (double)INT64_MAX / TIME_NS_PER_MS) {
        return calogFail(result, calogErrRangeE, "timeSleep: milliseconds out of range");
    }
    nanos           = (int64_t)(ms * TIME_NS_PER_MS);
    request.tv_sec  = (time_t)(nanos / TIME_NS_PER_SEC);
    request.tv_nsec = (long)(nanos % TIME_NS_PER_SEC);
    while (nanosleep(&request, &remaining) != 0) {
        if (errno != EINTR) {
            return calogFail(result, calogErrUnsupportedE, "timeSleep: nanosleep failed");
        }
        request = remaining;
    }
    return calogOkE;
}


static double timeToSeconds(const struct timespec *ts) {
    return (double)ts->tv_sec + (double)ts->tv_nsec / TIME_NS_PER_SEC;
}
