#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]

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, OGG_VORBIS_SEEK_TABLE_HEADER_SIZE, SCD_HEADER_SIZE,
    Scd,
};

const MONO_SPB: u16 = 500;
const MONO_BLOCK_ALIGN: i16 = 256;

const STEREO_SPB: u16 = 124;
const STEREO_BLOCK_ALIGN: i16 = 136;
const STEREO_BLOCK_COUNT: usize = 64;

const OGG_PAYLOAD_BYTES: usize = 4 * 1024;

fn build_scd_ms_adpcm(
    channels: i16,
    block_align: i16,
    samples_per_block: u16,
    payload: &[u8],
) -> 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(&channels.to_le_bytes());
    sub_info.extend_from_slice(&44_100i32.to_le_bytes());
    sub_info.extend_from_slice(&22_050i32.to_le_bytes());
    sub_info.extend_from_slice(&block_align.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(&samples_per_block.to_le_bytes());
    sub_info.extend_from_slice(&7u16.to_le_bytes());
    let coefs: [i16; 14] = [
        256, 0, 512, -256, 0, 0, 192, 64, 240, 0, 460, -208, 392, -232,
    ];
    for c in coefs {
        sub_info.extend_from_slice(&c.to_le_bytes());
    }
    assert_eq!(sub_info.len(), ADPCM_WAVE_FORMAT_SIZE);

    build_scd_raw(12, u32::from(channels as u16), &sub_info, payload)
}

fn build_scd_ogg_v2_passthrough(payload_bytes: usize) -> Vec<u8> {
    let mut sub_info = Vec::with_capacity(OGG_VORBIS_SEEK_TABLE_HEADER_SIZE);
    sub_info.push(2u8);
    sub_info.push(32u8);
    sub_info.push(0xCC);
    sub_info.extend_from_slice(&[0u8; 9]);
    sub_info.extend_from_slice(&1.0f32.to_le_bytes());
    sub_info.extend_from_slice(&0u32.to_le_bytes());
    sub_info.extend_from_slice(&0u32.to_le_bytes());
    sub_info.extend_from_slice(&[0u8; 8]);
    assert_eq!(sub_info.len(), OGG_VORBIS_SEEK_TABLE_HEADER_SIZE);

    let mut payload = vec![0u8; payload_bytes];
    payload[..4].copy_from_slice(b"OggS");

    build_scd_raw(6, 2, &sub_info, &payload)
}

fn build_scd_raw(format_raw: i32, channel_count: u32, sub_info: &[u8], payload: &[u8]) -> Vec<u8> {
    let audio_count: u16 = 1;
    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;

    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);
    buf.extend_from_slice(
        &u16::try_from(BINARY_HEADER_SIZE)
            .expect("BINARY_HEADER_SIZE fits u16")
            .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);

    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());
    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() + 4, 0);

    let desc_pos = u32::try_from(buf.len()).unwrap();
    buf[offsets_pos..offsets_pos + 4].copy_from_slice(&desc_pos.to_le_bytes());

    let payload_size = u32::try_from(payload.len()).unwrap();
    let sub_info_size = u32::try_from(sub_info.len()).unwrap();
    buf.extend_from_slice(&payload_size.to_le_bytes());
    buf.extend_from_slice(&channel_count.to_le_bytes());
    buf.extend_from_slice(&44_100u32.to_le_bytes());
    buf.extend_from_slice(&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(sub_info);
    buf.extend_from_slice(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_mono_adpcm_payload(block_align: usize) -> Vec<u8> {
    let mut block = vec![0u8; block_align];
    block[0] = 0;
    block[1..3].copy_from_slice(&16i16.to_le_bytes());
    block
}

fn build_stereo_adpcm_payload(block_align: usize) -> Vec<u8> {
    let mut block = vec![0u8; block_align];
    block[0] = 0;
    block[1] = 0;
    block[2..4].copy_from_slice(&16i16.to_le_bytes());
    block[4..6].copy_from_slice(&16i16.to_le_bytes());
    block
}

fn bench_scd_decode(c: &mut Criterion) {
    let mono_payload = build_mono_adpcm_payload(MONO_BLOCK_ALIGN as usize);
    let mono_scd = build_scd_ms_adpcm(1, MONO_BLOCK_ALIGN, MONO_SPB, &mono_payload);

    let one_block = build_stereo_adpcm_payload(STEREO_BLOCK_ALIGN as usize);
    let mut stereo_payload = Vec::with_capacity(STEREO_BLOCK_ALIGN as usize * STEREO_BLOCK_COUNT);
    for _ in 0..STEREO_BLOCK_COUNT {
        stereo_payload.extend_from_slice(&one_block);
    }
    let stereo_scd = build_scd_ms_adpcm(2, STEREO_BLOCK_ALIGN, STEREO_SPB, &stereo_payload);

    let ogg_scd = build_scd_ogg_v2_passthrough(OGG_PAYLOAD_BYTES);

    let mono_parsed = Scd::parse(&mono_scd).expect("mono scd parse");
    let stereo_parsed = Scd::parse(&stereo_scd).expect("stereo scd parse");
    let ogg_parsed = Scd::parse(&ogg_scd).expect("ogg scd parse");

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

    group.bench_function("ms_adpcm_single_block", |b| {
        b.iter(|| {
            let pcm = mono_parsed.tracks()[0]
                .decode_to_pcm16()
                .expect("mono decode");
            black_box(pcm);
        });
    });

    group.bench_function("ms_adpcm_multi_block_stereo", |b| {
        b.iter(|| {
            let pcm = stereo_parsed.tracks()[0]
                .decode_to_pcm16()
                .expect("stereo decode");
            black_box(pcm);
        });
    });

    group.bench_function("to_ogg_bytes_v2_passthrough", |b| {
        b.iter(|| {
            let bytes = ogg_parsed.tracks()[0].to_ogg_bytes().expect("to_ogg_bytes");
            black_box(bytes);
        });
    });

    group.finish();
}

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