use std::hint::black_box;

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

use sqpack_rs::scd::{ADPCM_WAVE_FORMAT_SIZE, BINARY_HEADER_SIZE, SCD_HEADER_SIZE, Scd};

struct TrackSpec<'a> {
    format_raw: i32,
    channel_count: u32,
    sample_rate: u32,
    sub_info: &'a [u8],
    payload: &'a [u8],
    declared_payload_size: Option<u32>,
}

fn build_scd(tracks: &[TrackSpec<'_>]) -> Vec<u8> {
    let mut buf = Vec::new();

    buf.extend_from_slice(b"SEDB");
    buf.extend_from_slice(b"SSCF");
    buf.extend_from_slice(&4u32.to_le_bytes());
    buf.push(0);
    buf.push(4);
    let scd_off = u16::try_from(BINARY_HEADER_SIZE).unwrap();
    buf.extend_from_slice(&scd_off.to_le_bytes());
    let size_pos = buf.len();
    buf.extend_from_slice(&0u64.to_le_bytes());
    buf.extend_from_slice(&0u64.to_le_bytes());
    for _ in 0..4 {
        buf.extend_from_slice(&0u32.to_le_bytes());
    }
    assert_eq!(buf.len(), BINARY_HEADER_SIZE);

    let audio_count = u16::try_from(tracks.len()).unwrap();
    buf.extend_from_slice(&0u16.to_le_bytes());
    buf.extend_from_slice(&0u16.to_le_bytes());
    buf.extend_from_slice(&audio_count.to_le_bytes());
    buf.extend_from_slice(&0u16.to_le_bytes());
    let audio_offsets_start = u32::try_from(BINARY_HEADER_SIZE + SCD_HEADER_SIZE).unwrap();
    let audio_offsets_size = u32::from(audio_count) * 4;
    let after_offsets = audio_offsets_start + audio_offsets_size;
    buf.extend_from_slice(&after_offsets.to_le_bytes());
    buf.extend_from_slice(&audio_offsets_start.to_le_bytes());
    buf.extend_from_slice(&0u32.to_le_bytes());
    buf.extend_from_slice(&0u32.to_le_bytes());
    buf.extend_from_slice(&0u32.to_le_bytes());
    buf.extend_from_slice(&0u16.to_le_bytes());
    buf.extend_from_slice(&0u16.to_le_bytes());
    assert_eq!(buf.len(), BINARY_HEADER_SIZE + SCD_HEADER_SIZE);

    let offsets_pos = buf.len();
    buf.resize(buf.len() + (tracks.len() * 4), 0);

    for (i, t) in tracks.iter().enumerate() {
        let desc_pos = buf.len();
        let off_le = u32::try_from(desc_pos).unwrap().to_le_bytes();
        buf[offsets_pos + i * 4..offsets_pos + i * 4 + 4].copy_from_slice(&off_le);
        let payload_size = t
            .declared_payload_size
            .unwrap_or_else(|| u32::try_from(t.payload.len()).unwrap());
        let sub_info_size = u32::try_from(t.sub_info.len()).unwrap();
        buf.extend_from_slice(&payload_size.to_le_bytes());
        buf.extend_from_slice(&t.channel_count.to_le_bytes());
        buf.extend_from_slice(&t.sample_rate.to_le_bytes());
        buf.extend_from_slice(&t.format_raw.to_le_bytes());
        buf.extend_from_slice(&0u32.to_le_bytes());
        buf.extend_from_slice(&0u32.to_le_bytes());
        buf.extend_from_slice(&sub_info_size.to_le_bytes());
        buf.extend_from_slice(&0u32.to_le_bytes());
        buf.extend_from_slice(t.sub_info);
        buf.extend_from_slice(t.payload);
    }

    let total = u64::try_from(buf.len()).unwrap();
    buf[size_pos..size_pos + 8].copy_from_slice(&total.to_le_bytes());
    buf
}

fn build_minimal_scd() -> Vec<u8> {
    build_scd(&[])
}

fn build_ms_adpcm_scd() -> Vec<u8> {
    let mut sub_info = Vec::with_capacity(ADPCM_WAVE_FORMAT_SIZE);
    sub_info.extend_from_slice(&2u16.to_le_bytes());
    sub_info.extend_from_slice(&2i16.to_le_bytes());
    sub_info.extend_from_slice(&44100i32.to_le_bytes());
    sub_info.extend_from_slice(&22050i32.to_le_bytes());
    sub_info.extend_from_slice(&512i16.to_le_bytes());
    sub_info.extend_from_slice(&4u16.to_le_bytes());
    sub_info.extend_from_slice(&32i16.to_le_bytes());
    sub_info.extend_from_slice(&512u16.to_le_bytes());
    sub_info.extend_from_slice(&7u16.to_le_bytes());
    for c in 0..14i16 {
        sub_info.extend_from_slice(&c.to_le_bytes());
    }
    assert_eq!(sub_info.len(), ADPCM_WAVE_FORMAT_SIZE);

    let payload = vec![0u8; 128];
    build_scd(&[TrackSpec {
        format_raw: 12,
        channel_count: 2,
        sample_rate: 44100,
        sub_info: &sub_info,
        payload: &payload,
        declared_payload_size: None,
    }])
}

fn bench_scd_parse(c: &mut Criterion) {
    let minimal = build_minimal_scd();
    let adpcm = build_ms_adpcm_scd();

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

    group.bench_function("minimal_scd", |b| {
        b.iter(|| {
            let scd = Scd::parse(black_box(&minimal)).expect("parse");
            black_box(scd);
        });
    });

    group.bench_function("ms_adpcm_track", |b| {
        b.iter(|| {
            let scd = Scd::parse(black_box(&adpcm)).expect("parse");
            black_box(scd);
        });
    });

    group.finish();
}

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