use super::{bits::*, Error};
use byteorder::{ByteOrder, LittleEndian};
use lz4::{BlockMode, EncoderBuilder};

pub use lz4::BlockSize;

pub fn lz4(data: &Vec<u8>, block_size: BlockSize, compression_level: u32) -> Result<Vec<u8>, Error> {
    let mut lz4_encoder = EncoderBuilder::new().level(compression_level).checksum(lz4::ContentChecksum::NoChecksum).block_size(block_size).block_mode(BlockMode::Linked).build(Vec::<u8>::new())?;
    
    std::io::copy(&mut&data[..], &mut lz4_encoder)?;
    let (output, result) = lz4_encoder.finish();

    match result {
        Ok(()) => Ok(output),
        Err(error) => Err(Error::from_error(error))
    }
}

pub fn strip_lz4(compressed_data: Vec<u8>) -> Result<Vec<u8>, Error> {
    fn read_header(raw_data: &[u8]) -> Result<(usize, bool, bool), Error> {
        const FRAME_HEADER_MIN_SIZE:usize = 7;
        const MAGIC_NUMBER:u32            = 0x184D2204u32;
        const BLOCK_CHECKSUM_BIT:usize    = 4;
        const CONTENT_SIZE_BIT:usize      = 3;
        const CONTENT_CHECKSUM_BIT:usize  = 2;
        const DICT_ID_BIT:usize           = 0;
        
        let mut bytes_to_skip = FRAME_HEADER_MIN_SIZE;
        if raw_data.len() >= FRAME_HEADER_MIN_SIZE {
            let magic_number = LittleEndian::read_u32(raw_data);
            if magic_number.to_le() == MAGIC_NUMBER {
                let raw_data          = &raw_data[std::mem::size_of::<u32>()..];
                let flg               = raw_data[0];
                let _bd               = raw_data[1];                

                if bit_set_u8(flg, CONTENT_SIZE_BIT) {
                    bytes_to_skip += std::mem::size_of::<u64>();
                }

                if bit_set_u8(flg, DICT_ID_BIT) {
                    bytes_to_skip += std::mem::size_of::<u32>();
                }

                if raw_data.len() < bytes_to_skip {
                    Err(Error::from_text(format!("Can't skip {} bytes of frame header because there are only {} bytes left", bytes_to_skip, raw_data.len())))
                }

                else {
                    Ok((bytes_to_skip, bit_set_u8(flg, BLOCK_CHECKSUM_BIT), bit_set_u8(flg, CONTENT_CHECKSUM_BIT)))
                }
            }

            else {
                Err(Error::from_text(format!("Magic number needs to be {:#08x} - but {:#08x} was found!", MAGIC_NUMBER, magic_number.to_le())))
            }
        }

        else {
            Err(Error::from_str("Not enough bytes for minimum header size"))
        }
    }
    
    let (bytes_to_skip, has_block_checksum, has_content_checksum) = read_header(&compressed_data)?;
    let mut raw_data                                              = &compressed_data[bytes_to_skip..];
    let end_len                                                   = {
        if has_content_checksum {
            std::mem::size_of::<u32>()*2
        }

        else {
            std::mem::size_of::<u32>()
        }
    };
    let mut new_data = Vec::new();

    while raw_data.len() > end_len {
        let block_size = LittleEndian::read_u32(raw_data);
        raw_data       = &raw_data[std::mem::size_of::<u32>()..];

        if bit_set_u32(block_size, 31) {
            return Err(Error::from_str("Stripping uncompressed data is not supported! (If this is an Overlay: try to increase the size of it by adding patterens so that compression actually happens)"));
        }

        let block_size = block_size as usize;
        new_data.extend_from_slice(&raw_data[0..block_size]);
        raw_data = &raw_data[block_size..];

        if has_block_checksum {
            raw_data = &raw_data[std::mem::size_of::<u32>()..];
        }
    }

    if LittleEndian::read_u32(raw_data) != 0 {
        Err(Error::from_str("EOF is not zero!"))
    }

    else {
        Ok(new_data)
    }
}

pub mod psx_default {
    use super::*;

    const DEFAULT_COMPRESSION_LEVEL:u32 = 16;
    const DEFAULT_BLOCK_SIZE:BlockSize  = BlockSize::Max4MB;

    pub fn lz4(data: &Vec<u8>) -> Result<Vec<u8>, Error> {
        strip_lz4(super::lz4(data, DEFAULT_BLOCK_SIZE, DEFAULT_COMPRESSION_LEVEL)?)
    }
}