#![allow(dead_code)]

use bin_rs::reader::BinaryReader;
use miniz_oxide::inflate::decompress_to_vec_zlib;

#[derive(Debug, Clone)]
pub struct WOFFHeader {
    pub(crate) sfnt_version: u32,
    pub(crate) signature: u32,
    pub(crate) flavor: u32,
    pub(crate) length: u32,
    pub(crate) num_tables: u16,
    pub(crate) reserved: u16,
    pub(crate) total_sfnt_size: u32,
    pub(crate) major_version: u16,
    pub(crate) minor_version: u16,
    pub(crate) meta_offset: u32,
    pub(crate) meta_length: u32,
    pub(crate) meta_orig_length: u32,
    pub(crate) priv_offset: u32,
    pub(crate) priv_length: u32,
}

impl WOFFHeader {
    pub(crate) fn new<R: BinaryReader>(reader: &mut R) -> Result<Self, std::io::Error> {
        let mut header = Self {
            sfnt_version: 0,
            signature: 0,
            flavor: 0,
            length: 0,
            num_tables: 0,
            reserved: 0,
            total_sfnt_size: 0,
            major_version: 0,
            minor_version: 0,
            meta_offset: 0,
            meta_length: 0,
            meta_orig_length: 0,
            priv_offset: 0,
            priv_length: 0,
        };
        header.signature = reader.read_u32()?;
        header.flavor = reader.read_u32()?;
        header.length = reader.read_u32()?;
        header.num_tables = reader.read_u16()?;
        header.reserved = reader.read_u16()?;
        header.total_sfnt_size = reader.read_u32()?;
        header.major_version = reader.read_u16()?;
        header.minor_version = reader.read_u16()?;
        header.meta_offset = reader.read_u32()?;
        header.meta_length = reader.read_u32()?;
        header.meta_orig_length = reader.read_u32()?;
        header.priv_offset = reader.read_u32()?;
        header.priv_length = reader.read_u32()?;
        Ok(header)
    }
}

#[derive(Debug, Clone)]
pub(crate) struct WOFFTableRecord {
    pub(crate) tag: u32,
    pub(crate) offset: u32,
    pub(crate) comp_length: u32,
    pub(crate) orig_length: u32,
    pub(crate) orig_checksum: u32,
}

impl WOFFTableRecord {
    pub(crate) fn new() -> WOFFTableRecord {
        WOFFTableRecord {
            tag: 0,
            offset: 0,
            comp_length: 0,
            orig_length: 0,
            orig_checksum: 0,
        }
    }
}

#[derive(Debug, Clone)]
pub(crate) struct WOFFTable {
    pub(crate) tag: u32,
    pub(crate) data: Vec<u8>,
}
impl WOFFTable {
    pub(crate) fn new() -> WOFFTable {
        WOFFTable {
            tag: 0,
            data: Vec::new(),
        }
    }
}

#[derive(Debug, Clone)]
pub(crate) struct WOFF {
    pub(crate) header: WOFFHeader,
    pub(crate) table_records: Vec<WOFFTableRecord>,
    pub(crate) metadata: Box<String>,
    pub(crate) private_data: Box<Vec<u8>>,
    pub(crate) tables: Vec<WOFFTable>,
}

impl WOFF {
    pub(crate) fn from<B: BinaryReader>(
        reader: &mut B,
        header: WOFFHeader,
    ) -> Result<Self, std::io::Error> {
        let mut table_records = Vec::new();
        for _ in 0..header.num_tables {
            let mut table_record = WOFFTableRecord::new();
            table_record.tag = reader.read_u32()?;
            table_record.offset = reader.read_u32()?;
            table_record.comp_length = reader.read_u32()?;
            table_record.orig_length = reader.read_u32()?;
            table_record.orig_checksum = reader.read_u32()?;
            #[cfg(debug_assertions)]
            {
                let _ = crate::util::u32_to_string(table_record.tag);
            }
            table_records.push(table_record);
        }
        // read metadata
        reader.seek(std::io::SeekFrom::Start(header.meta_offset as u64))?;
        let metadata = if header.meta_length > 0 {
            let compress_metadata = reader.read_bytes_as_vec(header.meta_length as usize)?;
            let metadata_bytes = decompress_to_vec_zlib(&compress_metadata);
            if metadata_bytes.is_err() {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "Failed to decompress metadata",
                ));
            }
            let metadata_bytes = metadata_bytes.unwrap();
            String::from_utf8(metadata_bytes).unwrap()
        } else {
            "".to_string()
        };
        // read private data

        reader.seek(std::io::SeekFrom::Start(header.priv_offset as u64))?;
        let private_data = reader.read_bytes_as_vec(header.priv_length as usize)?;

        // read table data
        let mut tables = Vec::new();
        for table_record in table_records.iter() {
            reader.seek(std::io::SeekFrom::Start(table_record.offset as u64))?;
            let mut table = WOFFTable::new();
            table.tag = table_record.tag;
            let mut table_data = reader.read_bytes_as_vec(table_record.comp_length as usize)?;
            if table_record.comp_length != table_record.orig_length {
                let decompress = decompress_to_vec_zlib(&table_data);
                if decompress.is_err() {
                    return Err(std::io::Error::new(
                        std::io::ErrorKind::InvalidData,
                        "Failed to decompress table data",
                    ));
                }
                table_data = decompress.unwrap();
            }
            table.data = table_data;
            tables.push(table);
        }

        Ok(WOFF {
            header,
            table_records,
            metadata: Box::new(metadata),
            private_data: Box::new(private_data),
            tables,
        })
    }

    pub fn get_metadata(&self) -> &str {
        &self.metadata
    }

    pub fn get_private_data(&self) -> &[u8] {
        &self.private_data
    }
}
