//! Marathon Wad format handling.

use crate::{durandal::{bin::*, err::*, image, text::mac_roman_cstr},
            marathon::{map, phy, pict, trm}};
use std::collections::BTreeMap;

/// Reads all chunks in an entry.
pub fn read_chunks(b: &[u8], old_dat: bool, siz_cnk: usize)
   -> ResultS<Vec<Chunk>>
{
   let mut chunks = Vec::new();
   let mut p = 0;

   let map_read_minfo = if old_dat {map::read_old_minf} else {map::read_minf};
   let map_read_sides = if old_dat {map::read_old_sids} else {map::read_sids};
   let map_read_polys = if old_dat {map::read_old_poly} else {map::read_poly};
   let map_read_light = if old_dat {map::read_old_lite} else {map::read_lite};

   while p < b.len() {
      read_data! {
         p + siz_cnk, BE in b =>
         iden = Ident[p];
         size = u32[p + 8] usize;
      }

      let beg  = p + siz_cnk;
      let end  = beg + size;
      let data = &b[beg..end];

      chunks.push(match &iden.0 {
         b"PICT" => Chunk::Pict(pict::load_pict(data)?),
         b"Minf" => Chunk::Minf(map_read_minfo(data)?),
         b"iidx" => Chunk::Iidx(rd_array(data, map::read_iidx)?),
         b"EPNT" => Chunk::Pnts(rd_array(data, map::read_epnt)?),
         b"PNTS" => Chunk::Pnts(rd_array(data, map::read_pnts)?),
         b"LINS" => Chunk::Lins(rd_array(data, map::read_lins)?),
         b"SIDS" => Chunk::Sids(rd_array(data, map_read_sides)?),
         b"POLY" => Chunk::Poly(rd_array(data, map_read_polys)?),
         b"OBJS" => Chunk::Objs(rd_array(data, map::read_objs)?),
         b"LITE" => Chunk::Lite(rd_array(data, map_read_light)?),
         b"plac" => Chunk::Plac(rd_array(data, map::read_plac)?),
         b"ambi" => Chunk::Ambi(rd_array(data, map::read_ambi)?),
         b"bonk" => Chunk::Bonk(rd_array(data, map::read_bonk)?),
         b"medi" => Chunk::Medi(rd_array(data, map::read_medi)?),
         b"plat" => Chunk::Plat(rd_array(data, map::read_plat)?),
         b"NOTE" => Chunk::Note(rd_array(data, map::read_note)?),
         b"term" => Chunk::Term(rd_array(data, trm::read_term)?),
         b"FXpx" => Chunk::Fxpx(rd_array(data, phy::read_fxpx)?),
         b"MNpx" => Chunk::Mnpx(rd_array(data, phy::read_mnpx)?),
         b"PRpx" => Chunk::Prpx(rd_array(data, phy::read_prpx)?),
         b"PXpx" => Chunk::Pxpx(rd_array(data, phy::read_pxpx)?),
         b"WPpx" => Chunk::Wppx(rd_array(data, phy::read_wppx)?),
         _ => Chunk::Data{iden, data: data.to_vec()},
      });

      p = end;
   }

   Ok(chunks)
}

/// Reads all entries in a `Wad`.
pub fn read_entries(b: &[u8],
                    old_wad: bool,
                    old_dat: bool,
                    siz_app: usize,
                    siz_ent: usize,
                    siz_cnk: usize)
   -> ResultS<BTreeMap<u16, Entry>>
{
   read_data! {
      128, BE in b =>
      dirofs  = u32[72] usize;
      numents = u16[76] usize;
   }

   let mut entries = BTreeMap::new();
   let mut p = dirofs;

   for i in 0..numents {
      read_data! {
         p + siz_ent, BE in b =>
         offset = u32[p] usize;
         size   = u32[p + 4] usize;
         index  = u16[p + 8];
      }

      let index = if old_wad {i as u16} else {index};

      let chunks  = read_chunks(&b[offset..offset + size], old_dat, siz_cnk)?;
      let appdata = b[p..p + siz_app].to_vec();

      entries.insert(index, Entry{chunks, appdata});

      p += siz_ent + siz_app;
   }

   Ok(entries)
}

