/* ============================================================================
 * absolute_clock.c
 * ================
 * Two-thread absolute-time clock with embedded AI auto-calibrator.
 * Single C99 file. No external dependencies beyond libc, pthread, libm.
 *
 * Build:   gcc -O2 -o abs_clock absolute_clock.c -lm -lpthread
 * Run:     ./abs_clock
 *          ./abs_clock --seconds 30
 *          ./abs_clock --lam 0.5 --eps 1e-3 --seconds 20
 *          ./abs_clock --ai-off              (fixed (λ, ε), no AI)
 *          ./abs_clock --quiet               (only final summary)
 *          ./abs_clock --iters 8000000       (shorter workload units)
 *
 * Theory (continuous ODE):
 *     dA/dt = dC̄/dt  −  λ · δ / ( ε² + δ² )
 * where δ is the inter-thread disagreement, λ the drift-correction gain,
 * ε the Laurent regulariser. The two are NOT constants — they are emitted
 * each step by a 5-state × 5-action Q-learning automaton (ClockwiseAI).
 *
 * State space   : CALM, DRIFT, NOISY, CRITICAL, CONVERGED
 * Action space  : Δλ ∈ {-0.10, -0.05, 0, +0.05, +0.10}
 *                  ε correlated with action (factor 0.95..1.05)
 * Reward        : r = −|A − wall| − 0.1·|correction| − 0.01·|δ|
 * Q-update      : Q[s,a] ← Q[s,a] + α·(r + γ·max_a' Q[s',a'] − Q[s,a])
 *                  α=0.20, γ=0.85, ε-greedy=0.15
 *
 * Threading: two POSIX threads run the *same* deterministic CPU
 * workload (a sin/cos/sqrt loop) and push (tid, t0_ns, t1_ns) samples
 * into a ring-buffered queue. The observer pulls pairs, advances A,
 * and feeds the AI.
 * ============================================================================
 */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <math.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>

/* ───────────────────────────── tunable constants ──────────────────────── */
#define UNIT_TIME             0.500     /* seconds — pole X (calibrated)   */
#define N_THREADS             2
#define QUEUE_CAP             4096
#define WINDOW                32        /* AI observation window           */
#define LAMBDA_DEFAULT        0.40
#define EPSILON_DEFAULT       1e-3
#define RUN_SECONDS_DEFAULT   10.0
#define WORKLOAD_ITERS_DEFAULT 25000000L /* tune for ~0.5 s · unit-1      */

#define EPS_GREEDY            0.15
#define ALPHA                 0.20
#define GAMMA                 0.85

/* ───────────────────────────── shared types ───────────────────────────── */
typedef struct {
    int     tid;
    int64_t t0_ns;            /* CLOCK_MONOTONIC nanoseconds            */
    int64_t t1_ns;
} sample_t;

typedef struct {
    sample_t           buf[QUEUE_CAP];
    int                head, tail, count;
    pthread_mutex_t    mtx;
    pthread_cond_t     not_empty;
} queue_t;

typedef enum {
    STATE_CALM = 0, STATE_DRIFT, STATE_NOISY, STATE_CRITICAL, STATE_CONVERGED
} ai_state_t;

static const char *STATE_NAMES[] = {
    "CALM", "DRIFT", "NOISY", "CRITICAL", "CONVERGED"
};

typedef struct {
    double     lam, eps;
    ai_state_t state, prev_state;
    int        prev_action;
    long       state_changes;
    double     total_reward;
    double     Q[5][5];
    double     deltas[WINDOW], drifts[WINDOW], corrs[WINDOW];
    int        n_hist;
    char       last_reason[160];
} ai_t;

/* ───────────────────────────── globals ────────────────────────────────── */
static queue_t         q;
static volatile sig_atomic_t stop_flag = 0;
static long            workload_iters = WORKLOAD_ITERS_DEFAULT;

/* ───────────────────────────── utilities ──────────────────────────────── */
static double mono_seconds(void) {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
}

static void on_sigint(int sig) { (void)sig; stop_flag = 1; }

static void format_hms_ms(long sec_part, long ms_part,
                          char *out, size_t n) {
    long h = (sec_part / 3600) % 24;
    long m = (sec_part / 60)   % 60;
    long s =  sec_part         % 60;
    snprintf(out, n, "%02ld:%02ld:%02ld.%03ld", h, m, s, ms_part);
}

