//! Marathon Shapes format handling.

use crate::{durandal::{bin::*, err::*, fixed::*, image::*, text::*},
            marathon::xfer::TransferMode};
use bitflags::bitflags;

/// Reads a color from a color table into `clut`.
fn read_color(b: &[u8], clut: &mut [ColorShp]) -> ResultS<()>
{
   read_data! {
      8, BE in b =>
      flag = u8[0];
      ind  = u8[1];
      r    = u16[2];
      g    = u16[4];
      b    = u16[6];
   }

   let cr = ok!(clut.get_mut(usize::from(ind)), "bad index")?;

   *cr = match flag {
      128 => ColorShp::Lit   {r, g, b},
      0   => ColorShp::Opaque{r, g, b},
      _   => {
         return Err(err_msg("invalid flag in color"));
      }
   };

   Ok(())
}

/// Reads all color tables.
fn color_tables(b: &[u8],
                    tab_ofs: usize,
                    tab_num: usize,
                    clr_num: usize)
   -> ResultS<Vec<Vec<ColorShp>>>
{
   let end = tab_num * clr_num * 8;

   let b = ok!(b.get(tab_ofs..tab_ofs + end), "bad offset")?;

   let mut v = vec![vec![ColorShp::Translucent; clr_num]; tab_num];
   let mut p = 0;

   for clut in v.iter_mut().take(tab_num) {
      for _ in 0..clr_num {
         read_color(ok!(b.get(p..p + 8), "not enough data")?, clut)?;
         p += 8;
      }
   }

   Ok(v)
}

/// Reads a `Bitmap`.
pub fn read_bitmap(b: &[u8]) -> ResultS<Bitmap>
{
   read_data! {
      26, BE in b =>
      width  = u16[0] usize;
      height = u16[2] usize;
      compr  = u16[4];
      flags  = u16[6];
      depth  = u16[8];
   }

   let compr = compr == u16::max_value();
   let flags = flag_ok!(BmpFlags, flags)?;
   let alpha = flags.contains(BmpFlags::TRANSPARENT);
   let cmajr = flags.contains(BmpFlags::COLUMN_MAJOR);

   if depth != 8 {
      bail!("invalid bit depth (should always be 8)");
   }

   let mut bmp = Bitmap::new(width, height, alpha, cmajr);
   let mut p   = 30 + if cmajr {4 * width} else {4 * height};

   let scanlines = if cmajr {width} else {height};
   let pitch     = if cmajr {height} else {width};

   if compr {
      // compressed scanlines (transparency RLE)
      for _ in 0..scanlines {
         read_data! {
            p + 4, BE in b =>
            fst = u16[p] usize;
            lst = u16[p + 2] usize;
         }

         let end = lst - fst;

         p += 4;

         if lst < fst || fst > pitch || lst > pitch {
            bail!("invalid compressed scanline");
         }

         for _ in 0..fst {
            bmp.cr.push(0);
         }

         bmp.cr.extend_from_slice(ok!(b.get(p..p + end), "not enough data")?);

         for _ in lst..pitch {
            bmp.cr.push(0);
         }

         p += end;
      }
   } else {
      // simple copy
      bmp.cr.extend_from_slice(ok!(b.get(p..p + width * height),
                                   "not enough data")?);
   }

   Ok(bmp)
}

/// Reads a `Frame`.
pub fn read_frame(b: &[u8]) -> ResultS<Frame>
{
   read_data! {
      36, BE in b =>
      flags   = u16[0];
      min_lt  = Fixed[2];
      bmp_ind = u16[6] usize;
      wrl_l   = Unit[16];
      wrl_r   = Unit[18];
      wrl_t   = Unit[20];
      wrl_b   = Unit[22];
      wrl_x   = Unit[24];
      wrl_y   = Unit[26];
   }

   let flags = flag_ok!(FrameFlags, flags)?;

   Ok(Frame{flags, min_lt, bmp_ind, wrl_l, wrl_r, wrl_t, wrl_b, wrl_x, wrl_y})
}

/// Reads a `Sequence`.
pub fn read_sequence(b: &[u8]) -> ResultS<Sequence>
{
   read_data! {
      88, BE in b =>
      name    = u8[4..38] slice;
      v_type  = u16[38];
      frames  = u16[40];
      ticks   = u16[42];
      key     = u16[44];
      xfer    = u16[46];
      xfer_pd = u16[48];
      snd_beg = OptU16[50];
      snd_key = OptU16[52];
      snd_end = OptU16[54];
      loop_f  = u16[58];
   }

   let name   = mac_roman_conv(ok!(pascal_str(name), "bad string")?);
   let xfer   = TransferMode::from_repr(xfer)?;
   let v_type = ViewType::from_repr(v_type)?;

   Ok(Sequence{name, v_type, frames, ticks, key, xfer, xfer_pd, snd_beg,
               snd_key, snd_end, loop_f})
}

