/*
 * hackers_bench.c — C/upb benchmark mirroring benches/hackers_bench.rs
 *
 * Proto:    proto/hackers.proto
 * Generated files: hackers.upb.h / .c, hackers.upb_minitable.h / .c
 *
 * Build:  make
 * Run:    ./hackers_bench
 *
 * Data files are read from ../data/bench/<name>.pb — the same files
 * produced by `cargo run --release --bin gen_bench_data -- --preset <name>`.
 *
 * The four benchmark groups match the Rust/criterion groups exactly:
 *
 *   shallow_parse  — Campaign_parse() + Arena_Free() per iteration.
 *                    upb fully decodes the message; roto merely scans
 *                    for field offsets.  This is the most important
 *                    comparison: total cost to "be ready to read".
 *
 *   deep_parse     — parse + walk Campaign → Operations → every Hacker,
 *                    touching each Hacker's handle field.
 *
 *   field_access   — message pre-parsed once outside the loop; each
 *                    micro-benchmark times a single field read.
 *                    upb: direct struct lookup.  roto: decode at offset.
 *
 *   iterate        — count_operations: parse + count top-level repeated.
 *                    count_all_crew:   parse + count nested repeated.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>

#include "hackers.upb.h"
#include "hackers.upb_minitable.h"

/* ==========================================================================
 * Timing
 * ========================================================================== */

static uint64_t now_ns(void) {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
}

/* ==========================================================================
 * Black-box sink — prevents the compiler from optimising away benchmark work.
 * We write the result of every meaningful computation here.
 * ========================================================================== */

static volatile uintptr_t g_sink;

/* ==========================================================================
 * File I/O
 * ========================================================================== */

typedef struct {
    uint8_t *data;
    size_t   len;
    char     path[256];
} BenchData;

static bool load_bench_data(BenchData *out, const char *name) {
    snprintf(out->path, sizeof(out->path), "data/bench/%s.pb", name);
    FILE *f = fopen(out->path, "rb");
    if (!f) {
        printf("[skip] %s not found — "
               "run `cargo run --release --bin gen_bench_data -- --preset %s` first\n",
               out->path, name);
        out->data = NULL;
        out->len  = 0;
        return false;
    }
    fseek(f, 0, SEEK_END);
    out->len = (size_t)ftell(f);
    rewind(f);
    out->data = malloc(out->len);
    if (!out->data) { fclose(f); return false; }
    fread(out->data, 1, out->len, f);
    fclose(f);
    return true;
}

static void free_bench_data(BenchData *d) {
    free(d->data);
    d->data = NULL;
}

/* ==========================================================================
 * Benchmark runner
 *
 * Finds a batch size such that one batch takes ≥1 ms, then runs batches
 * until at least BENCH_MIN_SECS of wall time has elapsed.  Reports the
 * mean ns/iter and, if bytes > 0, the MB/s throughput.
 * ========================================================================== */

#define BENCH_MIN_SECS 0.5

typedef void (*bench_fn)(void *state);

static void run_bench(bench_fn fn, void *state, size_t bytes, const char *label) {
    /* warmup */
    for (int i = 0; i < 5; i++) fn(state);

    /* calibrate: find batch size so one batch ≥ 1 ms */
    uint64_t batch = 1;
    while (batch < 10000000ULL) {
        uint64_t t0 = now_ns();
        for (uint64_t i = 0; i < batch; i++) fn(state);
        if (now_ns() - t0 >= 1000000ULL) break;   /* 1 ms */
        batch *= 4;
    }

    /* measure */
    uint64_t target_ns = (uint64_t)(BENCH_MIN_SECS * 1e9);
    uint64_t total_ns  = 0;
    uint64_t total_its = 0;
    while (total_ns < target_ns) {
        uint64_t t0 = now_ns();
        for (uint64_t i = 0; i < batch; i++) fn(state);
        total_ns  += now_ns() - t0;
        total_its += batch;
    }

    double ns_per_iter = (double)total_ns / (double)total_its;
    if (bytes > 0) {
        double mb_per_sec = (double)bytes / ns_per_iter * 1000.0;
        printf("  %-46s  %9.2f ns/iter  %8.2f MB/s\n",
               label, ns_per_iter, mb_per_sec);
    } else {
        printf("  %-46s  %9.2f ns/iter\n", label, ns_per_iter);
    }
}

/* ==========================================================================
 * shallow_parse — Campaign_parse() + upb_Arena_Free() per iteration
 *
 * Measures the full cost of becoming "ready to access any field", matching
 * the Rust `Campaign::new()` benchmark.  upb fully decodes; roto only scans.
 * ========================================================================== */

static void fn_shallow_parse(void *state) {
    BenchData *d = state;
    upb_Arena *arena = upb_Arena_New();
    Campaign  *c     = Campaign_parse((const char *)d->data, d->len, arena);
    g_sink = (uintptr_t)c;
    upb_Arena_Free(arena);
}

