///|
/// Commands for controlling the audio mixer from any thread.
pub(all) enum AudioCommand {
  PlaySource(AudioSource, Float, Float, Bool) // source, gain, pan, looping
  PauseVoice(VoiceId)
  ResumeVoice(VoiceId)
  StopVoice(VoiceId)
  SeekVoice(VoiceId, Int)
  SetVoiceGain(VoiceId, Float)
  SetVoicePan(VoiceId, Float)
  SetMasterGain(Float)
} derive(Show)

///|
/// Queue of audio commands to be flushed on the mixer thread.
pub(all) struct CommandQueue {
  mut commands : Array[AudioCommand]
  mut pending_ids : Array[VoiceId]
}

///|
/// Create a new empty command queue.
pub fn new_command_queue() -> CommandQueue {
  CommandQueue::{ commands: [], pending_ids: [] }
}

///|
/// Enqueue a command. For PlaySource, returns the pre-allocated VoiceId.
pub fn enqueue(
  queue : CommandQueue,
  cmd : AudioCommand,
  mixer : Mixer,
) -> VoiceId? {
  match cmd {
    PlaySource(_) => {
      let id = VoiceId(mixer.next_voice_id)
      mixer.next_voice_id = mixer.next_voice_id + 1
      queue.pending_ids.push(id)
      queue.commands.push(cmd)
      Some(id)
    }
    _ => {
      queue.commands.push(cmd)
      None
    }
  }
}

///|
/// Number of pending commands.
pub fn queue_length(queue : CommandQueue) -> Int {
  queue.commands.length()
}

///|
/// Flush all commands to the mixer. Processes in FIFO order.
pub fn flush_commands(queue : CommandQueue, mixer : Mixer) -> Unit {
  let mut id_idx = 0
  for cmd in queue.commands {
    match cmd {
      PlaySource(source, gain, pan, looping) => {
        let id = queue.pending_ids[id_idx]
        id_idx = id_idx + 1
        let voice = Voice::{
          id,
          source,
          position: 0.0,
          gain,
          pan,
          state: VoiceState::Playing,
          looping,
          sample_rate: source_sample_rate(source),
          loop_start: 0,
          loop_end: 0,
          envelope: None,
          effects: [],
        }
        mixer.voices.push(voice)
      }
      PauseVoice(id) => pause_voice(mixer, id) |> ignore
      ResumeVoice(id) => resume_voice(mixer, id) |> ignore
      StopVoice(id) => stop_voice(mixer, id) |> ignore
      SeekVoice(id, frame) => seek_voice(mixer, id, frame) |> ignore
      SetVoiceGain(id, gain) =>
        match find_voice(mixer, id) {
          Some(voice) => voice.gain = gain
          None => ()
        }
      SetVoicePan(id, pan) =>
        match find_voice(mixer, id) {
          Some(voice) => voice.pan = pan
          None => ()
        }
      SetMasterGain(gain) => mixer.master_gain = gain
    }
  }
  queue.commands = []
  queue.pending_ids = []
}