/* beam_probe — data for the "How the engine works" page of the Vectrex Studio SDK docs, taken from the REAL emitter and the
 * REAL Rust beam model, linked against a fake bus. Prints JSON on stdout.
 *
 * Build it from sdk/uvm2-sdk, after tools/build_host_tools.sh has built the Rust crate:
 *
 *   cc -O2 -w -DUVM2_HOST -DUVM2_BENCH_NO_CORE1 -DUVM2_SUBUNITS -DUVM2_HZ=0 \
 *      -DUVM2_CMD_CAPACITY=65536u -I. -o beam_probe beam_probe.c uvm2_draw.c uvm2_hud.c \
 *      uvm2_text.c tools/uvm2_host_stubs.c \
 *      ../vectrex-draw/cabi/target/release/libvectrex_draw_cabi.a
 *   ./beam_probe > beam.json
 */
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "uvm2_bus.h"
#include "uvm2_draw.h"

uvm2_stats_t uvm2_stats;
uint32_t uvm2_exec(const uint8_t *c, uint32_t n);
void     uvm2_bus_delay(uint32_t c) { (void)c; }
void     uvm2_via_write(uint32_t r, uint32_t d) { (void)r; (void)d; }
static const uint8_t *g_list; static uint32_t g_n;
uint32_t uvm2_exec(const uint8_t *c, uint32_t n){ g_list = c; g_n = n; return 0; }

void vx_ramp_params(int dx, int dy, int *vx, int *vy, unsigned *t1);
void vx_ramp_params_jump(int dx, int dy, int *vx, int *vy, unsigned *t1);
void vx_chain_reset(void);

static const char *rname(uint32_t r)
{
    static const char *n[16] = {"ORB","ORA","DDRB","DDRA","T1CL","T1CH","T1LL","T1LH",
                                "T2L","T2H","SR","ACR","PCR","IFR","IER","ORA_NH"};
    return n[r & 15];
}

static void dump_list(const char *key, int from)
{
    printf("\"%s\":[", key);
    uint32_t t = 0;
    for (uint32_t i = 0; i < g_n; i++){
        uint32_t v = (uint32_t)g_list[i*3] | ((uint32_t)g_list[i*3+1] << 8)
                   | ((uint32_t)g_list[i*3+2] << 16);
        uint32_t reg = (v >> 8) & 0xF, d = UVM2_CMD_DELAY(v);
        if ((int)i >= from)
            printf("%s{\"i\":%u,\"t\":%u,\"reg\":\"%s\",\"data\":%u,\"delay\":%u}",
                   (int)i > from ? "," : "", i, t, rname(reg), v & 0xFF, d);
        t += 1 + d;
    }
    printf("],\n");
}

extern volatile uint8_t uvm2_hud;

/* The ramps the VIA really receives: (T1CL, the X rate written to Port A just before it). */
static int ramps(int *t1, int *rate, int max)
{
    int n = 0, last_a = 0;
    for (uint32_t i = 0; i < g_n && n < max; i++){
        uint32_t v = (uint32_t)g_list[i*3] | ((uint32_t)g_list[i*3+1] << 8)
                   | ((uint32_t)g_list[i*3+2] << 16);
        uint32_t reg = (v >> 8) & 0xF, d = v & 0xFF;
        if (reg == UVM2_VIA_PORTA) last_a = (int8_t)d;
        if (reg == UVM2_VIA_T1CL){ t1[n] = (int)d; rate[n] = last_a; n++; }
    }
    return n;
}

int main(void)
{
    uvm2_draw_init();
    uvm2_hud = 0;
    printf("{\n");

    /* 1. The model alone: rate and T1 for a horizontal stroke of L device units. */
    printf("\"ramp\":[");
    for (int L = 1; L <= 120; L++){
        int vx, vy; unsigned t1, jt1; int jvx, jvy;
        vx_ramp_params(L, 0, &vx, &vy, &t1);
        vx_ramp_params_jump(L, 0, &jvx, &jvy, &jt1);
        printf("%s{\"L\":%d,\"vx\":%d,\"t1\":%u,\"jvx\":%d,\"jt1\":%u}", L > 1 ? "," : "",
               L, vx, t1, jvx, jt1);
    }
    printf("],\n");

    /* 1b. What the emitter sends: a jump, a stroke from rest, then a chained stroke. */
    printf("\"emitted\":[");
    for (int L = 1; L <= 100; L++){
        int t1[8], rate[8];
        uvm2_hud = 0;
        uvm2_frame_begin();
        uvm2_draw_intensity(100);
        uvm2_draw_move_abs(-60, 0);
        uvm2_draw_delta(L, 0);
        uvm2_draw_delta(L, 0);
        uvm2_frame_end();
        int n = ramps(t1, rate, 8);
        printf("%s{\"L\":%d,\"n\":%d,\"rest_t1\":%d,\"rest_rate\":%d,\"chain_t1\":%d,\"chain_rate\":%d}",
               L > 1 ? "," : "", L, n, t1[n-2], rate[n-2], t1[n-1], rate[n-1]);
    }
    printf("],\n");

    /* 2. Emitter cost per CHAINED stroke vs length, and per jump+stroke. */
    printf("\"cost\":[");
    const int Ls[] = {1,2,3,4,6,8,12,16,24,32,48,64,80,100};
    for (unsigned k = 0; k < sizeof Ls / sizeof *Ls; k++){
        int L = Ls[k], N = 16;
        uvm2_hud = 0;
        uvm2_frame_begin();
        uvm2_draw_intensity(100);
        uvm2_draw_move_abs(-60, 0);
        uvm2_draw_delta(L, 0);                       /* warm-up: first stroke from rest */
        uint32_t c0 = uvm2_list_cycles(), n0 = uvm2_list_commands();
        for (int i = 0; i < N; i++) uvm2_draw_delta((i & 1) ? -L : L, 0);
        uint32_t cc = uvm2_list_cycles() - c0, cn = uvm2_list_commands() - n0;
        uvm2_frame_end();

        /* the same strokes, each preceded by a blanked jump of the same length */
        uvm2_frame_begin();
        uvm2_draw_intensity(100);
        uvm2_draw_move_abs(-60, 0);
        uvm2_draw_delta(L, 0);
        c0 = uvm2_list_cycles(); n0 = uvm2_list_commands();
        for (int i = 0; i < N; i++){ uvm2_draw_move(0, (i & 1) ? -L : L); uvm2_draw_delta((i & 1) ? -L : L, 0); }
        uint32_t jc = uvm2_list_cycles() - c0, jn = uvm2_list_commands() - n0;
        uvm2_frame_end();
        printf("%s{\"L\":%d,\"chain_cyc\":%.1f,\"chain_cmd\":%.1f,\"jump_cyc\":%.1f,\"jump_cmd\":%.1f}",
               k ? "," : "", L, cc/(double)N, cn/(double)N, jc/(double)N, jn/(double)N);
    }
    printf("],\n");

    uvm2_hud = 0;
    /* 3. Command-by-command: a jump + a three-stroke chain. */

    uvm2_hud = 0;
    uvm2_frame_begin();
    uvm2_draw_intensity(100);
    uvm2_draw_move_abs(-30, -30);
    uvm2_draw_delta(40, 0);
    uvm2_draw_delta(0, 40);
    uvm2_draw_delta(-40, -40);
    uvm2_frame_end();
    dump_list("triangle", 0);
    uvm2_hud = 0;
    printf("\"end\":0\n}\n");
    return 0;
}
