use std::hint::black_box;

use codspeed_criterion_compat::{Criterion, criterion_group, criterion_main};

use sqpack_rs::phyb::{
    COLLISION_COUNT_BLOCK_SIZE, COLLISION_SPHERE_SIZE, PHYB_FULL_HEADER_SIZE, PHYB_STUB_SIZE,
    PHYB_VERSION_FULL, Phyb, SIM_CHAIN_INTRINSIC_SIZE, SIM_COLLISION_REF_SIZE, SIM_NODE_SIZE,
    SIMULATOR_COUNT_PREFIX_SIZE, SIMULATOR_INTRINSIC_SIZE,
};

fn build_v0_stub() -> Vec<u8> {
    let mut buf = Vec::with_capacity(PHYB_STUB_SIZE);
    for _ in 0..3 {
        buf.extend_from_slice(&0u32.to_le_bytes());
    }
    buf
}

fn name_buf(s: &str) -> [u8; 32] {
    let mut out = [0u8; 32];
    let bytes = s.as_bytes();
    let n = bytes.len().min(32);
    out[..n].copy_from_slice(&bytes[..n]);
    out
}

fn build_v1_minimal() -> Vec<u8> {
    let collision_offset: u32 = 16;
    let collision_section_size = COLLISION_COUNT_BLOCK_SIZE + COLLISION_SPHERE_SIZE;
    let simulator_offset: u32 =
        collision_offset + u32::try_from(collision_section_size).expect("fits");
    let intrinsic_end_in_sim_section = u32::try_from(SIMULATOR_INTRINSIC_SIZE).expect("fits");
    let chain_offset = intrinsic_end_in_sim_section;
    let chain_collision_offset =
        chain_offset + u32::try_from(SIM_CHAIN_INTRINSIC_SIZE).expect("fits");
    let chain_node_offset =
        chain_collision_offset + u32::try_from(SIM_COLLISION_REF_SIZE).expect("fits");

    let mut buf: Vec<u8> = Vec::new();

    buf.extend_from_slice(&PHYB_VERSION_FULL.to_le_bytes());
    buf.extend_from_slice(&1u32.to_le_bytes());
    buf.extend_from_slice(&collision_offset.to_le_bytes());
    buf.extend_from_slice(&simulator_offset.to_le_bytes());
    assert_eq!(buf.len(), PHYB_FULL_HEADER_SIZE);

    let mut counts = [0u8; COLLISION_COUNT_BLOCK_SIZE];
    counts[4] = 1;
    counts[5] = 0xCC;
    counts[6] = 0xCC;
    counts[7] = 0xCC;
    buf.extend_from_slice(&counts);

    let mut sphere = Vec::with_capacity(COLLISION_SPHERE_SIZE);
    sphere.extend_from_slice(&name_buf("col_sphere_0"));
    sphere.extend_from_slice(&name_buf("j_kao"));
    for v in [0.0f32, 0.0, 0.0, 1.0] {
        sphere.extend_from_slice(&v.to_le_bytes());
    }
    assert_eq!(sphere.len(), COLLISION_SPHERE_SIZE);
    buf.extend_from_slice(&sphere);

    assert_eq!(buf.len(), usize::try_from(simulator_offset).expect("fits"));

    buf.extend_from_slice(&1u32.to_le_bytes());

    let mut counters = [0u8; 8];
    counters[2] = 1;
    buf.extend_from_slice(&counters);

    buf.extend_from_slice(&[0u8; 32]);

    let mut offsets = [0u32; 8];
    offsets[2] = chain_offset;
    for o in offsets {
        buf.extend_from_slice(&o.to_le_bytes());
    }
    assert_eq!(
        buf.len(),
        usize::try_from(simulator_offset).expect("fits")
            + SIMULATOR_COUNT_PREFIX_SIZE
            + SIMULATOR_INTRINSIC_SIZE
    );

    let mut chain = Vec::with_capacity(SIM_CHAIN_INTRINSIC_SIZE);
    chain.extend_from_slice(&1u16.to_le_bytes());
    chain.extend_from_slice(&1u16.to_le_bytes());
    for v in [0.1f32, 1.0, 0.5, 0.2, 0.3] {
        chain.extend_from_slice(&v.to_le_bytes());
    }
    chain.extend_from_slice(&0.0f32.to_le_bytes());
    chain.extend_from_slice(&0.0f32.to_le_bytes());
    chain.extend_from_slice(&0.0f32.to_le_bytes());
    chain.extend_from_slice(&1u32.to_le_bytes());
    chain.extend_from_slice(&chain_collision_offset.to_le_bytes());
    chain.extend_from_slice(&chain_node_offset.to_le_bytes());
    assert_eq!(chain.len(), SIM_CHAIN_INTRINSIC_SIZE);
    buf.extend_from_slice(&chain);

    let mut cref = Vec::with_capacity(SIM_COLLISION_REF_SIZE);
    cref.extend_from_slice(&name_buf("col_sphere_0"));
    cref.extend_from_slice(&0i32.to_le_bytes());
    assert_eq!(cref.len(), SIM_COLLISION_REF_SIZE);
    buf.extend_from_slice(&cref);

    let mut node = Vec::with_capacity(SIM_NODE_SIZE);
    node.extend_from_slice(&name_buf("j_skirt_a"));
    node.extend_from_slice(&1.5f32.to_le_bytes());
    node.extend_from_slice(&0.2f32.to_le_bytes());
    node.extend_from_slice(&1.0f32.to_le_bytes());
    node.extend_from_slice(&1.0f32.to_le_bytes());
    node.extend_from_slice(&0.5f32.to_le_bytes());
    for _ in 0..6 {
        node.extend_from_slice(&0.0f32.to_le_bytes());
    }
    node.extend_from_slice(&0u32.to_le_bytes());
    node.extend_from_slice(&0u32.to_le_bytes());
    assert_eq!(node.len(), SIM_NODE_SIZE);
    buf.extend_from_slice(&node);

    buf
}

fn bench_phyb_parse(c: &mut Criterion) {
    let v0 = build_v0_stub();
    let v1 = build_v1_minimal();

    let mut group = c.benchmark_group("phyb.parse");
    group.bench_function("v0_stub", |b| {
        b.iter(|| {
            let phyb = Phyb::parse(black_box(&v0)).expect("parse");
            black_box(phyb);
        });
    });
    group.bench_function("v1_minimal", |b| {
        b.iter(|| {
            let phyb = Phyb::parse(black_box(&v1)).expect("parse");
            black_box(phyb);
        });
    });
    group.finish();
}

criterion_group!(benches, bench_phyb_parse);
criterion_main!(benches);
