use anyhow::{Context, Result, bail};
use deku::ctx::{Endian, Order};
use deku::{DekuError, DekuWriter};
use deku::prelude::{Reader, Writer};
use deku::prelude::*;

pub trait DekuBytes: Sized + Clone + DekuContainerWrite + for<'a> DekuContainerRead<'a> {
    fn to_bytes(&self) -> Result<Vec<u8>> {
        let type_name = std::any::type_name::<Self>();
        DekuContainerWrite::to_bytes(self)
            .context(format!("failed to encode {type_name}"))
    }

    fn from_bytes(bytes: &[u8]) -> Result<Self> {
        let type_name = std::any::type_name::<Self>();
        let ((rest, bit_offset), value) =
            <Self as DekuContainerRead>::from_bytes((bytes, 0))
                .context(format!("failed to decode {type_name}"))?;

        if !rest.is_empty() || bit_offset != 0 {
            bail!("trailing bytes after {type_name}");
        }

        Ok(value)
    }
}

pub fn read_pascal_string<R>(reader: &mut Reader<R>) -> core::result::Result<String, DekuError>
where
    R: deku::no_std_io::Read + deku::no_std_io::Seek,
{
    let byte_len = u32::from_reader_with_ctx(reader, Endian::Little)? as usize;
    let mut bytes = vec![0u8; byte_len];
    reader.read_bytes(byte_len, &mut bytes, Order::Msb0)?;

    String::from_utf8(bytes).map_err(|_| DekuError::Parse("invalid utf-8".into()))
}

pub fn write_pascal_string<W>(
    writer: &mut Writer<W>,
    value: &String,
) -> core::result::Result<(), DekuError>
where
    W: deku::no_std_io::Write + deku::no_std_io::Seek,
{
    let bytes = value.as_bytes();
    let byte_len =
        u32::try_from(bytes.len()).map_err(|_| DekuError::Parse("string too large".into()))?;

    byte_len.to_writer(writer, Endian::Little)?;
    writer.write_bytes(bytes)?;
    Ok(())
}

pub fn read_vec_u32<R, T>(reader: &mut Reader<R>) -> core::result::Result<Vec<T>, DekuError>
where
    R: deku::no_std_io::Read + deku::no_std_io::Seek,
    for<'a> T: DekuReader<'a, ()>,
{
    let len = u32::from_reader_with_ctx(reader, Endian::Little)? as usize;
    let mut items = Vec::with_capacity(len);
    for _ in 0..len {
        let item = T::from_reader_with_ctx(reader, ())?;
        items.push(item);
    }
    Ok(items)
}

pub fn write_vec_u32<W, T>(
    writer: &mut Writer<W>,
    value: &Vec<T>,
) -> core::result::Result<(), DekuError>
where
    W: deku::no_std_io::Write + deku::no_std_io::Seek,
    T: DekuWriter<()>,
{
    let len = u32::try_from(value.len())
        .map_err(|_| DekuError::Parse("vector too large".into()))?;
    len.to_writer(writer, Endian::Little)?;
    for item in value {
        item.to_writer(writer, ())?;
    }
    Ok(())
}
