use std::f32::NAN;

use crate::decoders::*;
use crate::decoders::tiff::*;
use crate::decoders::basics::*;

#[derive(Debug, Clone)]
pub struct Rw2Decoder<'a> {
  buffer: &'a [u8],
  rawloader: &'a RawLoader,
  tiff: TiffIFD<'a>,
}

impl<'a> Rw2Decoder<'a> {
  pub fn new(buf: &'a [u8], tiff: TiffIFD<'a>, rawloader: &'a RawLoader) -> Rw2Decoder<'a> {
    Rw2Decoder {
      buffer: buf,
      tiff: tiff,
      rawloader: rawloader,
    }
  }
}

impl<'a> Decoder for Rw2Decoder<'a> {
  fn image(&self, dummy: bool) -> Result<RawImage,String> {
    let width: usize;
    let height: usize;
    let image = {
      let data = self.tiff.find_ifds_with_tag(Tag::PanaOffsets);
      if data.len() > 0 {
        let raw = data[0];
        width = fetch_tag!(raw, Tag::PanaWidth).get_usize(0);
        height = fetch_tag!(raw, Tag::PanaLength).get_usize(0);
        let offset = fetch_tag!(raw, Tag::PanaOffsets).get_usize(0);
        let src = &self.buffer[offset..];
        Rw2Decoder::decode_panasonic(src, width, height, true, dummy)
      } else {
        let raw = fetch_ifd!(&self.tiff, Tag::StripOffsets);
        width = fetch_tag!(raw, Tag::PanaWidth).get_usize(0);
        height = fetch_tag!(raw, Tag::PanaLength).get_usize(0);
        let offset = fetch_tag!(raw, Tag::StripOffsets).get_usize(0);
        let src = &self.buffer[offset..];

        if src.len() >= width*height*2 {
          decode_12le_unpacked_left_aligned(src, width, height, dummy)
        } else if src.len() >= width*height*3/2 {
          decode_12le_wcontrol(src, width, height, dummy)
        } else {
          Rw2Decoder::decode_panasonic(src, width, height, false, dummy)
        }
      }
    };

    let mode = {
      let ratio = width*100/height;
      if ratio < 125 {
        "1:1"
      } else if ratio < 145 {
        "4:3"
      } else if ratio < 165 {
        "3:2"
      } else {
        "16:9"
      }
    };
    let camera = self.rawloader.check_supported_with_mode(&self.tiff, mode)?;

    ok_image(camera, width, height, self.get_wb()?, image)
  }
}

impl<'a> Rw2Decoder<'a> {
  fn get_wb(&self) -> Result<[f32;4], String> {
    if self.tiff.has_entry(Tag::PanaWBsR) && self.tiff.has_entry(Tag::PanaWBsB) {
      let r = fetch_tag!(self.tiff, Tag::PanaWBsR).get_u32(0) as f32;
      let b = fetch_tag!(self.tiff, Tag::PanaWBsB).get_u32(0) as f32;
      Ok([r, 256.0, b, NAN])
    } else if self.tiff.has_entry(Tag::PanaWBs2R)
           && self.tiff.has_entry(Tag::PanaWBs2G)
           && self.tiff.has_entry(Tag::PanaWBs2B) {
      let r = fetch_tag!(self.tiff, Tag::PanaWBs2R).get_u32(0) as f32;
      let g = fetch_tag!(self.tiff, Tag::PanaWBs2G).get_u32(0) as f32;
      let b = fetch_tag!(self.tiff, Tag::PanaWBs2B).get_u32(0) as f32;
      Ok([r, g, b, NAN])
    } else {
      Err("Couldn't find WB".to_string())
    }
  }

  pub(crate) fn decode_panasonic(buf: &[u8], width: usize, height: usize, split: bool, dummy: bool) -> Vec<u16> {
    decode_threaded_multiline(width, height, 5, dummy, &(|out: &mut [u16], row| {
      let skip = ((width * row * 9) + (width/14 * 2 * row)) / 8;
      let blocks = skip / 0x4000;
      let src = &buf[blocks*0x4000..];
      let mut pump = BitPumpPanasonic::new(src, split);
      for _ in 0..(skip % 0x4000) {
        pump.get_bits(8);
      }

      let mut sh: i32 = 0;
      for out in out.chunks_exact_mut(14) {
        let mut pred: [i32;2] = [0,0];
        let mut nonz: [i32;2] = [0,0];

        for i in 0..14 {
          if (i % 3) == 2 {
            sh = 4 >> (3 - pump.get_bits(2));
          }
          if nonz[i & 1] != 0 {
            let j = pump.get_bits(8) as i32;
            if j != 0 {
              pred[i & 1] -= 0x80 << sh;
              if pred[i & 1] < 0 || sh == 4 {
                pred[i & 1] &= !(-1 << sh);
              }
              pred[i & 1] += j << sh;
            }
          } else {
            nonz[i & 1] = pump.get_bits(8) as i32;
            if nonz[i & 1] != 0 || i > 11 {
              pred[i & 1] = nonz[i & 1] << 4 | (pump.get_bits(4) as i32);
            }
          }
          out[i] = pred[i & 1] as u16;
        }
      }
    }))
  }
}

pub struct BitPumpPanasonic<'a> {
  buffer: &'a [u8],
  pos: usize,
  nbits: u32,
  split: bool,
}

impl<'a> BitPumpPanasonic<'a> {
  pub fn new(src: &'a [u8], split: bool) -> BitPumpPanasonic {
    BitPumpPanasonic {
      buffer: src,
      pos: 0,
      nbits: 0,
      split: split,
    }
  }
}

impl<'a> BitPump for BitPumpPanasonic<'a> {
  fn peek_bits(&mut self, num: u32) -> u32 {
    if num > self.nbits {
      self.nbits += 0x4000 * 8;
      self.pos += 0x4000;
    }
    let mut byte = (self.nbits-num) >> 3 ^ 0x3ff0;
    if self.split {
      byte = (byte + 0x4000 - 0x2008) % 0x4000;
    }
    let bits = LEu16(self.buffer, byte as usize + self.pos - 0x4000) as u32;
    (bits >> ((self.nbits-num) & 7)) & (0x0ffffffffu32 >> (32-num))
  }

  fn consume_bits(&mut self, num: u32) {
    self.nbits -= num;
  }
}
