use std::io::{Cursor, Write};

use androscalpel_serializer::{ReadSeek, Result, Serializable};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExtraField {
    Zip64(Zip64ExtraField),
    Generic(GenericExtraField),
}

impl ExtraField {
    fn to_generic(&self) -> Result<GenericExtraField> {
        match self {
            Self::Zip64(field) => field.to_generic_field(),
            Self::Generic(field) => Ok(field.clone()),
        }
    }
}

impl Serializable for ExtraField {
    fn serialize(&self, output: &mut dyn Write) -> Result<()> {
        self.to_generic()?.serialize(output)
    }

    fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
        Ok(Self::Generic(GenericExtraField::deserialize(input)?))

        /*
        match field.id {
            Zip64ExtraField::ID => Ok(Self::Zip64(Zip64ExtraField::from_generic(&field)?)),
            _ => Ok(Self::Generic(field)),
        }
        */
    }

    fn size(&self) -> usize {
        self.to_generic().unwrap().size()
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Zip64ExtraField {
    pub original_size: Option<u64>,
    pub compressed_size: Option<u64>,
    pub offset_header: Option<u64>,
    pub disk_number: Option<u32>,
}

impl Zip64ExtraField {
    pub const ID: u16 = 0x0001;
    fn to_generic_field(&self) -> Result<GenericExtraField> {
        let mut data = Cursor::new(Vec::<u8>::new());
        if let Some(original_size) = self.original_size {
            original_size.serialize(&mut data)?;
        }
        if let Some(compressed_size) = self.compressed_size {
            compressed_size.serialize(&mut data)?;
        }
        if let Some(offset_header) = self.offset_header {
            offset_header.serialize(&mut data)?;
        }
        if let Some(disk_number) = self.disk_number {
            disk_number.serialize(&mut data)?;
        }

        Ok(GenericExtraField {
            id: Self::ID,
            data: data.into_inner(),
        })
    }

    pub fn from_generic(
        field: &GenericExtraField,
        original_size: bool,
        compressed_size: bool,
        offset_header: bool,
        disk_number: bool,
    ) -> Result<Self> {
        assert_eq!(field.id, Self::ID);
        let mut data = Cursor::new(&field.data);
        let original_size = if original_size {
            Some(u64::deserialize(&mut data)?)
        } else {
            None
        };
        let compressed_size = if compressed_size {
            Some(u64::deserialize(&mut data)?)
        } else {
            None
        };
        let offset_header = if offset_header {
            Some(u64::deserialize(&mut data)?)
        } else {
            None
        };
        let disk_number = if disk_number {
            Some(u32::deserialize(&mut data)?)
        } else {
            None
        };
        Ok(Self {
            original_size,
            compressed_size,
            offset_header,
            disk_number,
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GenericExtraField {
    pub id: u16,
    pub data: Vec<u8>,
}

impl Serializable for GenericExtraField {
    fn serialize(&self, output: &mut dyn Write) -> Result<()> {
        self.id.serialize(output)?;
        (self.data.len() as u16).serialize(output)?;
        for c in &self.data {
            c.serialize(output)?;
        }
        Ok(())
    }

    fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
        let id = u16::deserialize(input)?;
        let data_size = u16::deserialize(input)?;
        let mut data = vec![];
        for _ in 0..data_size {
            data.push(u8::deserialize(input)?);
        }
        Ok(Self { id, data })
    }

    fn size(&self) -> usize {
        4 + self.data.len()
    }
}
