use std::io::{Seek, SeekFrom, Write};

use binrw::{BinRead, BinResult, BinWrite, BinWriterExt, Endian, FilePtr64};

use crate::{array_of::ArrayOf, layer::Layer, pen::PenHeader, types::Rgba};

const FILE_MAGIC: &[u8; 16] = b"E\0Z\0C\0A\0D\0X\06\04\0";

#[derive(BinRead, Debug)]
#[br(magic = b"E\0Z\0C\0A\0D\0X\06\04\0")]
pub struct EzCadHeader {
    _unknown_1: [u8; 0x150],
    pub thumbnail_offset: FilePtr64<Thumbnail>,
    pub pens_offset: FilePtr64<PenHeader>,
    _unknown_offset: FilePtr64<Unknown>,
    pub layers_offset: FilePtr64<ArrayOf<Layer>>,
    _unknown_2: [u8; 0x1A8],
}

// Manually implement BinWrite as FilePtr does not support serialization
// See: https://github.com/jam1garner/binrw/issues/4
impl BinWrite for EzCadHeader {
    type Args<'a> = ();

    fn write_options<W: Write + Seek>(
        &self,
        writer: &mut W,
        endian: Endian,
        args: Self::Args<'_>,
    ) -> BinResult<()> {
        FILE_MAGIC.write_options(writer, endian, args)?;
        self._unknown_1.write_options(writer, endian, args)?;

        // Save offset addresses and write placeholder (zeros) for now
        let thumbnail_save_offset: u64 = writer.stream_position().unwrap();
        0u64.write_options(writer, endian, args)?;
        let pens_save_offset: u64 = writer.stream_position().unwrap();
        0u64.write_options(writer, endian, args)?;
        let unknown_save_offset: u64 = writer.stream_position().unwrap();
        0u64.write_options(writer, endian, args)?;
        let layers_save_offset: u64 = writer.stream_position().unwrap();
        0u64.write_options(writer, endian, args)?;

        self._unknown_2.write_options(writer, endian, args)?;

        // Helper fn to write current address to given offset without modifying cursor position
        let write_offset = |writer: &mut W, offset: u64| -> BinResult<()> {
            let current_offset: u64 = writer.stream_position().unwrap();
            writer.seek(SeekFrom::Start(offset)).unwrap();
            writer.write_type(&current_offset, endian)?;
            writer.seek(SeekFrom::Start(current_offset)).unwrap();
            Ok(())
        };

        // Write thumbnail contents
        write_offset(writer, thumbnail_save_offset)?;
        self.thumbnail_offset.write_options(writer, endian, args)?;

        // Write unknown contents
        write_offset(writer, unknown_save_offset)?;
        self._unknown_offset.write_options(writer, endian, args)?;

        // Write pens
        write_offset(writer, pens_save_offset)?;
        self.pens_offset.write_options(writer, endian, args)?;

        // Write layers
        write_offset(writer, layers_save_offset)?;
        self.layers_offset.write_options(writer, endian, args)
    }
}

#[derive(BinRead, BinWrite, Debug)]
pub struct Thumbnail {
    _zeros_1: u32,
    pub dimension_x: u32,
    pub dimension_y: u32,
    _unknown_1: u32, // 0x320?
    _unknown_2: u32, // 0x200001?
    _zeros_2: [u32; 3],

    #[br(count = dimension_x * dimension_y)]
    pub pixels: Vec<Rgba>, // Takes a few ms longer than Vec<u32>
}

#[derive(BinRead, BinWrite, Debug)]
pub struct Unknown {
    _zeros: u32,
}
