// ---
// tags: nox, bench
// crystal-type: source
// crystal-domain: comp
// ---
//! Criterion benchmarks for nox reduction patterns and jets.

use criterion::{Criterion, Throughput, black_box, criterion_group, criterion_main};
use nebu::Goldilocks;
use nox::call::NullCalls;
use nox::data::Reduction;
use nox::encode::{encode_atom, encode_tree, particle_of};
use nox::jets::merkle_verify::merkle_verify_jet;
use nox::reduce::reduce;
use nox::trace::NoTrace;
use nox::trace::TraceRow;

fn g(v: u64) -> Goldilocks {
    Goldilocks::new(v)
}

// โ”€โ”€ arithmetic patterns โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

fn bench_add(c: &mut Criterion) {
    c.bench_function("pattern/add", |b| {
        b.iter(|| {
            let mut ar = Reduction::<4096>::new();
            let obj = ar.atom(g(0)).unwrap();
            let t5 = ar.atom(g(5)).unwrap();
            let t1 = ar.atom(g(1)).unwrap();
            let va = ar.atom(g(12345678)).unwrap();
            let vb = ar.atom(g(87654321)).unwrap();
            let qa = ar.pair(t1, va).unwrap();
            let qb = ar.pair(t1, vb).unwrap();
            let body = ar.pair(qa, qb).unwrap();
            let fml = ar.pair(t5, body).unwrap();
            black_box(reduce(
                &mut ar,
                obj,
                fml,
                1_000_000,
                &NullCalls,
                &mut NoTrace,
            ))
        })
    });
}

fn bench_mul(c: &mut Criterion) {
    c.bench_function("pattern/mul", |b| {
        b.iter(|| {
            let mut ar = Reduction::<4096>::new();
            let obj = ar.atom(g(0)).unwrap();
            let t7 = ar.atom(g(7)).unwrap();
            let t1 = ar.atom(g(1)).unwrap();
            let va = ar.atom(g(999_999_999)).unwrap();
            let vb = ar.atom(g(111_111_111)).unwrap();
            let qa = ar.pair(t1, va).unwrap();
            let qb = ar.pair(t1, vb).unwrap();
            let body = ar.pair(qa, qb).unwrap();
            let fml = ar.pair(t7, body).unwrap();
            black_box(reduce(
                &mut ar,
                obj,
                fml,
                1_000_000,
                &NullCalls,
                &mut NoTrace,
            ))
        })
    });
}

fn bench_inv(c: &mut Criterion) {
    c.bench_function("pattern/inv", |b| {
        b.iter(|| {
            let mut ar = Reduction::<4096>::new();
            let obj = ar.atom(g(0)).unwrap();
            let t8 = ar.atom(g(8)).unwrap();
            let t1 = ar.atom(g(1)).unwrap();
            let va = ar.atom(g(999_999_937)).unwrap();
            let qa = ar.pair(t1, va).unwrap();
            let fml = ar.pair(t8, qa).unwrap();
            black_box(reduce(
                &mut ar,
                obj,
                fml,
                1_000_000,
                &NullCalls,
                &mut NoTrace,
            ))
        })
    });
}

// โ”€โ”€ hash pattern (hashrate) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

fn bench_hash(c: &mut Criterion) {
    // Each iteration is one Poseidon2 hash (25 rows: 24 rounds + 1 squeeze).
    // Throughput = hashes/sec.
    let mut group = c.benchmark_group("hash");
    group.throughput(Throughput::Elements(1));

    group.bench_function("pattern/hash", |b| {
        b.iter(|| {
            let mut ar = Reduction::<4096>::new();
            let obj = ar.atom(g(0)).unwrap();
            let t15 = ar.atom(g(15)).unwrap();
            let t1 = ar.atom(g(1)).unwrap();
            let input = ar.atom(g(42)).unwrap();
            let qi = ar.pair(t1, input).unwrap();
            let fml = ar.pair(t15, qi).unwrap();
            black_box(reduce(
                &mut ar,
                obj,
                fml,
                1_000_000,
                &NullCalls,
                &mut NoTrace,
            ))
        })
    });

    // Batch: chain N hashes in one reduce call to amortize Reduction allocation.
    // Measures sustained hashrate under real workload pressure.
    const CHAIN: u64 = 64;
    group.throughput(Throughput::Elements(CHAIN));
    group.bench_function("pattern/hash_chain_64", |b| {
        b.iter(|| {
            let mut ar = Reduction::<4096>::new();
            let obj = ar.atom(g(0)).unwrap();
            let t15 = ar.atom(g(15)).unwrap();
            let t1 = ar.atom(g(1)).unwrap();
            // Build a chain: hash(hash(hash(...hash(42)...))) 64 deep via compose.
            // compose tag = 3; hash tag = 15.
            let t3 = ar.atom(g(3)).unwrap();
            let input = ar.atom(g(42)).unwrap();
            let qi = ar.pair(t1, input).unwrap();
            let mut fml = ar.pair(t15, qi).unwrap();
            for _ in 1..CHAIN {
                let placeholder = ar.atom(g(0)).unwrap();
                let inner = ar.pair(t1, placeholder).unwrap();
                let hash_fml = ar.pair(t15, inner).unwrap();
                let pair = ar.pair(hash_fml, fml).unwrap();
                fml = ar.pair(t3, pair).unwrap();
            }
            black_box(reduce(
                &mut ar,
                obj,
                fml,
                1_000_000_000,
                &NullCalls,
                &mut NoTrace,
            ))
        })
    });

    group.finish();
}

