// The C host driver for the determinism battery's host-bearing probe.
//
// The battery's other legs are one-shot binaries with no host at all. This one
// is a real embedding host: it loads the HostProbe project through the
// generated C bindings, supplies the project's `host fn`s, and lets the probe
// program print its own WAXDET lines through the binding log hook.
//
// Everything the driver does is fixed at compile time. There is no clock, no
// environment read, no allocation whose address could reach the output, and the
// probe program is the only thing that writes to stdout -- the payloads below
// are the sole source of variation, and they vary only in the one way the probe
// is about.
//
// The payload is built as raw bytes and memcpy'd into the generated struct, and
// the very same bytes are handed back across a `uint8[]` crossing that has no
// float lanes. That is what makes the control exact: the two rows differ only
// by what the composite crossing did to them.
//
// wasm-probe-host.mjs writes the same bytes for the wasm leg. Two independent
// hosts writing one payload is what makes the cross-leg comparison a
// differential; drift between them fails against the pinned expectations rather
// than passing quietly.
//
// The generated element struct's name ends in its runtime type id, so this file
// cannot spell it. The harness reads it out of the emitted header and passes it
// in as WAXDET_VEC; failing to define it is an error rather than a guess.

#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "hostprobe.h"

#ifndef WAXDET_VEC
#error "WAXDET_VEC must name the generated ProbeVec struct"
#endif
#ifndef WAXDET_VEC_ARRAY
#error "WAXDET_VEC_ARRAY must name the generated ProbeVec array descriptor struct"
#endif

#define WAXDET_VEC_BYTES 24

// A layout the driver merely assumed would be a silently wrong probe: the
// payload offsets below are hand-placed, so the element's size and the position
// of every field are asserted rather than trusted.
_Static_assert(sizeof(struct WAXDET_VEC) == WAXDET_VEC_BYTES, "ProbeVec must be 24 bytes");
_Static_assert(offsetof(struct WAXDET_VEC, wide) == 0, "ProbeVec.wide must sit at 0");
_Static_assert(offsetof(struct WAXDET_VEC, narrow) == 8, "ProbeVec.narrow must sit at 8");
_Static_assert(offsetof(struct WAXDET_VEC, tag) == 12, "ProbeVec.tag must sit at 12");
_Static_assert(offsetof(struct WAXDET_VEC, trailing) == 16, "ProbeVec.trailing must sit at 16");

// Two elements, little-endian, laid out as { double wide; float narrow; int32
// tag; float trailing; pad[4] }.
//
// Element 0 is all NaN and no finite value: a signaling f64 NaN, an f32 quiet
// NaN that is both sign-set and payload-bearing, an integer field holding a
// third NaN-shaped pattern, and a signaling f32 NaN. Element 1 is the opposite
// mix -- two exactly representable finite values that must survive bit-for-bit
// beside one more non-canonical NaN. A canonicalizer that swept the element by
// width would flatten element 1's finite lanes and element 0's tag, and a
// canonicalizer that missed the table entirely would leave every NaN as written.
//
// The pad bytes carry a fill rather than zero. Zero is what a destination
// happens to hold, so a zero-filled pad could not tell a byte copy from a
// field-wise one; 0xA5 and 0x5A can.
static unsigned char gArrayPayload[2 * WAXDET_VEC_BYTES] = {
    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F,
    0x01, 0x00, 0xC0, 0xFF,
    0x01, 0x00, 0xC0, 0x7F,
    0x01, 0x00, 0x80, 0x7F,
    0xA5, 0xA5, 0xA5, 0xA5,

    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xC0,
    0x00, 0x00, 0x90, 0x40,
    0x01, 0x00, 0xC0, 0xFF,
    0x01, 0x00, 0xC0, 0x7F,
    0x5A, 0x5A, 0x5A, 0x5A,
};

// The by-value aggregate takes a different path through every backend than the
// array does, so it carries its own payload: a sign-set quiet f64 NaN, a
// signaling f32 NaN, an integer field shaped like an f64 NaN's high word, and
// one finite float.
static unsigned char gValuePayload[WAXDET_VEC_BYTES] = {
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF,
    0x01, 0x00, 0x80, 0x7F,
    0x01, 0x00, 0xF0, 0x7F,
    0x00, 0x00, 0xA0, 0x3F,
    0x3C, 0x3C, 0x3C, 0x3C,
};