/// Reads a Map file.
pub fn read_wad(b: &[u8]) -> ResultS<Wad>
{
   read_data! {
      128, BE in b =>
      ver_wad  = u16[0];
      ver_dat  = u16[2];
      name     = mac_roman_cstr[4..68] no_try;
      siz_app  = u16[78] usize;
      siz_wcnk = u16[80] usize;
      siz_went = u16[82] usize;
   }

   let old_dat = ver_dat == 0;
   let old_wad = match Ver::from_repr(ver_wad)? {
      Ver::Base => true,
      _         => false,
   };

   let siz_ent = if old_wad {8 } else {10};
   let siz_cnk = if old_wad {12} else {16};

   if !old_wad && siz_ent != siz_went {
      bail!("invalid entry size");
   }

   if !old_wad && siz_cnk != siz_wcnk {
      bail!("invalid chunk size");
   }

   let entries = read_entries(b, old_wad, old_dat, siz_app, siz_ent, siz_cnk)?;

   Ok(Wad{name, siz_app, entries})
}

/// Any kind of chunk in an `Entry`.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub enum Chunk
{
   /// A `PICT` chunk.
   Pict(image::Image8),
   /// A `Minf` chunk.
   Minf(map::Minf),
   /// An `iidx` chunk.
   Iidx(Vec<u16>),
   /// A `PNTS` chunk.
   Pnts(Vec<map::Point>),
   /// A `LINS` chunk.
   Lins(Vec<map::Line>),
   /// A `SIDS` chunk.
   Sids(Vec<map::Side>),
   /// A `POLY` chunk.
   Poly(Vec<map::Polygon>),
   /// A `LITE` chunk.
   Lite(Vec<map::Light>),
   /// An `OBJS` chunk.
   Objs(Vec<map::Object>),
   /// A `plac` chunk.
   Plac(Vec<map::ObjectFreq>),
   /// An `ambi` chunk.
   Ambi(Vec<map::SoundAmbi>),
   /// A `bonk` chunk.
   Bonk(Vec<map::SoundRand>),
   /// A `medi` chunk.
   Medi(Vec<map::Media>),
   /// A `plat` chunk.
   Plat(Vec<map::Platform>),
   /// A `NOTE` chunk.
   Note(Vec<map::Note>),
   /// A `term` chunk.
   Term(Vec<trm::Terminal>),
   /// A `FXpx` chunk.
   Fxpx(Vec<phy::Effect>),
   /// A `MNpx` chunk.
   Mnpx(Vec<phy::Monster>),
   /// A `PRpx` chunk.
   Prpx(Vec<phy::Projectile>),
   /// A `PXpx` chunk.
   Pxpx(Vec<phy::Physics>),
   /// A `WPpx` chunk.
   Wppx(Vec<phy::Weapon>),
   /// Any other type of chunk, which may have arbitrary data in it.
   Data{/** The name of the chunk. */ iden: Ident,
        /** The data. */              data: Vec<u8>},
}

/// An entry containing chunks and application-specific data.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Entry
{
   /// All of the chunks in this `Entry`.
   pub chunks: Vec<Chunk>,

   /// The application specific data for this Entry.
   pub appdata: Vec<u8>,
}

/// A Map file containing entries.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Wad
{
   /// The original name of this file.
   pub name: String,

   /// The size of each `Entry`'s `appdata` field.
   pub siz_app: usize,

   /// All of the entries in this `Wad`.
   pub entries: BTreeMap<u16, Entry>,
}

c_enum! {
   /// The version of a `Wad`.
   #[derive(Debug)]
   enum Ver: u16
   {
      0 => Base,
      1 => Dir,
      2 => Over,
      4 => Inf,
   }
}

// EOF