/// Reads a `Collection`.
pub fn read_collection(b: &[u8]) -> ResultS<Collection>
{
   read_data! {
      544, BE in b =>
      version = u16[0];
      cl_type = u16[2];
      clr_num = u16[6] usize;
      tab_num = u16[8] usize;
      tab_ofs = u32[10] usize;
      seq_num = u16[14] usize;
      seq_ofs = u32[16] usize;
      frm_num = u16[20] usize;
      frm_ofs = u32[22] usize;
      bmp_num = u16[26] usize;
      bmp_ofs = u32[28] usize;
   }

   let cl_type = CollectionType::from_repr(cl_type)?;

   if version != 3 {
      bail!("invalid collection definition");
   }

   let tabs = color_tables(b, tab_ofs, tab_num, clr_num)?;
   let bmps = rd_ofstable(b, bmp_ofs, bmp_num, read_bitmap)?;
   let frms = rd_ofstable(b, frm_ofs, frm_num, read_frame)?;
   let seqs = rd_ofstable(b, seq_ofs, seq_num, read_sequence)?;

   Ok(Collection{ctyp: cl_type, tabs, bmps, frms, seqs})
}

/// Read all of the collections in a Shapes file.
pub fn read_shapes(b: &[u8]) -> ResultS<Vec<CollectionDef>>
{
   let mut cl = Vec::with_capacity(32);
   let mut p  = 0;

   for _ in 0..32 {
      read_data! {
         p + 32, BE in b =>
         lo_ofs = u32[p + 4] usize;
         lo_len = u32[p + 8] usize;
         hi_ofs = u32[p + 12] usize;
         hi_len = u32[p + 16] usize;
      }

      let c_lo = if lo_ofs == usize_from_u32(u32::max_value()) {
         None
      } else {
         let dat = ok!(b.get(lo_ofs..lo_ofs + lo_len), "bad offset")?;
         Some(read_collection(dat)?)
      };

      let c_hi = if hi_ofs == usize_from_u32(u32::max_value()) {
         None
      } else {
         let dat = ok!(b.get(hi_ofs..hi_ofs + hi_len), "bad offset")?;
         Some(read_collection(dat)?)
      };

      cl.push((c_lo, c_hi));

      p += 32;
   }

   Ok(cl)
}

impl Bitmap
{
   /// Creates an empty bitmap.
   pub fn new(w: usize, h: usize, alpha: bool, cmajr: bool) -> Self
   {
      Self{w, h, alpha, cmajr, cr: Vec::with_capacity(w * h)}
   }
}

impl<'a, 'b> ImageShp<'a, 'b>
{
   /// Creates an `ImageShp` with the given bitmap.
   pub fn new(bmp: &'a Bitmap, clut: &'b [ColorShp]) -> Self
   {
      Self{bmp, clut}
   }
}

impl Image for ImageShp<'_, '_>
{
   type Output = ColorShp;

   fn w(&self) -> usize {self.bmp.w}
   fn h(&self) -> usize {self.bmp.h}

   fn index(&self, x: usize, y: usize) -> &Self::Output
   {
      static TRANSLUCENT_COLOR: ColorShp = ColorShp::Translucent;

      let cr = usize::from(if self.bmp.cmajr {
         self.bmp.cr[y + x * self.bmp.h]
      } else {
         self.bmp.cr[x + y * self.bmp.w]
      });

      if self.bmp.alpha && cr == 0 {
         &TRANSLUCENT_COLOR
      } else {
         self.clut.get(cr).unwrap_or(&TRANSLUCENT_COLOR)
      }
   }
}

impl Color for ColorShp
{
   fn r(&self) -> u16
   {
      match *self {
         ColorShp::Translucent => 0,
         ColorShp::Opaque{r, ..} |
         ColorShp::Lit   {r, ..} => r,
      }
   }

   fn g(&self) -> u16
   {
      match *self {
         ColorShp::Translucent => 0,
         ColorShp::Opaque{g, ..} |
         ColorShp::Lit   {g, ..} => g,
      }
   }

   fn b(&self) -> u16
   {
      match *self {
         ColorShp::Translucent => 0,
         ColorShp::Opaque{b, ..} |
         ColorShp::Lit   {b, ..} => b,
      }
   }

   fn a(&self) -> u16
   {
      match *self {
         ColorShp::Translucent => 0,
         ColorShp::Opaque{..} |
         ColorShp::Lit   {..} => u16::max_value(),
      }
   }
}