static void bench_shallow_parse(void) {
    const char *sizes[] = {"tiny", "small", "medium", "large", NULL};
    printf("\n=== shallow_parse ===\n");
    for (int i = 0; sizes[i]; i++) {
        BenchData d;
        if (!load_bench_data(&d, sizes[i])) continue;
        char label[80];
        snprintf(label, sizeof(label), "Campaign_parse/%s  [%zu B]", sizes[i], d.len);
        run_bench(fn_shallow_parse, &d, d.len, label);
        free_bench_data(&d);
    }
}

/* ==========================================================================
 * deep_parse — parse + walk Campaign → Operations → Hackers
 *
 * After Campaign_parse(), upb has already decoded everything.  The "deep"
 * walk is pointer-chasing through the decoded tree.  In roto each level
 * calls ::new(), paying another linear scan over that sub-message's bytes.
 * ========================================================================== */

static void fn_deep_parse(void *state) {
    BenchData *d = state;
    upb_Arena *arena = upb_Arena_New();
    Campaign  *c     = Campaign_parse((const char *)d->data, d->len, arena);

    size_t n_ops;
    const Operation * const *ops = Campaign_operations(c, &n_ops);
    size_t hacker_count = 0;
    for (size_t i = 0; i < n_ops; i++) {
        size_t n_crew;
        const Hacker * const *crew = Operation_crew(ops[i], &n_crew);
        for (size_t j = 0; j < n_crew; j++) {
            upb_StringView handle = Hacker_handle(crew[j]);
            g_sink = (uintptr_t)handle.data;
            hacker_count++;
        }
    }
    g_sink = hacker_count;
    upb_Arena_Free(arena);
}

static void bench_deep_parse(void) {
    const char *sizes[] = {"tiny", "small", "medium", NULL};
    printf("\n=== deep_parse ===\n");
    for (int i = 0; sizes[i]; i++) {
        BenchData d;
        if (!load_bench_data(&d, sizes[i])) continue;
        char label[80];
        snprintf(label, sizeof(label), "Campaign+Ops+Hackers/%s  [%zu B]", sizes[i], d.len);
        run_bench(fn_deep_parse, &d, d.len, label);
        free_bench_data(&d);
    }
}

/* ==========================================================================
 * field_access — individual field reads on a pre-parsed message
 *
 * Parse once outside the loop; each micro-benchmark measures the accessor
 * call itself.  upb: a struct-field read with a MiniTable lookup.
 * roto: decode the value at a pre-recorded byte offset.
 * ========================================================================== */

typedef struct {
    upb_Arena     *arena;
    Campaign      *campaign;
    Operation     *op;
    Hacker        *hacker;
    Worm          *worm;
} FieldState;

static void fn_field_campaign_name(void *s) {
    upb_StringView v = Campaign_name(((FieldState *)s)->campaign);
    g_sink = (uintptr_t)v.data;
}
static void fn_field_total_bytes_stolen(void *s) {
    g_sink = (uintptr_t)(uint64_t)Campaign_total_bytes_stolen(((FieldState *)s)->campaign);
}
static void fn_field_op_codename(void *s) {
    upb_StringView v = Operation_codename(((FieldState *)s)->op);
    g_sink = (uintptr_t)v.data;
}
static void fn_field_op_timestamp(void *s) {
    g_sink = (uintptr_t)(uint64_t)Operation_timestamp(((FieldState *)s)->op);
}
static void fn_field_op_successful(void *s) {
    g_sink = (uintptr_t)Operation_successful(((FieldState *)s)->op);
}
static void fn_field_hacker_handle(void *s) {
    upb_StringView v = Hacker_handle(((FieldState *)s)->hacker);
    g_sink = (uintptr_t)v.data;
}
static void fn_field_hacker_skill_level(void *s) {
    /* store float bits to avoid FPU → int conversion costs */
    float f = Hacker_skill_level(((FieldState *)s)->hacker);
    uint32_t bits; memcpy(&bits, &f, 4);
    g_sink = bits;
}
static void fn_field_hacker_is_elite(void *s) {
    g_sink = (uintptr_t)Hacker_is_elite(((FieldState *)s)->hacker);
}
static void fn_field_worm_polymorphic(void *s) {
    g_sink = (uintptr_t)Worm_polymorphic(((FieldState *)s)->worm);
}
static void fn_field_worm_payload(void *s) {
    upb_StringView v = Worm_payload(((FieldState *)s)->worm);
    g_sink = (uintptr_t)v.data;
}