static struct WAXDET_VEC gArrayStorage[2];

static int gSawArrayCall = 0;
static int gSawValueCall = 0;

// The probe program's own output channel. The binding's default sink writes to
// stderr; routing it to stdout instead keeps the WAXDET lines in one stream,
// separate from the WAXDET-ERROR messages this file writes when something goes
// wrong.
static void DriverLog(const char* message, enum HostDeterminismProbe_LogLevel level) {
    (void)level;
    printf("%s\n", message);
}

static void ProbeHostVecs(struct WAXDET_VEC_ARRAY* result) {
    gSawArrayCall = 1;
    memcpy(gArrayStorage, gArrayPayload, sizeof gArrayPayload);
    result->data = gArrayStorage;
    result->count = (int32_t) (sizeof gArrayPayload / WAXDET_VEC_BYTES);
}

static void ProbeHostVec(struct WAXDET_VEC* result) {
    gSawValueCall = 1;
    memcpy(result, gValuePayload, sizeof gValuePayload);
}

static void ProbeHostRawArrayBytes(struct WxUInt8Array* result) {
    result->data = gArrayPayload;
    result->count = (int32_t) sizeof gArrayPayload;
}

static void ProbeHostRawValueBytes(struct WxUInt8Array* result) {
    result->data = gValuePayload;
    result->count = (int32_t) sizeof gValuePayload;
}

static int Fail(const char* what, struct WxError* err) {
    if (err != 0 && err->errorType != 0) {
        fprintf(stderr, "WAXDET-ERROR %s: %s\n", what, err->errorMessage ? err->errorMessage : err->errorType);
    } else {
        fprintf(stderr, "WAXDET-ERROR %s\n", what);
    }
    return 1;
}

int main(void) {
    struct HostDeterminismProbe_Config config;
    memset(&config, 0, sizeof config);
    config.heapSize = 4u * 1024u * 1024u;
    config.log = DriverLog;
    config.hostFns.ProbeHostVec = ProbeHostVec;
    config.hostFns.ProbeHostVecs = ProbeHostVecs;
    config.hostFns.ProbeHostRawArrayBytes = ProbeHostRawArrayBytes;
    config.hostFns.ProbeHostRawValueBytes = ProbeHostRawValueBytes;

    const char** errors = 0;
    struct HostDeterminismProbe_App* app = 0;
    // LoadApp returns the error COUNT, and validation reports one entry per
    // unbound required host fn. Printing only the first would name one of four
    // missing slots and hide the rest.
    int32_t loadErrors = HostDeterminismProbe_LoadApp(&config, &app, &errors);
    if (loadErrors != 0 || app == 0) {
        if (loadErrors <= 0) return Fail("LoadApp failed without reporting an error", 0);
        if (errors == 0) return Fail("LoadApp reported errors but named none", 0);
        for (int32_t i = 0; i < loadErrors; i++) {
            fprintf(stderr, "WAXDET-ERROR LoadApp: %s\n", errors[i] ? errors[i] : "(null)");
        }
        return 1;
    }

    struct WxError err;
    memset(&err, 0, sizeof err);
    HostDeterminismProbe_FrameBegin(app, 16.0f, &err);
    if (err.errorType != 0) return Fail("FrameBegin failed", &err);

    int32_t status = HostDeterminismProbe_api_HostDeterminismProbe_RunHostCompositeProbes(app, &err);
    if (err.errorType != 0) return Fail("RunHostCompositeProbes failed", &err);
    if (status != 0) return Fail("the probe program reported a failure", 0);
    // A crossing that never happened would leave every probe line absent, which
    // reads as a missing row rather than as the silence it is. Naming it here
    // turns it into a message.
    if (gSawArrayCall == 0) return Fail("the app never asked for the struct array", 0);
    if (gSawValueCall == 0) return Fail("the app never asked for the by-value struct", 0);

    HostDeterminismProbe_FrameEnd(app, &err);
    if (err.errorType != 0) return Fail("FrameEnd failed", &err);
    HostDeterminismProbe_UnloadApp(app);
    return 0;
}
