//! Shared little-endian byte cursor for the format parsers.
//!
//! Returns a generic [`OutOfBounds`] error that each format converts
//! into its own `ParseError::Truncated` variant via `From`.

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct OutOfBounds {
    pub at: usize,
    pub need: usize,
}

pub(crate) struct Cursor<'a> {
    bytes: &'a [u8],
    pub(crate) pos: usize,
}

impl<'a> Cursor<'a> {
    pub(crate) fn new(bytes: &'a [u8]) -> Self {
        Self { bytes, pos: 0 }
    }

    pub(crate) fn read_u8(&mut self) -> Result<u8, OutOfBounds> {
        let buf = self.read_bytes(1)?;
        Ok(buf[0])
    }

    pub(crate) fn read_u16(&mut self) -> Result<u16, OutOfBounds> {
        let buf = self.read_bytes(2)?;
        Ok(u16::from_le_bytes([buf[0], buf[1]]))
    }

    pub(crate) fn read_u32(&mut self) -> Result<u32, OutOfBounds> {
        let buf = self.read_bytes(4)?;
        Ok(u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]))
    }

    pub(crate) fn read_i16(&mut self) -> Result<i16, OutOfBounds> {
        let buf = self.read_bytes(2)?;
        Ok(i16::from_le_bytes([buf[0], buf[1]]))
    }

    pub(crate) fn read_i32(&mut self) -> Result<i32, OutOfBounds> {
        let buf = self.read_bytes(4)?;
        Ok(i32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]))
    }

    pub(crate) fn read_f32(&mut self) -> Result<f32, OutOfBounds> {
        Ok(f32::from_bits(self.read_u32()?))
    }

    pub(crate) fn read_bytes(&mut self, n: usize) -> Result<&'a [u8], OutOfBounds> {
        let end = self.pos.checked_add(n).ok_or(OutOfBounds {
            at: self.pos,
            need: n,
        })?;
        if end > self.bytes.len() {
            return Err(OutOfBounds {
                at: self.pos,
                need: n,
            });
        }
        let slice = &self.bytes[self.pos..end];
        self.pos = end;
        Ok(slice)
    }

    pub(crate) fn remaining(&self) -> usize {
        self.bytes.len() - self.pos
    }

    pub(crate) fn peek(&self, n: usize) -> Option<&'a [u8]> {
        let end = self.pos.checked_add(n)?;
        if end > self.bytes.len() {
            return None;
        }
        Some(&self.bytes[self.pos..end])
    }
}

impl Cursor<'_> {
    /// QE.6b — capacity clamp for length-prefixed reads: a crafted
    /// count can't drive `Vec::with_capacity` past what the remaining
    /// bytes could possibly encode (`elem_size` = minimum bytes per
    /// element). A lying count still fails the subsequent reads with
    /// [`OutOfBounds`] — this only defuses the allocation bomb that
    /// would abort the process before those reads ran.
    pub(crate) fn clamped_capacity(&self, declared: usize, elem_size: usize) -> usize {
        declared.min(self.remaining() / elem_size.max(1))
    }
}