/// A color in an `ImageShp`'s color table.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ColorShp
{
   /// A completely translucent color.
   Translucent,

   /// An opaque color which may be shaded.
   Opaque{/** The red component.   */ r: u16,
          /** The green component. */ g: u16,
          /** The blue component.  */ b: u16},

   /// An opaque color which may not be shaded.
   Lit{/** The red component.   */ r: u16,
       /** The green component. */ g: u16,
       /** The blue component.  */ b: u16},
}

/// An unpacked Shape bitmap.
#[derive(Debug, Eq, PartialEq)]
pub struct Bitmap
{
   w:     usize,
   h:     usize,
   cr:    Vec<u8>,
   alpha: bool,
   cmajr: bool,
}

/// An image from a Shape. This mainly just exists so that `Bitmap` can use the
/// `Image` trait.
#[derive(Debug, Eq, PartialEq)]
pub struct ImageShp<'a, 'b>
{
   bmp:  &'a Bitmap,
   clut: &'b [ColorShp],
}

/// A frame, also known as a low level shape.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Frame
{
   /// The flags for this frame.
   pub flags: FrameFlags,

   /// The minimum light level for this frame.
   pub min_lt: Fixed,

   /// The index of the bitmap this frame uses.
   pub bmp_ind: usize,

   /// The left translation for this frame.
   pub wrl_l: Unit,

   /// The right translation for this frame.
   pub wrl_r: Unit,

   /// The top translation for this frame.
   pub wrl_t: Unit,

   /// The bottom translation for this frame.
   pub wrl_b: Unit,

   /// The X translation for this frame.
   pub wrl_x: Unit,

   /// The Y translation for this frame.
   pub wrl_y: Unit,
}

/// A sequence, also known as a high level shape.
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
#[derive(Debug, Eq, PartialEq)]
pub struct Sequence
{
   /// The display name for this sequence.
   pub name: String,

   /// The view type for each frame in this sequence.
   pub v_type: ViewType,

   /// The number of frames in this sequence.
   pub frames: u16,

   /// The number of ticks each frame in this sequence takes.
   pub ticks: u16,

   /// The key frame index for this sequence.
   pub key: u16,

   /// The transfer mode to play over this sequence.
   pub xfer: TransferMode,

   /// The period in game ticks the transfer mode plays over.
   pub xfer_pd: u16,

   /// The sound to play at the beginning of this sequence.
   pub snd_beg: OptU16,

   /// The sound to play at the key frame of this sequence.
   pub snd_key: OptU16,

   /// The sound to play at the end of this sequence.
   pub snd_end: OptU16,

   /// Which frame to loop on.
   pub loop_f: u16,
}

/// A collection of color tables, bitmaps, frames and sequences.
#[derive(Debug, Eq, PartialEq)]
pub struct Collection
{
   /// The type of collection this is.
   pub ctyp: CollectionType,

   /// All of the color tables in this collection.
   pub tabs: Vec<Vec<ColorShp>>,

   /// All of the bitmaps in this collection.
   pub bmps: Vec<Bitmap>,

   /// All of the frames in this collection.
   pub frms: Vec<Frame>,

   /// All of the sequences in this collection.
   pub seqs: Vec<Sequence>,
}

/// A collection, which may have low- and high-definition variations, or none.
pub type CollectionDef = (Option<Collection>, Option<Collection>);

bitflags! {
   struct BmpFlags: u16
   {
      const TRANSPARENT  = 1 << 14;
      const COLUMN_MAJOR = 1 << 15;
   }
}

bitflags! {
   /// Flags for `Frame`.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   pub struct FrameFlags: u16
   {
      /// The player's torso will obscure the player's legs.
      const OBSCURE = 1 << 13;
      /// The bitmap will be flipped on the vertical axis.
      const FLIP_Y  = 1 << 14;
      /// The bitmap will be flipped on the horizontal axis.
      const FLIP_X  = 1 << 15;
   }
}

c_enum! {
   /// The type of a collection.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   #[derive(Debug)]
   pub enum CollectionType: u16
   {
      0 => Unused,
      1 => Wall,
      2 => Object,
      3 => Interface,
      4 => Scenery,
   }
}

c_enum! {
   /// The type of or number of views for a sequence.
   #[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
   #[derive(Debug)]
   pub enum ViewType: u16
   {
      1  => Anim,
      2  => Anim8from2,
      3  => Anim4from3,
      4  => Anim4,
      5  => Anim8from5,
      8  => Anim8,
      9  => Anim5from3,
      10 => Still,
      11 => Anim5,
   }
}

// EOF
