//! FMOD System wrapper

use log;
use fmod::{self, ChannelControl};
use vec_map::VecMap;

use crate::{Sampler, Music, Voice, voice};

/// A high-level interface to an FMOD System
#[derive(Debug, PartialEq)]
pub struct Audition {
  pub system  : fmod::System,
  pub sampler : Sampler,
  pub music   : Music,
  pub voices  : VecMap <Voice>
}

impl Audition {
  /// Create with default FMOD system
  pub fn new() -> Self {
    Self::default()
  }
  /// Create with provided initialized FMOD system
  #[inline]
  pub fn with_system (system : fmod::System) -> Self {
    let sampler = Sampler::new (&system);
    let music   = Music::default();
    let voices  = VecMap::new();
    Audition { system, sampler, music, voices }
  }
  /// Sets the master volume on the master channel group
  #[inline]
  #[expect(clippy::missing_panics_doc)]
  pub fn set_master_volume (&self, volume : f32) {
    self.system.get_master_channel_group().unwrap().set_volume (volume).unwrap()
  }
  /// Load sound sample files with given filenames.
  #[inline]
  pub fn load_samples (&mut self, samples : VecMap <String>) {
    self.sampler.load (samples);
  }
  /// Load sound sample files from memory.
  #[inline]
  pub fn load_samples_memory (&mut self, samples : VecMap <&[u8]>) {
    self.sampler.load_memory (samples);
  }
  /// Load sound sample files from PCM data.
  #[inline]
  pub fn load_samples_pcm (&mut self, samples : VecMap <&[i16]>) {
    self.sampler.load_pcm (samples);
  }
  /// Load standard MIDI files (.mid) with given soundfont file paths (.dls)
  #[inline]
  pub fn load_songs_midi (&mut self, songs : VecMap <(String, String)>) {
    self.music = Music::load_midi (&mut self.system, songs);
  }
  /// Load standard MIDI files (.mid) from memory, with given soundfont file
  /// paths (.dls)
  #[inline]
  pub fn load_songs_midi_memory (&mut self, songs : VecMap <(&[u8], String)>) {
    self.music = Music::load_midi_memory (&mut self.system, songs);
  }
  #[expect(clippy::missing_panics_doc)]
  pub fn new_voice (&mut self) -> voice::Id {
    let z : voice::IndexType = 0;
    for i in z.. {
      if !self.voices.contains_key (i as usize) {
        assert!(self.voices.insert (i as usize, Voice::default()).is_none());
        return voice::Id (i)
      }
    }
    unreachable!();
  }
  #[inline]
  pub fn remove_voice (&mut self, voice_id : voice::Id) -> Option <Voice> {
    self.voices.remove (voice_id.0 as usize)
  }
  /// Convennience method to call the `update()` method of the FMOD system
  #[inline]
  #[expect(clippy::missing_panics_doc)]
  pub fn update (&mut self) {
    self.system.update().unwrap();
  }
}

impl Default for Audition {
  fn default() -> Self {
    let system = fmod::System::default().unwrap_or_else (|err|{
      log::error!("failed to create FMOD system: {err:?}");
      panic!()
    });
    Audition::with_system (system)
  }
}