/* SYSTEM datetime  = current wall clock, live                           */
static void format_now(char *out, size_t n) {
    time_t now = time(NULL);
    struct tm tm; localtime_r(&now, &tm);
    long ms = (long)((mono_seconds() - (double)(long)mono_seconds()) * 1000.0);
    format_hms_ms((long)now, ms, out, n);
}

/* ABSOLUTE datetime = start_wall_sec + A seconds                        */
static void format_absolute(time_t start_wall_sec, double A_sec,
                            char *out, size_t n) {
    double total = (double)start_wall_sec + A_sec;
    long sec   = (long)total;
    long fr_ms = (long)((total - (double)sec) * 1000.0);
    if (fr_ms < 0) { fr_ms += 1000; sec--; }
    /* localtime_r uses local TZ; we want the same TZ semantics */
    struct tm tm; localtime_r((const time_t *)&sec, &tm);
    (void)tm;        /* not used in this simple formatter */
    format_hms_ms(sec, fr_ms, out, n);
}

/* tiny xorshift32 RNG so we don't depend on rand() seeding */
static uint32_t rng_state = 0xCAFEF00D;
static double frand01(void) {
    uint32_t x = rng_state;
    x ^= x << 13; x ^= x >> 17; x ^= x << 5;
    rng_state = x;
    return (double)x / (double)0xFFFFFFFFu;
}

/* ───────────────────────────── ring-buffer queue ─────────────────────── */
static void queue_init(queue_t *q) {
    q->head = q->tail = q->count = 0;
    pthread_mutex_init(&q->mtx, NULL);
    pthread_cond_init(&q->not_empty, NULL);
}

static void queue_push(queue_t *q, const sample_t *s) {
    pthread_mutex_lock(&q->mtx);
    if (q->count >= QUEUE_CAP) {                /* drop oldest on overflow */
        q->tail = (q->tail + 1) % QUEUE_CAP;
        q->count--;
    }
    q->buf[q->head] = *s;
    q->head = (q->head + 1) % QUEUE_CAP;
    q->count++;
    pthread_cond_signal(&q->not_empty);
    pthread_mutex_unlock(&q->mtx);
}

/* returns 1 on success, 0 on timeout */
static int queue_pop(queue_t *q, sample_t *out, int timeout_ms) {
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    ts.tv_sec  += timeout_ms / 1000;
    ts.tv_nsec += (long)(timeout_ms % 1000) * 1000000L;
    if (ts.tv_nsec >= 1000000000L) { ts.tv_sec++; ts.tv_nsec -= 1000000000L; }

    pthread_mutex_lock(&q->mtx);
    while (q->count == 0) {
        int rc = pthread_cond_timedwait(&q->not_empty, &q->mtx, &ts);
        if (rc == ETIMEDOUT) {
            pthread_mutex_unlock(&q->mtx);
            return 0;
        }
    }
    *out = q->buf[q->tail];
    q->tail = (q->tail + 1) % QUEUE_CAP;
    q->count--;
    pthread_mutex_unlock(&q->mtx);
    return 1;
}

/* ───────────────────────────── known compute workload ────────────────── */
static void known_compute(int unit_index) {
    volatile double acc = 0.0;
    for (long i = 0; i < workload_iters; i++) {
        acc += sin((double)i * 0.001 + (double)unit_index)
             * cos((double)i * 0.0007);
        acc -= sqrt((double)(i + 1)) * 0.000001;
    }
    (void)acc;          /* volatile forces the work to actually happen  */
}

/* ───────────────────────────── a parallel worker thread ──────────────── */
static void *thread_worker(void *arg) {
    int tid = *(int *)arg;
    while (!stop_flag) {
        struct timespec t0, t1;
        clock_gettime(CLOCK_MONOTONIC, &t0);
        known_compute(tid);
        clock_gettime(CLOCK_MONOTONIC, &t1);
        sample_t s = { .tid = tid,
                       .t0_ns = (int64_t)t0.tv_sec * 1000000000LL + t0.tv_nsec,
                       .t1_ns = (int64_t)t1.tv_sec * 1000000000LL + t1.tv_nsec };
        queue_push(&q, &s);
    }
    return NULL;
}

