use std::{fs::File, io::{self, BufWriter, Cursor, Read, Seek, Write}, path::PathBuf};

use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};

use crate::common::{CommonHeader, CzError, CzHeader, CzImage};

#[derive(Debug, Clone, Copy)]
pub struct Cz0Header {
    /// Common CZ# header
    pub common: CommonHeader,

    /// Unknown bytes
    unknown_1: [u8; 5],

    /// Width of cropped image area
    pub crop_width: u16,

    /// Height of cropped image area
    pub crop_height: u16,

    /// Bounding box width
    pub bounds_width: u16,

    /// Bounding box height
    pub bounds_height: u16,

    /// Offset width
    pub offset_width: Option<u16>,

    /// Offset height
    pub offset_height: Option<u16>,

    unknown_2: Option<[u8; 4]>,
}

#[derive(Debug)]
pub struct Cz0Image {
    header: Cz0Header,
    bitmap: Vec<u8>,
}

impl CzHeader for Cz0Header {
    fn new<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError>
    where
        Self: Sized,
    {
        let common = CommonHeader::new(bytes)?;

        if common.version() != 0 {
            return Err(CzError::VersionMismatch);
        }

        let mut unknown_1 = [0u8; 5];
        bytes.read_exact(&mut unknown_1)?;

        let crop_width = bytes.read_u16::<LittleEndian>()?;
        let crop_height = bytes.read_u16::<LittleEndian>()?;

        let bounds_width = bytes.read_u16::<LittleEndian>()?;
        let bounds_height = bytes.read_u16::<LittleEndian>()?;

        let mut offset_width = None;
        let mut offset_height = None;
        let mut unknown_2 = None;
        if common.length() > 28 {
            offset_width = Some(bytes.read_u16::<LittleEndian>()?);
            offset_height = Some(bytes.read_u16::<LittleEndian>()?);

            let mut un_2 = [0u8; 4];
            bytes.read_exact(&mut un_2)?;

            unknown_2 = Some(un_2);
        }

        Ok(Self {
            common,

            unknown_1,

            crop_width,
            crop_height,

            bounds_width,
            bounds_height,

            offset_width,
            offset_height,

            unknown_2,
        })
    }

    fn version(&self) -> u8 {
        self.common.version()
    }

    fn length(&self) -> usize {
        self.common.length()
    }

    fn width(&self) -> u16 {
        self.common.width()
    }

    fn height(&self) -> u16 {
        self.common.height()
    }

    fn depth(&self) -> u16 {
        self.common.depth()
    }

    fn color_block(&self) -> u8 {
        self.common.color_block()
    }

    fn to_bytes(&self) -> Result<Vec<u8>, io::Error> {
        let mut buf = vec![];

        buf.write_all(&self.common.to_bytes()?)?;
        buf.write_all(&self.unknown_1)?;
        buf.write_u16::<LittleEndian>(self.crop_width)?;
        buf.write_u16::<LittleEndian>(self.crop_height)?;
        buf.write_u16::<LittleEndian>(self.bounds_width)?;
        buf.write_u16::<LittleEndian>(self.bounds_height)?;

        if self.length() > 28 {
            buf.write_u16::<LittleEndian>(self.offset_width.unwrap())?;
            buf.write_u16::<LittleEndian>(self.offset_height.unwrap())?;
            buf.write_all(&self.unknown_2.unwrap())?;
        }

        Ok(buf)
    }
}

impl CzImage for Cz0Image {
    type Header = Cz0Header;

    fn decode<T: Seek + ReadBytesExt + Read>(bytes: &mut T) -> Result<Self, CzError> {
        // Get the header from the input
        let header = Cz0Header::new(bytes)?;
        bytes.seek(io::SeekFrom::Start(header.length() as u64))?;

        // Get the rest of the file, which is the bitmap
        let mut bitmap = vec![];
        bytes.read_to_end(&mut bitmap)?;

        Ok(Self { header, bitmap })
    }

    fn save_as_png(&self, name: &str) -> Result<(), image::error::ImageError> {
        image::save_buffer(
            name,
            &self.bitmap,
            self.header.width() as u32,
            self.header.height() as u32,
            image::ExtendedColorType::Rgba8,
        )
    }

    fn save_as_cz<T: Into<PathBuf>>(&self, path: T) -> Result<(), CzError> {
        let mut output_file = BufWriter::new(File::create(path.into())?);

        output_file.write_all(&self.header().to_bytes()?)?;
        output_file.write_all(&self.bitmap)?;
        output_file.flush()?;

        Ok(())
    }

    fn header(&self) -> &Self::Header {
        &self.header
    }

    fn set_header(&mut self, header: Self::Header) {
        self.header = header
    }

    fn into_bitmap(self) -> Vec<u8> {
        self.bitmap
    }

    fn set_bitmap(&mut self, bitmap: &[u8], header: &Self::Header) {
        self.bitmap = bitmap.to_vec();

        self.header = *header;
    }
}

impl TryFrom<&[u8]> for Cz0Image {
    type Error = CzError;

    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        let mut input = Cursor::new(value);

        // Get the header from the input
        let header = Cz0Header::new(&mut input)?;

        // Get the rest of the file, which is the bitmap
        let mut bitmap = vec![];
        input.read_to_end(&mut bitmap)?;

        Ok(Self { header, bitmap })
    }
}