use std::any::Any;
use std::error::Error;
use binrw::{BinRead, BinWrite, BinWriterExt};
use crate::blf::s_blf_header::s_blf_header;
use crate::result::BLFLibResult;
use crate::types::chunk_signature::chunk_signature;
use crate::types::chunk_version::chunk_version;

pub trait BlfChunk {
    fn get_signature() -> chunk_signature;
    fn get_version() -> chunk_version;
}

pub trait DynamicBlfChunk {
    fn signature(&self) -> chunk_signature;
    fn version(&self) -> chunk_version;
}

pub trait BlfChunkHooks {
    fn before_write(&mut self, _previously_written: &Vec<u8>) -> BLFLibResult { Ok(()) }
    fn after_read(&mut self, _previously_read: &[u8]) -> BLFLibResult { Ok(()) }
}

pub trait SerializableBlfChunk: DynamicBlfChunk + Any + Send + Sync {
    fn encode_body(&mut self, previously_written: &Vec<u8>) -> BLFLibResult<Vec<u8>>;
    fn decode_body(&mut self, buffer: &[u8], previously_read: &[u8]) -> BLFLibResult;

    fn write(&mut self, previously_written: &Vec<u8>) -> BLFLibResult<Vec<u8>> {
        let mut encoded_chunk = self.encode_body(previously_written)?;
        let header = s_blf_header {
            signature: self.signature(),
            version: self.version(),
            chunk_size: (encoded_chunk.len() + s_blf_header::size()) as u32,
        };

        let mut encoded = Vec::with_capacity(s_blf_header::size() + encoded_chunk.len());
        encoded.append(&mut header.encode());
        encoded.append(&mut encoded_chunk);

        Ok(encoded)
    }

    fn as_any(&self) -> &dyn Any;
}

impl<T: DynamicBlfChunk + BinRead + BinWrite + Clone + Any + BlfChunkHooks + Send + Sync> SerializableBlfChunk for T
    where for<'a> <T as BinWrite>::Args<'a>: Default, for<'a> <T as BinRead>::Args<'a>: Default {
    fn encode_body(&mut self, previously_written: &Vec<u8>) -> BLFLibResult<Vec<u8>> where for<'a> <T as BinWrite>::Args<'a>: Default {
        self.before_write(previously_written)?;

        let mut writer = std::io::Cursor::new(Vec::<u8>::new());
        writer.write_ne(self)?;
        Ok(writer.get_ref().clone())
    }

    fn decode_body(&mut self, buffer: &[u8], previously_read: &[u8]) -> BLFLibResult where for<'b> <T as BinRead>::Args<'b>: Default {
        let mut reader = std::io::Cursor::new(buffer);

        self.clone_from(&T::read_ne(&mut reader)?);

        self.after_read(previously_read)?;
        Ok(())
    }

    fn as_any(&self) -> &dyn Any {
        self
    }
}

pub trait ReadableBlfChunk: BlfChunk + Sized + SerializableBlfChunk + Default {
    fn read(buffer: Vec<u8>, header: Option<s_blf_header>, _previously_read: &[u8]) -> BLFLibResult<Self> {
        let offset = if header.is_some() { 0 } else { s_blf_header::size() };
        let header = match header {
            None => s_blf_header::decode(buffer.as_slice())?,
            Some(header) => header
        };
        let end = (header.chunk_size as usize - s_blf_header::size()) - offset;
        let mut chunk = Self::default();
        chunk.decode_body(&buffer[offset..end], _previously_read.as_ref())?;

        Ok(chunk)
    }
}

impl<T: BlfChunk + Sized + SerializableBlfChunk + Default> ReadableBlfChunk for T {

}

pub trait TitleAndBuild {
    fn get_title() -> &'static str;

    fn get_build_string() -> &'static str;
}

pub trait DynTitleAndBuild {
    fn title(&self) -> String;

    fn build_string(&self) -> String;
}
