//! Bounds-checked big-endian reads over a byte slice.
//!
//! Slicing clamps to the end of the buffer, which matches how the source
//! produces sub-buffers. Typed reads check bounds and return
//! [`Error::UnexpectedEof`] when a read would pass the end.

use crate::Error;

/// A view over a byte slice with big-endian read helpers.
#[derive(Clone, Copy)]
pub struct Reader<'a> {
    data: &'a [u8],
}

impl<'a> Reader<'a> {
    /// Wrap a slice.
    pub fn new(data: &'a [u8]) -> Self {
        Reader { data }
    }

    /// A sub-view starting at `start`. `start` clamps to the buffer length, so
    /// an out-of-range start yields an empty view. This never fails.
    pub fn slice_from(&self, start: usize) -> Reader<'a> {
        let start = start.min(self.data.len());
        Reader {
            data: &self.data[start..],
        }
    }

    /// A sub-view of `len` bytes at `start`. Both bounds clamp to the buffer
    /// length, so an out-of-range slice yields a shorter or empty view. This
    /// never fails. Reads on that view then report any shortfall.
    pub fn slice(&self, start: usize, len: usize) -> Reader<'a> {
        let start = start.min(self.data.len());
        let end = start.saturating_add(len).min(self.data.len());
        Reader {
            data: &self.data[start..end],
        }
    }

    /// Borrow `len` bytes at `offset`, clamped to the buffer end.
    ///
    /// An offset past the end yields an empty slice. A length that runs past the
    /// end yields the bytes that remain. This matches the sub-buffer semantics
    /// of the slicing helpers and never fails.
    pub fn clamped(&self, offset: usize, len: usize) -> &'a [u8] {
        let start = offset.min(self.data.len());
        let end = start.saturating_add(len).min(self.data.len());
        &self.data[start..end]
    }

    /// Borrow `len` bytes at `offset`.
    pub fn bytes(&self, offset: usize, len: usize) -> Result<&'a [u8], Error> {
        let end = offset.checked_add(len).ok_or(Error::UnexpectedEof)?;
        self.data.get(offset..end).ok_or(Error::UnexpectedEof)
    }

    /// Read a `u8` at `offset`.
    pub fn u8(&self, offset: usize) -> Result<u8, Error> {
        self.data.get(offset).copied().ok_or(Error::UnexpectedEof)
    }

    /// Read a big-endian `u16` at `offset`.
    pub fn u16_be(&self, offset: usize) -> Result<u16, Error> {
        let b = self.bytes(offset, 2)?;
        Ok(u16::from_be_bytes([b[0], b[1]]))
    }

    /// Read a big-endian `u32` at `offset`.
    pub fn u32_be(&self, offset: usize) -> Result<u32, Error> {
        let b = self.bytes(offset, 4)?;
        Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
    }
}
