use std::{
    collections::HashSet,
    convert::TryInto,
    default,
    hash::Hash,
    io::{Cursor, Read, Write},
    iter::FromIterator,
    mem::size_of,
};

use boytacean_common::error::Error;
use boytacean_hashing::crc32c::crc32c;

use crate::{
    huffman::{decode_huffman, encode_huffman},
    rle::{decode_rle, encode_rle},
};

pub const ZIPPY_MAGIC: &str = "ZIPY";

pub const ZIPPY_MAGIC_UINT: u32 = 0x5a495059;

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum ZippyFeatures {
    Crc32,
    Encrypted,
    Other,
}

impl From<&ZippyFeatures> for &str {
    fn from(value: &ZippyFeatures) -> Self {
        match value {
            ZippyFeatures::Crc32 => "crc32",
            ZippyFeatures::Encrypted => "encrypted",
            ZippyFeatures::Other => "other",
        }
    }
}

impl From<u32> for ZippyFeatures {
    fn from(value: u32) -> Self {
        match value {
            0 => Self::Crc32,
            1 => Self::Encrypted,
            _ => Self::Other,
        }
    }
}

impl From<&str> for ZippyFeatures {
    fn from(value: &str) -> Self {
        match value {
            "crc32" => Self::Crc32,
            "encrypted" => Self::Encrypted,
            _ => Self::Other,
        }
    }
}

#[derive(Default)]
pub struct Zippy {
    name: String,
    description: String,
    features: HashSet<ZippyFeatures>,
    crc32: u32,
    data: Vec<u8>,
}

pub struct ZippyOptions {
    crc32: bool,
}

impl ZippyOptions {
    pub fn new(crc32: bool) -> Self {
        Self { crc32 }
    }
}

impl default::Default for ZippyOptions {
    fn default() -> Self {
        Self { crc32: true }
    }
}

impl Zippy {
    pub fn build(
        data: &[u8],
        name: String,
        description: String,
        features: Option<Vec<ZippyFeatures>>,
        options: Option<ZippyOptions>,
    ) -> Result<Self, Error> {
        let features = features.unwrap_or(vec![ZippyFeatures::Crc32]);
        let options = options.unwrap_or_default();
        Ok(Self {
            name,
            description,
            features: HashSet::from_iter(features.iter().cloned()),
            crc32: if options.crc32 {
                crc32c(data)
            } else {
                0xffffffff
            },
            data: data.to_vec(),
        })
    }

    pub fn is_zippy(data: &[u8]) -> Result<bool, Error> {
        let mut data = Cursor::new(data);

        let mut buffer = [0x00; size_of::<u32>()];
        data.read_exact(&mut buffer)?;
        let magic = u32::from_le_bytes(buffer);

        Ok(magic == ZIPPY_MAGIC_UINT)
    }

    pub fn decode(data: &[u8], _options: Option<ZippyOptions>) -> Result<Zippy, Error> {
        let mut data = Cursor::new(data);

        let magic = Self::read_u32(&mut data)?;
        if magic != ZIPPY_MAGIC_UINT {
            return Err(Error::InvalidData);
        }

        let name = Self::read_string(&mut data)?;
        let description = Self::read_string(&mut data)?;

        let mut instance = Self {
            name,
            description,
            features: HashSet::new(),
            crc32: 0xffffffff,
            data: vec![],
        };

        instance.read_features(&mut data)?;

        let buffer = Self::read_payload(&mut data)?;
        let decoded = decode_rle(&decode_huffman(&buffer)?);
        instance.data = decoded;

        Ok(instance)
    }

    pub fn encode(&self) -> Result<Vec<u8>, Error> {
        let mut buffer = Cursor::new(vec![]);
        let encoded = encode_huffman(&encode_rle(&self.data))?;

        Self::write_u32(&mut buffer, ZIPPY_MAGIC_UINT)?;

        Self::write_string(&mut buffer, &self.name)?;
        Self::write_string(&mut buffer, &self.description)?;

        self.write_features(&mut buffer)?;

        Self::write_payload(&mut buffer, &encoded)?;

        Ok(buffer.into_inner())
    }

