use crate::{string, vec};

use crate::{Layout, ReaderExt, Result, reader};

pub trait Decode: Sized + Layout {
    fn decode(buffer: &[u8]) -> Result<Self>;
}
impl Decode for bool {
    fn decode(r: &[u8]) -> Result<Self> {
        let [v] = r.read_const::<1>();
        Ok(v != 0)
    }
}

impl Decode for i8 {
    fn decode(r: &[u8]) -> Result<Self> {
        Ok(i8::from_le_bytes(r.read_const()))
    }
}
impl Decode for i16 {
    fn decode(r: &[u8]) -> Result<Self> {
        Ok(i16::from_le_bytes(r.read_const()))
    }
}
impl Decode for i32 {
    fn decode(r: &[u8]) -> Result<Self> {
        Ok(i32::from_le_bytes(r.read_const()))
    }
}
impl Decode for i64 {
    fn decode(r: &[u8]) -> Result<Self> {
        Ok(i64::from_le_bytes(r.read_const()))
    }
}
impl Decode for u8 {
    fn decode(r: &[u8]) -> Result<Self> {
        Ok(u8::from_le_bytes(r.read_const()))
    }
}
impl Decode for u16 {
    fn decode(r: &[u8]) -> Result<Self> {
        Ok(u16::from_le_bytes(r.read_const()))
    }
}
impl Decode for u32 {
    fn decode(r: &[u8]) -> Result<Self> {
        Ok(u32::from_le_bytes(r.read_const()))
    }
}
impl Decode for u64 {
    fn decode(r: &[u8]) -> Result<Self> {
        Ok(u64::from_le_bytes(r.read_const()))
    }
}
impl Decode for f32 {
    fn decode(r: &[u8]) -> Result<Self> {
        Ok(f32::from_le_bytes(r.read_const()))
    }
}
impl Decode for f64 {
    fn decode(r: &[u8]) -> Result<Self> {
        Ok(f64::from_le_bytes(r.read_const()))
    }
}

impl Decode for string::String {
    fn decode(r: &[u8]) -> Result<Self> {
        let (offset, len) = reader::ArrayReader::<&[u8]>::read_head(r);

        let buf = r.skip(offset).read_n(len).to_vec();
        Ok(string::String::from_utf8(buf).unwrap())
    }
}
impl<E: Decode> Decode for vec::Vec<E> {
    fn decode(r: &[u8]) -> Result<Self> {
        let (offset, len) = reader::ArrayReader::<&[u8]>::read_head(r);
        let mut buf = r.skip(offset);

        let mut out = vec::Vec::with_capacity(len);
        let item_head_bytes = E::head_size().div_ceil(8);
        for _ in 0..len {
            out.push(E::decode(buf)?);
            buf = buf.skip(item_head_bytes);
        }
        Ok(out)
    }
}
impl<E: Decode> Decode for Option<E> {
    fn decode(buf: &[u8]) -> Result<Self> {
        let [tag] = buf.read_const::<1>();
        if tag == 0 {
            Ok(None)
        } else {
            let buf = buf.skip(1);
            let inner_head_size = E::head_size();
            let has_ptr = inner_head_size > 32;

            Ok(Some(if has_ptr {
                let offset = u32::from_le_bytes(buf.read_const::<4>()) as usize;
                E::decode(buf.skip(offset))?
            } else {
                E::decode(buf)?
            }))
        }
    }
}
impl<T: Decode, E: Decode> Decode for core::result::Result<T, E> {
    fn decode(buf: &[u8]) -> Result<Self> {
        let t_head_bits = T::head_size();
        let e_head_bits = E::head_size();
        let max_head_bits = t_head_bits.max(e_head_bits);
        let has_ptr = max_head_bits > 32;

        let [tag] = buf.read_const::<1>();
        let buf = buf.skip(1);

        Ok(match tag {
            0 => {
                let t = if has_ptr {
                    let offset = u32::from_le_bytes(buf.read_const::<4>()) as usize;
                    T::decode(buf.skip(offset))?
                } else {
                    T::decode(buf)?
                };
                Ok(t)
            }
            1 => {
                let e = if has_ptr {
                    let offset = u32::from_le_bytes(buf.read_const::<4>()) as usize;
                    E::decode(buf.skip(offset))?
                } else {
                    E::decode(buf)?
                };
                Err(e)
            }
            _ => return Err(crate::Error::InvalidData),
        })
    }
}
impl Decode for () {
    fn decode(_buf: &[u8]) -> Result<Self> {
        Ok(())
    }
}

pub fn decode_enum_head(mut data: &[u8], tag_bytes: u8, has_ptr: bool) -> (u64, &[u8]) {
    use crate::ReaderExt;
    use bytes::Buf;

    let mut tag = vec![0; 8];
    data.copy_to_slice(&mut tag[0..tag_bytes as usize]);
    let tag = u64::from_le_bytes(tag.try_into().unwrap()) as u64;

    if has_ptr {
        // read ptr and dereference
        let offset = u32::from_le_bytes(data.read_const::<4>());
        data = data.skip(offset as usize);
    } else {
        // inner is right after the tag
    }

    (tag, data)
}
