//! Marathon Sounds format handling.

use crate::durandal::{bin::*, err::*, fixed::*, sound::*};
use bitflags::bitflags;
use std::collections::BTreeMap;

/// Reads a sound.
pub fn read_sound(b: &[u8]) -> ResultS<Sound16>
{
   read_data! {
      21, BE in b =>
      len    = u32[4] usize;
      rate   = u16[8];
      lp_beg = u32[12] usize;
      lp_end = u32[16] usize;
      magic  = u8[20];
   }

   match magic {
      0 => {
         let stream = &b[22..22 + len];
         Ok(Sound16::new_from_8(rate, lp_beg, lp_end, stream))
      }
      0xFF => {
         read_data! {
            63, BE in b =>
            len = u32[22] usize;
            bps = u16[48];
         }

         match bps {
            16 => {
               let stream = &b[63..63 + len * 2];
               Ok(Sound16::new_from_16(rate, lp_beg, lp_end, stream))
            }
            8 => {
               let stream = &b[63..63 + len];
               Ok(Sound16::new_from_8(rate, lp_beg, lp_end, stream))
            }
            _ => bail!("bad bits per sample"),
         }
      }
      _ => bail!("invalid magic number"),
   }
}

/// Reads a sound definition.
pub fn read_sound_def(b: &[u8]) -> ResultS<Option<(Vec<usize>, u16, SoundDef)>>
{
   read_data! {
      64, BE in b =>
      index    = u16[0];
      volume   = u16[2];
      flags    = u16[4];
      chance   = u16[6];
      pitch_lo = Fixed[8];
      pitch_hi = Fixed[12];
      n_sounds = u16[16] usize;
      grp_ofs  = u32[20] usize;
   }

   let flags  = flag_ok!(SoundFlags, flags)?;
   let volume = Volume::from_repr(volume)?;

   if index == u16::max_value() {
      return Ok(None);
   }

   if n_sounds > 5 {
      bail!("too many sounds");
   }

   let mut ofs = Vec::with_capacity(n_sounds);
   let mut p   = 36;

   for _ in 0..n_sounds {
      ofs.push(grp_ofs + usize_from_u32(u32b(&b[p..])));
      p += 4;
   }

   let sounds = Vec::with_capacity(n_sounds);

   Ok(Some((ofs, index,
            SoundDef{volume, flags, chance, pitch_lo, pitch_hi, sounds})))
}

/// Reads all sounds from a Sound file.
pub fn read_sounds(b: &[u8]) -> ResultS<Vec<SoundTable>>
{
   read_data! {
      260, BE in b =>
      version = u32[0];
      magic   = u8[4..8] slice;
      src_num = u16[8] usize;
      snd_num = u16[10] usize;
   }

   if version != 1 || magic != b"snd2" {
      bail!("bad sound header");
   }

   let mut sc = Vec::with_capacity(src_num);
   let mut p  = 260;

   for _ in 0..src_num {
      let mut st = BTreeMap::new();

      for _ in 0..snd_num {
         if let Some((ofs, idx, mut def)) = read_sound_def(&b[p..p + 64])? {
            for &ofs in &ofs {
               def.sounds.push(read_sound(&b[ofs..])?);
            }

            st.insert(idx, def);
         }

         p += 64;
      }

      sc.push(st);
   }

   Ok(sc)
}

/// A sound definition containing one, many or no sounds.
#[derive(Debug, Eq, PartialEq)]
pub struct SoundDef
{
   /// The volume type for this sound.
   pub volume: Volume,

   /// The flags for this sound.
   pub flags: SoundFlags,

   /// The chance out of `u16::max_value()` that this sound will not play.
   pub chance: u16,

   /// The low random pitch bound.
   pub pitch_lo: Fixed,

   /// The high random pitch bound.
   pub pitch_hi: Fixed,

   /// All of the sounds in this collection.
   pub sounds: Vec<Sound16>,
}

/// A table of sound definitions.
pub type SoundTable = BTreeMap<u16, SoundDef>;

bitflags! {
   /// Flags for `SoundDef`.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   pub struct SoundFlags: u16
   {
      /// The sound will not restart when trying to play over itself.
      const NO_RESTART        = 1;
      /// The sound will not switch channels when trying to play over itself.
      const NO_CHANNEL_SWITCH = 1 << 1;
      /// The pitch variance will be halved.
      const LESS_PITCH_CHANGE = 1 << 2;
      /// The pitch variance will be nullified.
      const NO_PITCH_CHANGE   = 1 << 3;
      /// The sound will play even when completely obstructed by walls.
      const NO_OBSTRUCTION    = 1 << 4;
      /// The sound will play even when completely obstructed by media.
      const NO_MEDIA_OBSTRUCT = 1 << 5;
      /// The sound will have special stereo effects.
      const AMBIENT           = 1 << 6;
   }
}

c_enum! {
   /// The type of volume this sound has.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   #[derive(Debug)]
   pub enum Volume: u16
   {
      0 => Quiet,
      1 => Normal,
      2 => Loud,
   }
}

// EOF
