// millisHost.c - Host-cc harness for jlpGenericMillisElapsed (the
// IIgs/Amiga millisecond clock). Drives simulated frame-count sequences,
// including the 16-bit wrap at 65536 frames, and asserts the millisecond
// clock never runs backward.
//
// Built by scripts/check-millis.sh with -DJL_HAS_FRAME_COUNT and
// -DJL_HAS_FRAME_HZ so port.h routes jlpFrameCount/jlpFrameHz to the mock
// implementations below instead of the generic stubs.
//
// TODAY this harness FAILS: jlpGenericMillisElapsed is a pure function of
// the wrapping uint16_t frame count (PERF-AUDIT.md finding #23), so the
// clock jumps backward every 65536 frames (~18 min at 60Hz). The Phase 1
// fix makes it accumulate deltas; this harness must then PASS and becomes
// the regression gate for it.

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>

#include "port.h"

static uint16_t gMockFrame = 0;
static uint16_t gMockHz    = 60;

// Prototypes (harness-local, alphabetical).
static bool runScenario(const char *name, uint16_t hz, const uint32_t *frames, int count);


uint16_t jlpFrameCount(void) {
    return gMockFrame;
}


uint16_t jlpFrameHz(void) {
    return gMockHz;
}


// Walk an absolute-frame sequence (values may exceed 65535 to express
// real elapsed frames; the mock truncates to uint16_t exactly like real
// hardware counters wrap). Asserts millis is non-decreasing and lands
// within 1 frame-period of the true elapsed time at the end.
static bool runScenario(const char *name, uint16_t hz, const uint32_t *frames, int count) {
    uint32_t prevMs;
    uint32_t ms;
    uint32_t expectMs;
    uint32_t tolerance;
    bool     ok;
    int      i;

    gMockHz   = hz;
    ok        = true;
    prevMs    = 0;
    ms        = 0;
    for (i = 0; i < count; i++) {
        gMockFrame = (uint16_t)frames[i];
        ms = jlpGenericMillisElapsed();
        if (ms < prevMs) {
            printf("  %s: BACKWARD at step %d (frame %lu): %lu ms -> %lu ms\n",
                   name, i, (unsigned long)frames[i],
                   (unsigned long)prevMs, (unsigned long)ms);
            ok = false;
        }
        prevMs = ms;
    }
    expectMs  = (uint32_t)(((uint64_t)frames[count - 1] * 1000u) / hz);
    tolerance = (uint32_t)(1000u / hz + 1u);
    if (ok && (ms + tolerance < expectMs || ms > expectMs + tolerance)) {
        printf("  %s: DRIFT: got %lu ms, expected ~%lu ms (+/- %lu)\n",
               name, (unsigned long)ms, (unsigned long)expectMs,
               (unsigned long)tolerance);
        ok = false;
    }
    printf("  %s: %s\n", name, ok ? "PASS" : "FAIL");
    return ok;
}


int main(void) {
    // Sequences are ABSOLUTE frame counts; steps stay under 65535 frames
    // apart (the documented poll-at-least-once-per-wrap requirement).
    static const uint32_t simple60[] = { 0, 60, 600, 3600, 36000 };
    static const uint32_t wrap60[]   = { 0, 30000, 65000, 70000, 131000, 190000, 200000 };
    static const uint32_t wrap50[]   = { 0, 40000, 65500, 66000, 131000 };
    bool ok = true;

    printf("millisHost: jlpGenericMillisElapsed scenarios\n");
    // Scenario order alternates Hz deliberately: the accumulator's
    // statics persist across scenarios, and a frame-rate change is the
    // documented re-base point -- alternating gives each scenario a
    // fresh epoch instead of inheriting the previous one's baseline.
    ok &= runScenario("simple-60hz", 60, simple60, (int)(sizeof(simple60) / sizeof(simple60[0])));
    ok &= runScenario("wrap-50hz",   50, wrap50,   (int)(sizeof(wrap50)   / sizeof(wrap50[0])));
    ok &= runScenario("wrap-60hz",   60, wrap60,   (int)(sizeof(wrap60)   / sizeof(wrap60[0])));
    if (!ok) {
        printf("millisHost: FAIL (finding #23 wrap bug present -- expected until Phase 1 lands)\n");
        return 1;
    }
    printf("millisHost: PASS\n");
    return 0;
}