/* ─────────────────────────────  AI AUTOMATON  ─────────────────────────── */
static const double ACT_DL[]    = { -0.10, -0.05,  0.00, +0.05, +0.10 };
static const double ACT_EPS_F[] = {  0.95,  0.99,  1.00,  1.01,  1.05 };

static void ai_init(ai_t *ai, double lam, double eps) {
    memset(ai, 0, sizeof(*ai));
    ai->lam = lam; ai->eps = eps;
    ai->state = ai->prev_state = STATE_CALM;
    ai->prev_action = 2;
    /* hand-coded priors (warm start) — same as Python v3 */
    ai->Q[STATE_CALM][2]      = +0.30;
    ai->Q[STATE_CALM][0]      = -0.40;
    ai->Q[STATE_CALM][4]      = -1.50;
    ai->Q[STATE_DRIFT][3]     = +0.50;
    ai->Q[STATE_DRIFT][4]     = +0.60;
    ai->Q[STATE_DRIFT][0]     = -1.00;
    ai->Q[STATE_NOISY][0]     = +0.40;
    ai->Q[STATE_NOISY][1]     = +0.20;
    ai->Q[STATE_CRITICAL][4]  = +0.80;
    ai->Q[STATE_CRITICAL][3]  = +0.30;
    ai->Q[STATE_CONVERGED][2] = +0.30;
    snprintf(ai->last_reason, sizeof(ai->last_reason), "init");
}

static void ai_hist_push(ai_t *ai, double d, double dr, double c) {
    if (ai->n_hist < WINDOW) {
        ai->deltas[ai->n_hist] = d;
        ai->drifts[ai->n_hist] = dr;
        ai->corrs [ai->n_hist] = c;
        ai->n_hist++;
    } else {
        memmove(&ai->deltas[0], &ai->deltas[1], (WINDOW-1) * sizeof(double));
        memmove(&ai->drifts[0], &ai->drifts[1], (WINDOW-1) * sizeof(double));
        memmove(&ai->corrs [0], &ai->corrs [1], (WINDOW-1) * sizeof(double));
        ai->deltas[WINDOW-1] = d;
        ai->drifts[WINDOW-1] = dr;
        ai->corrs [WINDOW-1] = c;
    }
}

/* returns the action index chosen (0..4) */
static int  ai_observe_and_learn(ai_t *ai, double delta,
                                 double A_drift, double correction) {
    ai_hist_push(ai, delta, A_drift, correction);
    if (ai->n_hist < 4) { ai->prev_action = 2; return 2; }

    /* features */
    double mean_abs_d = 0.0, mean_abs_dr = 0.0, max_abs_d = 0.0;
    for (int i = 0; i < ai->n_hist; i++) {
        double ad = fabs(ai->deltas[i]);
        double ar = fabs(ai->drifts[i]);
        mean_abs_d  += ad;
        mean_abs_dr += ar;
        if (ad > max_abs_d) max_abs_d = ad;
    }
    mean_abs_d  /= ai->n_hist;
    mean_abs_dr /= ai->n_hist;

    /* oscillation: range of last 4 */
    int recent_n = ai->n_hist < 4 ? ai->n_hist : 4;
    double rmin = ai->deltas[ai->n_hist - recent_n];
    double rmax = rmin;
    for (int i = ai->n_hist - recent_n + 1; i < ai->n_hist; i++) {
        if (ai->deltas[i] < rmin) rmin = ai->deltas[i];
        if (ai->deltas[i] > rmax) rmax = ai->deltas[i];
    }
    double rng_recent = rmax - rmin;

    /* state inference */
    ai_state_t prev_state = ai->state;
    ai_state_t new_state;
    const char *reason;
    if (max_abs_d > 5.0 * ai->eps) {
        new_state = STATE_CRITICAL;                                     reason = "max|δ|>5ε";
    } else if (mean_abs_dr > 0.05) {
        new_state = STATE_DRIFT;                                        reason = "|A−wall|>50ms";
    } else if (rng_recent > 3.0 * fmax(mean_abs_d, 1e-9) && mean_abs_d > ai->eps) {
        new_state = STATE_NOISY;                                        reason = "δ oscillation";
    } else if (mean_abs_d < 0.5 * ai->eps && mean_abs_dr < 1e-3) {
        new_state = STATE_CONVERGED;                                    reason = "|A−wall|<1ms & |δ|<ε/2";
    } else {
        new_state = STATE_CALM;                                         reason = "no-pattern";
    }
    if (new_state != prev_state) ai->state_changes++;
    ai->prev_state = prev_state;

    /* reward */
    double r = -fabs(A_drift) - 0.1 * fabs(correction) - 0.01 * fabs(delta);
    ai->total_reward += r;

    /* Q-update on previous (state, action) */
    double max_next = ai->Q[new_state][0];
    for (int a = 1; a < 5; a++)
        if (ai->Q[new_state][a] > max_next) max_next = ai->Q[new_state][a];
    double target = r + GAMMA * max_next;
    if (prev_state >= 0) {
        ai->Q[prev_state][ai->prev_action] +=
            ALPHA * (target - ai->Q[prev_state][ai->prev_action]);
    }

    /* ε-greedy action selection */
    int action;
    if (frand01() < EPS_GREEDY) {
        action = (int)(frand01() * 5.0);
        if (action >= 5) action = 4;
    } else {
        double best = ai->Q[new_state][0];
        action = 0;
        for (int a = 1; a < 5; a++)
            if (ai->Q[new_state][a] > best) { best = ai->Q[new_state][a]; action = a; }
    }

    ai->state = new_state;
    ai->prev_action = action;

    /* apply action */
    ai->lam += ACT_DL[action];
    if (ai->lam < 0.05) ai->lam = 0.05;
    if (ai->lam > 1.0)  ai->lam = 1.0;
    ai->eps *= ACT_EPS_F[action];
    if (ai->eps < 1e-5) ai->eps = 1e-5;
    if (ai->eps > 1e-2) ai->eps = 1e-2;

    snprintf(ai->last_reason, sizeof(ai->last_reason),
             "%s → %s (%s)",
             STATE_NAMES[prev_state], STATE_NAMES[new_state], reason);
    return action;
}