    pub fn check_crc32(&self) -> bool {
        self.crc32 == crc32c(&self.data)
    }

    pub fn crc32(&self) -> u32 {
        self.crc32
    }

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

    pub fn has_feature(&self, feature: ZippyFeatures) -> bool {
        self.features.contains(&feature)
    }

    #[inline(always)]
    fn read_u32(data: &mut Cursor<&[u8]>) -> Result<u32, Error> {
        let mut buffer = [0x00; size_of::<u32>()];
        data.read_exact(&mut buffer)?;
        Ok(u32::from_le_bytes(buffer))
    }

    #[inline(always)]
    fn read_string(data: &mut Cursor<&[u8]>) -> Result<String, Error> {
        let length = Self::read_u32(data)?;
        let mut buffer = vec![0; length as usize];
        data.read_exact(&mut buffer)?;
        Ok(String::from_utf8(buffer)?)
    }

    #[inline(always)]
    fn read_payload(data: &mut Cursor<&[u8]>) -> Result<Vec<u8>, Error> {
        let size = Self::read_u32(data)?;
        let mut payload = vec![0; size as usize];
        data.read_exact(&mut payload)?;
        Ok(payload)
    }

    #[inline(always)]
    fn read_features(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> {
        let num_features = Self::read_u32(data)?;
        for _ in 0..num_features {
            let feature_str = Self::read_string(data)?;
            let feature = ZippyFeatures::from(feature_str.as_str());
            match feature {
                ZippyFeatures::Crc32 => self.read_crc32_feature(data)?,
                _ => self.read_empty_feature(data)?,
            };
            self.features.insert(feature);
        }
        Ok(())
    }

    #[inline(always)]
    fn read_crc32_feature(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> {
        let payload = Self::read_payload(data)?;
        if payload.len() != size_of::<u32>() {
            return Err(Error::InvalidData);
        }
        let payload: [u8; 4] = payload.try_into().unwrap();
        self.crc32 = u32::from_le_bytes(payload);
        Ok(())
    }

    #[inline(always)]
    fn read_empty_feature(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> {
        Self::read_payload(data)?;
        Ok(())
    }

    #[inline(always)]
    fn write_u32(data: &mut Cursor<Vec<u8>>, value: u32) -> Result<(), Error> {
        data.write_all(&value.to_le_bytes())?;
        Ok(())
    }

    #[inline(always)]
    fn write_string(data: &mut Cursor<Vec<u8>>, value: &str) -> Result<(), Error> {
        Self::write_u32(data, value.len() as u32)?;
        data.write_all(value.as_bytes())?;
        Ok(())
    }

    #[inline(always)]
    fn write_payload(data: &mut Cursor<Vec<u8>>, value: &[u8]) -> Result<(), Error> {
        Self::write_u32(data, value.len() as u32)?;
        data.write_all(value)?;
        Ok(())
    }

    #[inline(always)]
    fn write_features(&self, data: &mut Cursor<Vec<u8>>) -> Result<(), Error> {
        Self::write_u32(data, self.features.len() as u32)?;
        for feature in &self.features {
            match feature {
                ZippyFeatures::Crc32 => self.write_crc32_feature(data)?,
                _ => self.write_empty_feature(data, feature.into())?,
            }
        }
        Ok(())
    }

    #[inline(always)]
    fn write_crc32_feature(&self, data: &mut Cursor<Vec<u8>>) -> Result<(), Error> {
        Self::write_string(data, "crc32")?;
        Self::write_u32(data, size_of::<u32>() as u32)?;
        Self::write_u32(data, self.crc32)?;
        Ok(())
    }

    #[inline(always)]
    fn write_empty_feature(&self, data: &mut Cursor<Vec<u8>>, name: &str) -> Result<(), Error> {
        Self::write_string(data, name)?;
        Self::write_u32(data, 0)?;
        Ok(())
    }
}

pub fn encode_zippy(data: &[u8], options: Option<ZippyOptions>) -> Result<Vec<u8>, Error> {
    Zippy::build(data, String::from(""), String::from(""), None, options)?.encode()
}

pub fn decode_zippy(data: &[u8], options: Option<ZippyOptions>) -> Result<Vec<u8>, Error> {
    Ok(Zippy::decode(data, options)?.data().to_vec())
}

#[cfg(test)]
mod tests {
    use boytacean_common::error::Error;

    use super::{decode_zippy, Zippy, ZippyFeatures, ZippyOptions};

    #[test]
    fn test_build_and_encode() {
        let data = vec![1, 2, 3, 4, 5];
        let name = String::from("Test");
        let description = String::from("Test description");

        let zippy = Zippy::build(&data, name.clone(), description.clone(), None, None).unwrap();
        let encoded = zippy.encode().unwrap();

        let decoded = Zippy::decode(&encoded, None).unwrap();
        assert_eq!(decoded.name, name);
        assert_eq!(decoded.description, description);
        assert_eq!(decoded.data, data);
    }

    #[test]
    fn test_decode_zippy() {
        let data = vec![1, 2, 3, 4, 5];
        let name = String::from("Test");
        let description = String::from("Test description");

        let zippy = Zippy::build(&data, name.clone(), description.clone(), None, None).unwrap();
        let encoded = zippy.encode().unwrap();

        let decoded_data = decode_zippy(&encoded, None).unwrap();
        assert_eq!(decoded_data, data);
    }

    #[test]
    fn test_crc32_zippy() {
        let data = vec![1, 2, 3, 4, 5];
        let name = String::from("Test");
        let description = String::from("Test description");

        let zippy = Zippy::build(&data, name.clone(), description.clone(), None, None).unwrap();
        let encoded = zippy.encode().unwrap();

        let zippy = Zippy::decode(&encoded, None).unwrap();
        assert!(zippy.has_feature(ZippyFeatures::Crc32));
        assert!(zippy.check_crc32());
        assert_eq!(zippy.crc32(), 0x53518fab);
    }

    #[test]
    fn test_no_crc32_zippy() {
        let data = vec![1, 2, 3, 4, 5];
        let name = String::from("Test");
        let description = String::from("Test description");

        let zippy = Zippy::build(
            &data,
            name.clone(),
            description.clone(),
            None,
            Some(ZippyOptions::new(false)),
        )
        .unwrap();
        let encoded = zippy.encode().unwrap();

        let zippy = Zippy::decode(&encoded, None).unwrap();
        assert!(zippy.has_feature(ZippyFeatures::Crc32));
        assert!(!zippy.check_crc32());
        assert_eq!(zippy.crc32(), 0xffffffff);
    }

    #[test]
    fn test_decode_invalid() {
        let decoded_data = decode_zippy(b"invalid", None);
        assert!(decoded_data.is_err());
        assert_eq!(decoded_data.unwrap_err(), Error::InvalidData);
    }

    #[test]
    fn test_dummy_feature() {
        let data = vec![1, 2, 3, 4, 5];
        let name = String::from("Test");
        let description = String::from("Test description");

        let zippy = Zippy::build(
            &data,
            name.clone(),
            description.clone(),
            Some(vec![ZippyFeatures::Other]),
            Some(ZippyOptions::new(false)),
        )
        .unwrap();
        let encoded = zippy.encode().unwrap();

        let zippy = Zippy::decode(&encoded, None).unwrap();
        assert!(zippy.has_feature(ZippyFeatures::Other));
        assert!(!zippy.has_feature(ZippyFeatures::Crc32));
        assert!(!zippy.check_crc32());
        assert_eq!(zippy.crc32(), 0xffffffff);
    }
}