use super::super::{args::ColorType, types::{HeaderEncoder, Rect}};
use std::io::Write;
use tool_helper::{bits::{Bit, BitRange}, raw::RawConversion, Error};


pub struct Header {
    flag:        u32,
    clut_block:  DataBlock,
    pixel_block: DataBlock,
}

impl Header {
    const ID_VALUE:        u32 = BitRange::from_to(0, 7).as_value(0x10) as u32;
    const ID_VERSION_VALUE:u32 = BitRange::from_to(8, 15).as_value(0x0) as u32;

    const FLAG_PMODE_BIT_RANGE: BitRange = BitRange::from_to(0, 2);
    const FLAG_CF_BIT:          Bit      = Bit::at(3);

    const ID:u32 = Self::ID_VALUE | Self::ID_VERSION_VALUE;
}

impl Default for Header {
    fn default() -> Self {
        Header{flag: 0, clut_block: DataBlock::default(), pixel_block: DataBlock::default()}
    }
}

impl HeaderEncoder for Header {
    fn encode_settings(&mut self, color_type: ColorType, tex_rect: Rect, clut_rect: Rect) -> Result<(), Error> {
        self.flag = match color_type {
            ColorType::Clut4  => (Self::FLAG_PMODE_BIT_RANGE.as_value(0x0) | Self::FLAG_CF_BIT.as_value(true))  as u32,
            ColorType::Clut8  => (Self::FLAG_PMODE_BIT_RANGE.as_value(0x1) | Self::FLAG_CF_BIT.as_value(true))  as u32,
            ColorType::Full16 => (Self::FLAG_PMODE_BIT_RANGE.as_value(0x2) | Self::FLAG_CF_BIT.as_value(false)) as u32,
        };

        self.clut_block  = DataBlock::new(clut_rect);
        self.pixel_block = DataBlock::new(tex_rect);
        Ok(())
    }

    fn write_header(&self, output: &mut dyn Write) -> Result<usize, Error> {
        let bytes = output.write(&Header::ID.to_le_bytes())?;
        Ok(bytes + output.write(&self.flag.to_le_bytes())?)
    }

    fn write_clut_header(&self, output: &mut dyn Write) -> Result<usize, Error> {
        if self.clut_block.w > 0 {
            Ok(output.write(&self.clut_block.convert_to_raw())?)
        }
        
        else {
            Ok(0)
        }
    }

    fn write_pixel_header(&self, output: &mut dyn Write) -> Result<usize, Error> {
        Ok(output.write(&self.pixel_block.convert_to_raw())?)
    }
}

pub struct DataBlock {
    bytes: u32,
    x:     u16,
    y:     u16,
    w:     u16,
    h:     u16,
}

impl DataBlock {
    const RAW_HEADER_SIZE: usize = (4*std::mem::size_of::<u16>()) + std::mem::size_of::<u32>();

    pub fn new(rect: Rect) -> DataBlock {
        let x = rect.x;
        let y = rect.y;
        let w = rect.width;
        let h = rect.height;

        let bytes = ((w as usize*h as usize*std::mem::size_of::<u16>()) + Self::RAW_HEADER_SIZE) as u32;
        DataBlock{bytes, x, y, w, h}
    }
}

impl std::default::Default for DataBlock {
    fn default() -> Self {
        DataBlock{bytes: 0, x: 0, y: 0, w: 0, h: 0}
    }
}

impl RawConversion<{Self::RAW_HEADER_SIZE}> for DataBlock {
    fn convert_to_raw(&self) -> [u8; Self::RAW_HEADER_SIZE] {
        let mut raw = [0u8; Self::RAW_HEADER_SIZE];

        raw[ 0..4].copy_from_slice(&self.bytes.to_le_bytes());
        raw[ 4..6].copy_from_slice(&self.x.to_le_bytes());
        raw[ 6..8].copy_from_slice(&self.y.to_le_bytes());
        raw[ 8..10].copy_from_slice(&self.w.to_le_bytes());
        raw[10..12].copy_from_slice(&self.h.to_le_bytes());
        raw
    }
}