/* ─────────────────────────────  M A I N  ──────────────────────────────── */
static void usage(const char *prog) {
    fprintf(stderr,
        "usage: %s [--seconds N] [--lam X] [--eps Y] [--iters K]\n"
        "          [--ai-off] [--quiet]\n", prog);
}

int main(int argc, char **argv) {
    double run_seconds = RUN_SECONDS_DEFAULT;
    double lam0 = LAMBDA_DEFAULT, eps0 = EPSILON_DEFAULT;
    int    ai_on = 1, quiet_mode = 0;

    for (int i = 1; i < argc; i++) {
        if      (!strcmp(argv[i], "--seconds")  && i+1 < argc) run_seconds = atof(argv[++i]);
        else if (!strcmp(argv[i], "--lam")      && i+1 < argc) lam0        = atof(argv[++i]);
        else if (!strcmp(argv[i], "--eps")      && i+1 < argc) eps0        = atof(argv[++i]);
        else if (!strcmp(argv[i], "--iters")    && i+1 < argc) workload_iters = atol(argv[++i]);
        else if (!strcmp(argv[i], "--ai-off"))                    ai_on = 0;
        else if (!strcmp(argv[i], "--quiet"))                     quiet_mode = 1;
        else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
            usage(argv[0]); return 0;
        } else {
            fprintf(stderr, "unknown arg: %s\n", argv[i]);
            usage(argv[0]);
            return 1;
        }
    }

    /* capture program-start datetime (anchors absolute clock) */
    time_t  start_wall_sec = time(NULL);
    double  start_mono     = mono_seconds();
    char    start_iso[32];
    {
        struct tm tm;
        localtime_r(&start_wall_sec, &tm);
        strftime(start_iso, sizeof(start_iso), "%Y-%m-%dT%H:%M:%S", &tm);
    }

    /* signal handler — Ctrl-C stops cleanly */
    struct sigaction sa = { .sa_handler = on_sigint };
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT,  &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);

    /* init queue + AI */
    queue_init(&q);
    ai_t ai; ai_init(&ai, lam0, eps0);
    double lam = lam0, eps = eps0;

    /* spawn the two parallel compute threads */
    pthread_t th[N_THREADS];
    int tids[N_THREADS] = {0, 1};
    for (int i = 0; i < N_THREADS; i++)
        pthread_create(&th[i], NULL, thread_worker, &tids[i]);

    /* state for forming paired samples */
    sample_t pending[N_THREADS] = {{-1,0,0},{-1,0,0}};
    int      have    [N_THREADS] = {0,0};
    double   A = 0.0, last_Cbar = -1.0;
    long     step = 0;

    /* ── BANNER ── */
    printf("\n  ┌─ program START datetime : %s (local)\n", start_iso);
    printf("  ├─ λ initial              : %.3f\n", lam0);
    printf("  ├─ ε initial              : %.3e\n", eps0);
    printf("  ├─ T_ref (pole X)         : %.3f s\n", UNIT_TIME);
    printf("  ├─ workload iters         : %ld\n", workload_iters);
    printf("  ├─ AI enabled             : %s\n", ai_on ? "yes" : "no");
    printf("  └─ run for                : %.1f s\n\n", run_seconds);

    /* ── TABLE HEADER ── */
    static const char *HDR =
        "  step |   SYSTEM    |   ABSOLUTE  |     δ      |     A      |  STATE   |   λ    |  ε×1e3  | act | reward  ";
    static const char *SEP =
        "  -----+-------------+-------------+------------+-------------+----------+--------+----------+-----+---------";

    if (!quiet_mode) printf("%s\n%s\n", HDR, SEP);

    /* ── MAIN OBSERVER LOOP ── */
    while (!stop_flag) {
        /* time-up check */
        if (mono_seconds() - start_mono > run_seconds) break;

        sample_t s;
        if (!queue_pop(&q, &s, 5)) continue;          /* 5 ms timeout */

        pending[s.tid] = s;
        have[s.tid] = 1;
        if (!have[0] || !have[1]) continue;
        have[0] = have[1] = 0;

        double C1 = (pending[0].t1_ns - pending[0].t0_ns) * 1e-9;
        double C2 = (pending[1].t1_ns - pending[1].t0_ns) * 1e-9;
        double delta = C1 - C2;
        double Cbar = 0.5 * (C1 + C2);
        double dCbar = (last_Cbar < 0.0) ? 0.0 : Cbar - last_Cbar;
        double correction = lam * delta / (eps * eps + delta * delta);
        A += dCbar - correction;
        last_Cbar = Cbar;
        step++;

        /* A_drift = A_total − elapsed_real_time_since_start */
        double sys_elapsed = mono_seconds() - start_mono;
        double A_drift     = A - sys_elapsed;

        if (ai_on) {
            (void)ai_observe_and_learn(&ai, delta, A_drift, correction);
            lam = ai.lam;
            eps = ai.eps;
        }

        if (!quiet_mode) {
            char sys_buf[64], abs_buf[64];
            format_now(sys_buf, sizeof(sys_buf));   /* live wall */
            format_absolute(start_wall_sec, A, abs_buf, sizeof(abs_buf));
            printf("  %4ld | %s | %s | %+10.5f | %10.6f | %-8s | %6.3f | %7.4f |  %d  | %+8.4f\n",
                   step,
                   sys_buf, abs_buf,
                   delta,
                   A,
                   ai_on ? STATE_NAMES[ai.state] : "—",
                   lam,
                   eps * 1e3,
                   ai_on ? ai.prev_action : 2,
                   ai_on ? (-fabs(A_drift) - 0.1 * fabs(correction) - 0.01 * fabs(delta)) : 0.0);
            fflush(stdout);
        }
    }

    /* ── SHUTDOWN ── */
    stop_flag = 1;
    for (int i = 0; i < N_THREADS; i++) pthread_join(th[i], NULL);

    /* ── FINAL SUMMARY ── */
    char final_abs[64];
    format_absolute(start_wall_sec, A, final_abs, sizeof(final_abs));
    printf("\n  ────────────────────────────────────────────────────────────────\n");
    printf("  ▸ final A          = %.6f s\n", A);
    printf("  ▸ final absolute   = %s\n",   final_abs);
    printf("  ▸ paired samples   = %ld\n",  step);
    if (ai_on) {
        printf("  ▸ AI state changes = %ld\n",  ai.state_changes);
        printf("  ▸ AI ∑reward       = %.3f\n", ai.total_reward);
        printf("  ▸ final λ, ε       = %.3f, %.3e\n", ai.lam, ai.eps);
        printf("  ▸ last AI reason   : %s\n", ai.last_reason);
    }
    return 0;
}