use crate::compress::{compress, CompressOptions};
use crate::error::Error;
use crate::schema::Dictionary;
use crate::OpenDictionary;

use super::consts::{SIGNATURE, VERSION};

#[derive(Default)]
pub struct CompilerOptions {
    pub compress_options: CompressOptions,
}

impl AsRef<CompilerOptions> for CompilerOptions {
    fn as_ref(&self) -> &CompilerOptions {
        self
    }
}

impl CompilerOptions {
    pub fn with_compression(mut self, compress_options: CompressOptions) -> Self {
        self.compress_options = compress_options;
        self
    }
}

impl OpenDictionary {
    pub fn to_bytes(&self) -> crate::Result<Vec<u8>> {
        self.to_bytes_with_options(CompilerOptions::default())
    }

    pub fn to_bytes_with_options<Options: AsRef<CompilerOptions>>(
        &self,
        options: Options,
    ) -> crate::Result<Vec<u8>> {
        let compressed = compress(&self.bytes, &options.as_ref().compress_options)
            .map_err(|e| Error::Write(e.to_string()))?;

        let version_bytes = VERSION.as_bytes();
        let version_size = version_bytes.len() as u64;
        let version_size_bytes = version_size.to_le_bytes();

        let compressed_size = compressed.len() as u64;
        let compressed_size_bytes = compressed_size.to_le_bytes();

        let total_size = SIGNATURE.len()
            + version_size_bytes.len()
            + compressed_size_bytes.len()
            + compressed.len();

        if SIGNATURE.len() != 5 {
            return Err(Error::Write("Signature bytes do not equal 5".into()));
        }

        if version_size_bytes.len() != 8 {
            return Err(Error::Write("Version byte count does not equal 8".into()));
        }

        if version_bytes.len() != version_size as usize {
            return Err(Error::Write(
                "Version byte count does not equal the computed byte count".into(),
            ));
        }

        if compressed_size_bytes.len() != 8 {
            return Err(Error::Write(
                "Compressed byte count does not equal 8".into(),
            ));
        }

        if compressed.len() != compressed_size as usize {
            return Err(Error::Write(
                "Compressed byte count does not equal the computed byte count".into(),
            ));
        }

        let mut output = Vec::with_capacity(total_size);

        output.extend_from_slice(SIGNATURE);
        output.extend_from_slice(&version_size_bytes);
        output.extend_from_slice(&version_bytes);
        output.extend_from_slice(&compressed_size_bytes);
        output.extend_from_slice(&compressed);

        Ok(output)
    }
}

impl Dictionary {
    pub fn build(&self) -> crate::Result<OpenDictionary> {
        let dict = OpenDictionary {
            signature: String::from_utf8_lossy(SIGNATURE).to_string(),
            version: VERSION.clone(),
            path: None,
            bytes: self.serialize()?,
        };
        Ok(dict)
    }
}
