use std::ffi::CStr;

use rodio::cpal::{BufferSize, SampleFormat, SampleRate, StreamConfig};
use wgpu::naga::FastHashMap;
use zerocopy::{FromBytes, Immutable, KnownLayout};

pub struct BgmFormat {
  pub tracks: FastHashMap<String, BgmTrack>,
}

pub struct BgmTrack {
  pub(super) config: StreamConfig,
  pub(super) format: SampleFormat,

  pub(super) track_offset: u32,
  pub(super) intro_size: u32,
  pub(super) track_size: u32,
}

impl BgmFormat {
  pub fn new(bytes: Vec<u8>) -> BgmFormat {
    #[derive(FromBytes, KnownLayout, Immutable)]
    #[repr(C, packed)]
    struct Track {
      track_offset: u32,
      _preload_size: u32,
      intro_size: u32,
      track_size: u32,

      format_type: u16,
      channels: u16,
      samples_per_sec: u32,
      average_bytes_per_sec: u32,
      block_align: u16,
      bits_per_sample: u16,
      extra_byte_count: u16,
      pad: u16,
    }
    type TrackName = [u8; 16];

    const TRACK_NAME_SIZE: usize = size_of::<TrackName>();
    const TRACK_SIZE: usize = size_of::<TrackName>() + size_of::<Track>();

    assert_eq!(TRACK_SIZE, 0x34);

    let mut count = 0;
    let mut tracks = FastHashMap::default();
    loop {
      let start = TRACK_SIZE * count;
      let track_name = TrackName::ref_from_bytes(&bytes[start..(start + TRACK_NAME_SIZE)]).unwrap();
      let track_name = CStr::from_bytes_until_nul(track_name).expect("name has no null byte");
      if track_name.count_bytes() == 0 {
        break;
      }

      let track_name = track_name.to_str().unwrap().to_owned();
      let track = Track::ref_from_bytes(&bytes[(start + TRACK_NAME_SIZE)..(start + TRACK_SIZE)]).unwrap();
      tracks.insert(
        track_name,
        BgmTrack {
          config: StreamConfig {
            channels: track.channels,
            sample_rate: SampleRate(track.samples_per_sec),
            buffer_size: BufferSize::Fixed(track.intro_size + track.track_size),
          },
          format: SampleFormat::I16,
          track_offset: track.track_offset,
          intro_size: track.intro_size,
          track_size: track.track_size,
        },
      );

      count += 1;
    }

    Self { tracks }
  }
}
