use std::hint::black_box;

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

use sqpack_rs::eid::Eid;

const EID_MAGIC: [u8; 4] = *b"eid\0";
const EID_HEADER_BYTES: usize = 16;
const EID_V1_VERSION1: i16 = 0x3132;
const EID_V1_ENTRY_BYTES: usize = 64;
const EID_V1_NAME_LEN: usize = 32;
const EID_OLD_ENTRY_BYTES: usize = 44;
const EID_OLD_NAME_LEN: usize = 12;
const ENTRY_COUNT: usize = 64;

fn pack_name(name: &str, width: usize) -> Vec<u8> {
    assert!(name.len() <= width);
    let mut buf = vec![0u8; width];
    buf[..name.len()].copy_from_slice(name.as_bytes());
    buf
}

fn build_header(version1: i16, count: i32) -> Vec<u8> {
    let mut buf = Vec::with_capacity(EID_HEADER_BYTES);
    buf.extend_from_slice(&EID_MAGIC);
    buf.extend_from_slice(&version1.to_le_bytes());
    buf.extend_from_slice(&0x0000i16.to_le_bytes());
    buf.extend_from_slice(&count.to_le_bytes());
    buf.extend_from_slice(&0u32.to_le_bytes());
    assert_eq!(buf.len(), EID_HEADER_BYTES);
    buf
}

fn build_v1_eid() -> Vec<u8> {
    let count = i32::try_from(ENTRY_COUNT).expect("entry count fits i32");
    let mut buf = build_header(EID_V1_VERSION1, count);
    for i in 0..ENTRY_COUNT {
        let id_i32 = i32::try_from(i).expect("bench index fits i32");
        let offset_f32 = f32::from(i16::try_from(i).expect("bench index fits i16"));
        let name = format!("n_bone_{i}");
        buf.extend_from_slice(&pack_name(&name, EID_V1_NAME_LEN));
        buf.extend_from_slice(&id_i32.to_le_bytes());
        for v in [offset_f32 * 0.1, offset_f32 * 0.2, offset_f32 * 0.3] {
            buf.extend_from_slice(&v.to_le_bytes());
        }
        for v in [0.0f32; 3] {
            buf.extend_from_slice(&v.to_le_bytes());
        }
        buf.extend_from_slice(&0i32.to_le_bytes());
    }
    assert_eq!(
        buf.len(),
        EID_HEADER_BYTES + ENTRY_COUNT * EID_V1_ENTRY_BYTES,
    );
    buf
}

fn build_old_eid() -> Vec<u8> {
    let count = i32::try_from(ENTRY_COUNT).expect("entry count fits i32");
    let mut buf = build_header(0x0000, count);
    for i in 0..ENTRY_COUNT {
        let id_i32 = i32::try_from(i).expect("bench index fits i32");
        let offset_f32 = f32::from(i16::try_from(i).expect("bench index fits i16"));
        buf.extend_from_slice(&id_i32.to_le_bytes());
        for v in [offset_f32 * 0.1, offset_f32 * 0.2, offset_f32 * 0.3] {
            buf.extend_from_slice(&v.to_le_bytes());
        }
        for v in [0.0f32; 3] {
            buf.extend_from_slice(&v.to_le_bytes());
        }
        let name = format!("j_{i:03}");
        buf.extend_from_slice(&pack_name(&name, EID_OLD_NAME_LEN));
        buf.extend_from_slice(&0i32.to_le_bytes());
    }
    assert_eq!(
        buf.len(),
        EID_HEADER_BYTES + ENTRY_COUNT * EID_OLD_ENTRY_BYTES,
    );
    buf
}

fn bench_eid_parse(c: &mut Criterion) {
    let eid_v1 = build_v1_eid();
    let eid_old = build_old_eid();

    let mut group = c.benchmark_group("eid.parse");

    group.bench_function("v1_64_entries", |b| {
        b.iter(|| {
            let eid = Eid::parse(black_box(&eid_v1)).expect("parse");
            black_box(eid);
        });
    });

    group.bench_function("old_64_entries", |b| {
        b.iter(|| {
            let eid = Eid::parse(black_box(&eid_old)).expect("parse");
            black_box(eid);
        });
    });

    group.finish();
}

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