//! QuickDraw PICT format loader.

use crate::durandal::{bin::*, err::*, image::*};
use std::convert::TryFrom;

// Reads a `PixMap` header.
fn read_pm_header<'a>(b: &'a [u8],
                      pack: bool,
                      clip: bool,
                      im: &Image8)
   -> ResultS<(&'a [u8], Header)>
{
   read_data! {
      endian: BIG, buf: b, size: 36, start: 0, data {
         let pt_fl  = u16[0];
         let top    = u16[2] usize;
         let left   = u16[4] usize;
         let bottom = u16[6] usize;
         let right  = u16[8] usize;
         let pack_t = u16[12] enum PackType;
         let depth  = u16[28] enum Depth;
      }
   }

   if pt_fl & 0x8000 == 0 {
      bail!("PICT1 not supported");
   }

   if right - left != im.w() || bottom - top != im.h() {
      bail!("image bounds are incorrect");
   }

   let mut p = 46;

   // get CLUT if packed
   let clut = if pack {
      let (clut, sz) = get_clut(&b[p..])?;
      p += sz;
      Some(clut)
   } else {
      None
   };

   p += 18; // srcRect, dstRect, mode

   if clip {
      p += usize::from(u16b(&b[p..])); // maskRgn
   }

   let rle = pack_t == PackType::Default ||
             pack_t == PackType::Rle16 && depth == Depth::_16 ||
             pack_t == PackType::Rle32 && depth == Depth::_32;

   let pitch = usize::from(pt_fl & 0x3FFF);

   Ok((&b[p..], Header{pitch, pack_t, depth, clut, rle}))
}

// Reads an indexed `PixMap`.
fn read_pm_ind(mut im: Image8, b: &[u8], hdr: Header) -> ResultS<Image8>
{
   let clut = ok!(hdr.clut, "no CLUT in indexed mode")?;
   let mut p = 0;

   if hdr.pitch < 8 && hdr.depth == Depth::_8 {
      // uncompressed 8-bit colormap indices
      for _ in 0..im.h() {
         for _ in 0..im.w() {
            let idx = usize::from(b[p]);

            im.cr.push(ok!(clut.get(idx), "invalid index")?.clone());

            p += 1;
         }
      }

      Ok(im)
   } else if hdr.rle {
      // RLE compressed 1, 2, 4 or 8 bit colormap indices
      for _ in 0..im.h() {
         let (d, pp) = read_rle::<u8>(&b[p..], hdr.pitch)?;

         let d = if hdr.depth < Depth::_8 {
            expand_data(d, hdr.depth)?
         } else {
            d
         };

         p += pp;

         for &idx in &d {
            im.cr
              .push(ok!(clut.get(usize::from(idx)), "invalid index")?.clone());
         }
      }

      Ok(im)
   } else {
      bail!("invalid configuration")
   }
}

// Reads a R5G5B5 `PixMap`.
fn read_pm_16(mut im: Image8, b: &[u8], hdr: Header) -> ResultS<Image8>
{
   let mut p = 0;

   if hdr.pitch < 8 || hdr.pack_t == PackType::None {
      // uncompressed R5G5B5
      for _ in 0..im.h() {
         for _ in 0..im.w() {
            let cr = u16b(&b[p..]);

            im.cr.push(r5g5b5_to_rgb8(cr));
            p += 2;
         }
      }

      Ok(im)
   } else if hdr.rle {
      // RLE compressed R5G5B5
      for _ in 0..im.h() {
         let (d, pp) = read_rle::<u16>(&b[p..], hdr.pitch)?;

         p += pp;

         for &cr in &d {
            im.cr.push(r5g5b5_to_rgb8(cr));
         }
      }

      Ok(im)
   } else {
      bail!("invalid configuration")
   }
}

// Reads a RGB8 `PixMap`.
fn read_pm_32(mut im: Image8, b: &[u8], hdr: Header) -> ResultS<Image8>
{
   let mut p = 0;

   if hdr.pitch < 8 ||
      hdr.pack_t == PackType::None ||
      hdr.pack_t == PackType::NoPad
   {
      // uncompressed RGB8 or XRGB8
      for _ in 0..im.h() {
         for _ in 0..im.w() {
            if hdr.pack_t != PackType::NoPad {
               p += 1;
            }

            read_data! {
               endian: BIG, buf: b, size: 3, start: p, data {
                  let r = u8[0];
                  let g = u8[1];
                  let b = u8[2];
               }
            }

            im.cr.push(Color8::new(r, g, b));

            p += 3;
         }
      }

      Ok(im)
   } else if hdr.rle {
      // RLE compressed RGB8
      let pitch = hdr.pitch - im.w(); // remove padding byte from pitch
      for _ in 0..im.h() {
         let (d, pp) = read_rle::<u8>(&b[p..], pitch)?;

         p += pp;

         for x in 0..im.w() {
            let r = d[x + im.w()];
            let g = d[x + im.w() * 2];
            let b = d[x + im.w() * 3];
            im.cr.push(Color8::new(r, g, b));
         }
      }

      Ok(im)
   } else {
      bail!("invalid configuration")
   }
}

// Process a `CopyBits` operation.
fn read_pm_area(im: Image8, b: &[u8], pack: bool, clip: bool)
   -> ResultS<Image8>
{
   let p = if pack {0} else {4};
   let (b, hdr) = read_pm_header(&b[p..], pack, clip, &im)?;

   match hdr.depth {
      Depth::_1 |
      Depth::_2 |
      Depth::_4 |
      Depth::_8 => read_pm_ind(im, b, hdr),
      Depth::_16 => read_pm_16(im, b, hdr),
      Depth::_32 => read_pm_32(im, b, hdr),
   }
}

/// Load a `PICT` image.
pub fn load_pict(b: &[u8]) -> ResultS<Image8>
{
   read_data! {
      endian: BIG, buf: b, size: 10, start: 0, data {
         let h = u16[6] usize;
         let w = u16[8] usize;
      }
   }

   if w * h > 16_000_000 {
      bail!("image is too large");
   }

   let im = Image8::new(w, h);

   let mut p = 10; // size of header

   while p < b.len() {
      read_data! {
         endian: BIG, buf: b, size: 2, start: p, data {
            let op = u16[0];
         }
      }

      p += 2;

      match op {
         0x0098 => {
            // PackBitsRect
            return read_pm_area(im, &b[p..], true, false);
         }
         0x0099 => {
            // PackBitsRgn
            return read_pm_area(im, &b[p..], true, true);
         }
         0x009A => {
            // DirectBitsRect
            return read_pm_area(im, &b[p..], false, false);
         }
         0x009B => {
            // DirectBitsRgn
            return read_pm_area(im, &b[p..], false, true);
         }
         0x8200 => {
            // CompressedQuickTime
            unimplemented!();
         }
         0x00FF => {
            // OpEndPic
            break;
         }
         // help i'm trapped in an awful metafile format from the 80s
         0x0000 |           // NoOp
         0x001C |           // HiliteMode
         0x001E |           // DefHilite
         0x0038 |           // FrameSameRect
         0x0039 |           // PaintSameRect
         0x003A |           // EraseSameRect
         0x003B |           // InvertSameRect
         0x003C |           // FillSameRect
         0x8000 |           // Reserved
         0x8100 => (),      // Reserved
         0x0003 |           // TxFont
         0x0004 |           // TxFace
         0x0005 |           // TxMode
         0x0008 |           // PnMode
         0x000D |           // TxSize
         0x0011 |           // VersionOp
         0x0015 |           // PnLocHFrac
         0x0016 |           // ChExtra
         0x0023 |           // ShortLineFrom
         0x00A0 => p += 2,  // ShortComment
         0x0006 |           // SpExtra
         0x0007 |           // PnSize
         0x000B |           // OvSize
         0x000C |           // Origin
         0x000E |           // FgCol
         0x000F |           // BkCol
         0x0021 => p += 4,  // LineFrom
         0x001A |           // RGBFgCol
         0x001B |           // RGBBkCol
         0x001D |           // TxRatio
         0x0022 => p += 6,  // ShortLine
         0x0002 |           // BkPat
         0x0009 |           // PnPat
         0x0010 |           // TxRatio
         0x0020 |           // Line
         0x002E |           // GlyphState
         0x0030 |           // FrameRect
         0x0031 |           // PaintRect
         0x0032 |           // EraseRect
         0x0033 |           // InvertRect
         0x0034 => p += 8,  // FillRect
         0x002D => p += 10, // LineJustify
         0x0001 => p += usize::from(u16b(&b[p..  ]) & !1),     // Clip
         0x00A1 => p += usize::from(u16b(&b[p+2..]) & !1) + 2, // LongComment
         0x100..=
         0x7FFF => p += usize::from(op >> 8) * 2, // Reserved
         _      => {
            bail!("invalid op in PICT");
         }
      }
   }

   Err(err_msg("no image in data"))
}

/// Read a `ColorTable` structure.
pub fn get_clut(b: &[u8]) -> ResultS<(Vec<Color8>, usize)>
{
   read_data! {
      endian: BIG, buf: b, size: 8, start: 0, data {
         let dev = u16[4];
         let num = u16[6] usize;
      }
   }

   let dev = dev & 0x8000 != 0;
   let num = num + 1;

   let mut p = 8;
   let mut clut = vec![Color8::new(0, 0, 0); num];

   for i in 0..num {
      read_data! {
         endian: BIG, buf: b, size: 8, start: p, data {
            let n = u16[0] usize;
            let r = u8[2];
            let g = u8[4];
            let b = u8[6];
         }
      }

      // with device mapping, we ignore the index entirely
      let n = if dev {i} else {n};

      *ok!(clut.get_mut(n), "invalid index")? = Color8::new(r, g, b);

      p += 8;
   }

   Ok((clut, p))
}

// Read run-length encoded data.
fn read_rle<T>(b: &[u8], pitch: usize) -> ResultS<(Vec<T>, usize)>
   where T: ReadRleData
{
   let mut p = 0;
   let mut o = Vec::with_capacity(pitch);

   let sz = if pitch > 250 {
      (usize::from(u16b(b)) + 2, p += 2).0
   } else {
      (usize::from(b[0]) + 1, p += 1).0
   };

   while p < sz {
      let szf = b[p];
      let cmp = szf & 0x80 != 0;
      let len = usize::from(if cmp {!szf + 2} else {szf + 1});

      p += 1;
      o.reserve(len);

      T::read_rle_data(b, &mut p, cmp, len, &mut o);
   }

   if o.len() == pitch {
      Ok((o, p))
   } else {
      Err(err_msg("incorrect size for compressed scanline"))
   }
}

trait ReadRleData: Sized
{
   // Read a sequence of packed RLE data.
   fn read_rle_data(b: &[u8],
                    p: &mut usize,
                    cmp: bool,
                    len: usize,
                    out: &mut Vec<Self>);
}

impl ReadRleData for u16
{
   fn read_rle_data(b: &[u8],
                    p: &mut usize,
                    cmp: bool,
                    len: usize,
                    out: &mut Vec<Self>)
   {
      if cmp {
         let d = u16b(&b[*p..*p + 2]);
         *p += 2;
         for _ in 0..len {
            out.push(d);
         }
      } else {
         for _ in 0..len {
            let d = u16b(&b[*p..*p + 2]);
            *p += 2;
            out.push(d);
         }
      }
   }
}

impl ReadRleData for u8
{
   fn read_rle_data(b: &[u8],
                    p: &mut usize,
                    cmp: bool,
                    len: usize,
                    out: &mut Vec<Self>)
   {
      if cmp {
         let d = b[*p];
         *p += 1;
         for _ in 0..len {
            out.push(d);
         }
      } else {
         for _ in 0..len {
            let d = b[*p];
            *p += 1;
            out.push(d);
         }
      }
   }
}

// Expand packed pixel data based on bit depth.
fn expand_data(b: Vec<u8>, depth: Depth) -> ResultS<Vec<u8>>
{
   let mut o = Vec::with_capacity(match depth {
                                     Depth::_4 => b.len() * 2,
                                     Depth::_2 => b.len() * 4,
                                     Depth::_1 => b.len() * 8,
                                     _ => bail!("invalid bit depth"),
                                  });

   for ch in b {
      match depth {
         Depth::_4 => {
            for i in (0..=1).rev() {
               o.push(ch >> (i * 4) & 0xF_u8);
            }
         }
         Depth::_2 => {
            for i in (0..=3).rev() {
               o.push(ch >> (i * 2) & 0x3_u8);
            }
         }
         Depth::_1 => {
            for i in (0..=7).rev() {
               o.push(ch >> i & 0x1_u8);
            }
         }
         _ => bail!("invalid bit depth"),
      }
   }

   Ok(o)
}

struct Header
{
   pitch:  usize,
   pack_t: PackType,
   depth:  Depth,
   clut:   Option<Vec<Color8>>,
   rle:    bool,
}

c_enum! {
   enum Depth: u16
   {
      _1  = 1,
      _2  = 2,
      _4  = 4,
      _8  = 8,
      _16 = 16,
      _32 = 32,
   }
}

c_enum! {
   enum PackType: u16
   {
      Default = 0,
      None    = 1,
      NoPad   = 2,
      Rle16   = 3,
      Rle32   = 4,
   }
}

// EOF