// โ”€โ”€ look pattern (null provider) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

fn bench_look(c: &mut Criterion) {
    c.bench_function("pattern/look_null", |b| {
        b.iter(|| {
            let mut ar = Reduction::<4096>::new();
            let l0 = ar.atom(g(0)).unwrap();
            let l1 = ar.atom(g(0)).unwrap();
            let l2 = ar.atom(g(0)).unwrap();
            let l3 = ar.atom(g(0)).unwrap();
            let inner = ar.pair(l2, l3).unwrap();
            let mid = ar.pair(l1, inner).unwrap();
            let rc = ar.pair(l0, mid).unwrap();
            let rest = ar.atom(g(0)).unwrap();
            let obj = ar.pair(rc, rest).unwrap();
            let t17 = ar.atom(g(17)).unwrap();
            let t1 = ar.atom(g(1)).unwrap();
            let vns = ar.atom(g(0)).unwrap();
            let vkey = ar.atom(g(42)).unwrap();
            let ns_f = ar.pair(t1, vns).unwrap();
            let key_f = ar.pair(t1, vkey).unwrap();
            let body = ar.pair(ns_f, key_f).unwrap();
            let fml = ar.pair(t17, body).unwrap();
            black_box(reduce(
                &mut ar,
                obj,
                fml,
                1_000_000,
                &NullCalls,
                &mut NoTrace,
            ))
        })
    });
}

// โ”€โ”€ jet: merkle_verify โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

fn bench_merkle_verify(c: &mut Criterion) {
    use hemera::Hash;
    use hemera::tree::{hash_leaf, hash_node};

    let leaf0 = hash_leaf(b"leaf0", 0, false);
    let leaf1 = hash_leaf(b"leaf1", 1, false);
    let root = hash_node(&leaf0, &leaf1, true);

    let build_hash_data = |ar: &mut Reduction<4096>, hash: &Hash| {
        let bytes = hash.as_bytes();
        let mut limb = |off: usize| -> u32 {
            let mut buf = [0u8; 8];
            buf.copy_from_slice(&bytes[off..off + 8]);
            ar.atom(g(u64::from_le_bytes(buf))).unwrap()
        };
        let h0 = limb(0);
        let h1 = limb(8);
        let h2 = limb(16);
        let h3 = limb(24);
        let left = ar.pair(h0, h1).unwrap();
        let right = ar.pair(h2, h3).unwrap();
        ar.pair(left, right).unwrap()
    };

    c.bench_function("jet/merkle_verify", |b| {
        b.iter(|| {
            let mut ar = Reduction::<4096>::new();
            // object =  [root ๏ฟฟ formula | [current | path]]
            let root_data = build_hash_data(&mut ar, &root);
            let leaf_data = build_hash_data(&mut ar, &leaf0);
            let sib_data = build_hash_data(&mut ar, &leaf1);
            let depth_id = ar.atom(g(1)).unwrap();
            let formula = ar.atom(g(0)).unwrap();
            let root_fml = ar.pair(root_data, formula).unwrap();
            let meta = ar.pair(depth_id, root_fml).unwrap();
            let dir = ar.atom(g(0)).unwrap(); // left sibling
            let step = ar.pair(sib_data, dir).unwrap();
            let term = ar.atom(g(0)).unwrap();
            let path = ar.pair(step, term).unwrap();
            let data = ar.pair(leaf_data, path).unwrap();
            let obj = ar.pair(meta, data).unwrap();
            let body = ar.atom(g(0)).unwrap();
            let mut row = TraceRow::default();
            black_box(merkle_verify_jet(
                &mut ar,
                obj,
                body,
                1_000_000,
                &NullCalls,
                &mut NoTrace,
                0,
                &mut row,
            ))
        })
    });
}

// โ”€โ”€ encode_tree โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

fn bench_encode_tree(c: &mut Criterion) {
    let mut ar = Reduction::<4096>::new();
    let mut cur = ar.atom(g(0)).unwrap();
    for i in 1u64..=64 {
        let a = ar.atom(g(i)).unwrap();
        cur = ar.pair(a, cur).unwrap();
    }
    let root = cur;

    c.bench_function("encode/tree_64", |b| {
        b.iter(|| black_box(encode_tree(&ar, root)))
    });
}

fn bench_particle(c: &mut Criterion) {
    let encoded = encode_atom(g(12345));
    c.bench_function("encode/particle_atom", |b| {
        b.iter(|| black_box(particle_of(&encoded)))
    });
}

criterion_group!(
    benches,
    bench_add,
    bench_mul,
    bench_inv,
    bench_hash,
    bench_look,
    bench_merkle_verify,
    bench_encode_tree,
    bench_particle,
);
criterion_main!(benches);

Graph