static void bench_field_access(void) {
    BenchData d;
    if (!load_bench_data(&d, "small")) return;

    upb_Arena *arena = upb_Arena_New();
    Campaign  *campaign = Campaign_parse((const char *)d.data, d.len, arena);
    if (!campaign) { fprintf(stderr, "parse failed\n"); return; }

    size_t n_ops;
    const Operation * const *ops = Campaign_operations(campaign, &n_ops);
    if (n_ops == 0) { fprintf(stderr, "no operations\n"); return; }
    Operation *op = (Operation *)ops[0];  /* cast away const for state */

    size_t n_crew;
    const Hacker * const *crew = Operation_crew(op, &n_crew);
    if (n_crew == 0) { fprintf(stderr, "no crew\n"); return; }
    Hacker *hacker = (Hacker *)crew[0];

    const Worm *worm = Operation_worm(op);
    if (!worm) { fprintf(stderr, "no worm\n"); return; }

    FieldState state = {
        .arena    = arena,
        .campaign = campaign,
        .op       = op,
        .hacker   = hacker,
        .worm     = (Worm *)worm,
    };

    printf("\n=== field_access ===\n");
    run_bench(fn_field_campaign_name,         &state, 0, "campaign::name");
    run_bench(fn_field_total_bytes_stolen,    &state, 0, "campaign::total_bytes_stolen");
    run_bench(fn_field_op_codename,           &state, 0, "operation::codename");
    run_bench(fn_field_op_timestamp,          &state, 0, "operation::timestamp");
    run_bench(fn_field_op_successful,         &state, 0, "operation::successful");
    run_bench(fn_field_hacker_handle,         &state, 0, "hacker::handle");
    run_bench(fn_field_hacker_skill_level,    &state, 0, "hacker::skill_level (f32)");
    run_bench(fn_field_hacker_is_elite,       &state, 0, "hacker::is_elite (bool)");
    run_bench(fn_field_worm_polymorphic,      &state, 0, "worm::polymorphic (bool)");
    run_bench(fn_field_worm_payload,          &state, 0, "worm::payload (bytes)");

    upb_Arena_Free(arena);
    free_bench_data(&d);
}

/* ==========================================================================
 * iterate — count repeated fields at different depths
 *
 * count_operations: after parsing, Campaign_operations() returns pointer+count
 *                   in O(1) — upb already decoded the array.
 *                   roto's Campaign::new() scan IS the counting work.
 *
 * count_all_crew:   parse + walk ops + sum crew sizes.
 * ========================================================================== */

static void fn_count_operations(void *state) {
    BenchData *d = state;
    upb_Arena *arena = upb_Arena_New();
    Campaign  *c     = Campaign_parse((const char *)d->data, d->len, arena);
    size_t n;
    Campaign_operations(c, &n);
    g_sink = n;
    upb_Arena_Free(arena);
}

static void fn_count_all_crew(void *state) {
    BenchData *d = state;
    upb_Arena *arena = upb_Arena_New();
    Campaign  *c     = Campaign_parse((const char *)d->data, d->len, arena);
    size_t n_ops;
    const Operation * const *ops = Campaign_operations(c, &n_ops);
    size_t total = 0;
    for (size_t i = 0; i < n_ops; i++) {
        size_t n_crew;
        Operation_crew(ops[i], &n_crew);
        total += n_crew;
    }
    g_sink = total;
    upb_Arena_Free(arena);
}

static void fn_read_update_write(void *state) {
    BenchData *d = state;
    upb_Arena *arena = upb_Arena_New();
    Campaign  *c     = Campaign_parse((const char *)d->data, d->len, arena);

    upb_StringView name = { .data = "updated", .size = 7 };
    Campaign_set_name(c, name);

    size_t len;
    char *out = Campaign_serialize(c, arena, &len);
    g_sink = (uintptr_t)out;

    upb_Arena_Free(arena);
}

static void bench_read_update_write(void) {
    const char *sizes[] = {"tiny", "small", "medium", NULL};
    printf("\n=== read_update_write ===\n");
    for (int i = 0; sizes[i]; i++) {
        BenchData d;
        if (!load_bench_data(&d, sizes[i])) continue;
        char label[80];
        snprintf(label, sizeof(label), "Campaign_parse+set+serialize/%s  [%zu B]", sizes[i], d.len);
        run_bench(fn_read_update_write, &d, d.len, label);
        free_bench_data(&d);
    }
}

static void bench_iterate(void) {
    const char *sizes[] = {"tiny", "small", "medium", NULL};
    printf("\n=== iterate ===\n");
    for (int i = 0; sizes[i]; i++) {
        BenchData d;
        if (!load_bench_data(&d, sizes[i])) continue;

        char label[80];

        snprintf(label, sizeof(label), "count_operations/%s  [%zu B]", sizes[i], d.len);
        run_bench(fn_count_operations, &d, d.len, label);

        snprintf(label, sizeof(label), "count_all_crew/%s  [%zu B]", sizes[i], d.len);
        run_bench(fn_count_all_crew, &d, d.len, label);

        free_bench_data(&d);
    }
}

/* ==========================================================================
 * main
 * ========================================================================== */

int main(void) {
    printf("hackers_bench (upb / protobuf %s)\n", "33.1");
    printf("Data files: ../data/bench/<name>.pb\n");
    printf("Run `cargo run --release --bin gen_bench_data -- --preset <name>` to generate.\n");

    bench_shallow_parse();
    bench_deep_parse();
    bench_field_access();
    bench_iterate();
    bench_read_update_write();

    printf("\n");
    return 0;
}
