use std::fmt::Display;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ByteOrder {
    BigEndian,
    LittleEndian,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CursorError {
    UnexpectedEof,
}

impl Display for CursorError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CursorError::UnexpectedEof => write!(f, "Unexpected end of input"),
        }
    }
}

pub struct ByteCursor<'a> {
    data: &'a [u8],
    pos: usize,
    order: ByteOrder,
}

impl<'a> ByteCursor<'a> {
    #[inline]
    pub fn new(data: &'a [u8]) -> Self {
        Self {
            data,
            pos: 0,
            order: ByteOrder::BigEndian,
        }
    }

    #[inline]
    pub fn with_order(data: &'a [u8], order: ByteOrder) -> Self {
        Self {
            data,
            pos: 0,
            order,
        }
    }

    #[inline]
    pub fn order(&self) -> ByteOrder {
        self.order
    }
    #[inline]
    pub fn position(&self) -> usize {
        self.pos
    }
    #[inline]
    pub fn remaining(&self) -> usize {
        self.data.len().saturating_sub(self.pos)
    }
    #[inline]
    pub fn is_eof(&self) -> bool {
        self.pos >= self.data.len()
    }

    #[inline]
    pub fn skip(&mut self, n: usize) -> Result<(), CursorError> {
        if self.remaining() < n {
            return Err(CursorError::UnexpectedEof);
        }
        self.pos += n;
        Ok(())
    }

    #[inline]
    pub fn align(&mut self, align: usize) -> Result<(), CursorError> {
        let mis = self.pos % align;
        if mis == 0 {
            return Ok(());
        }
        self.skip(align - mis)
    }

    #[inline]
    pub fn slice(&mut self, n: usize) -> Result<&'a [u8], CursorError> {
        if self.remaining() < n {
            return Err(CursorError::UnexpectedEof);
        }
        let s = &self.data[self.pos..self.pos + n];
        self.pos += n;
        Ok(s)
    }

    #[inline]
    pub fn bytes(&mut self, n: usize) -> Result<&[u8], CursorError> {
        self.slice(n)
    }

    #[inline]
    fn take<const N: usize>(&mut self) -> Result<&'a [u8; N], CursorError> {
        if self.remaining() < N {
            return Err(CursorError::UnexpectedEof);
        }
        let p = self.pos;
        self.pos += N;
        let ptr = &self.data[p] as *const u8 as *const [u8; N];
        Ok(unsafe { &*ptr })
    }

    #[inline]
    pub fn u8(&mut self) -> Result<u8, CursorError> {
        self.take::<1>().map(|b| b[0])
    }
    #[inline]
    pub fn i8(&mut self) -> Result<i8, CursorError> {
        self.u8().map(|v| v as i8)
    }

    #[inline]
    pub fn u16(&mut self) -> Result<u16, CursorError> {
        let b = self.take::<2>()?;
        Ok(match self.order {
            ByteOrder::BigEndian => u16::from_be_bytes(*b),
            ByteOrder::LittleEndian => u16::from_le_bytes(*b),
        })
    }

    #[inline]
    pub fn i16(&mut self) -> Result<i16, CursorError> {
        self.u16().map(|v| v as i16)
    }

    #[inline]
    pub fn u32(&mut self) -> Result<u32, CursorError> {
        let b = self.take::<4>()?;
        Ok(match self.order {
            ByteOrder::BigEndian => u32::from_be_bytes(*b),
            ByteOrder::LittleEndian => u32::from_le_bytes(*b),
        })
    }

    #[inline]
    pub fn i32(&mut self) -> Result<i32, CursorError> {
        self.u32().map(|v| v as i32)
    }

    #[inline]
    pub fn u64(&mut self) -> Result<u64, CursorError> {
        let b = self.take::<8>()?;
        Ok(match self.order {
            ByteOrder::BigEndian => u64::from_be_bytes(*b),
            ByteOrder::LittleEndian => u64::from_le_bytes(*b),
        })
    }

    #[inline]
    pub fn i64(&mut self) -> Result<i64, CursorError> {
        self.u64().map(|v| v as i64)
    }

    #[inline]
    pub fn f32(&mut self) -> Result<f32, CursorError> {
        self.u32().map(f32::from_bits)
    }
    #[inline]
    pub fn f64(&mut self) -> Result<f64, CursorError> {
        self.u64().map(f64::from_bits)
    }

    #[inline]
    pub fn try_u8(&mut self) -> Option<u8> {
        if self.remaining() == 0 {
            None
        } else {
            let b = self.data[self.pos];
            self.pos += 1;
            Some(b)
        }
    }

    #[inline]
    pub fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), CursorError> {
        let n = buf.len();
        if self.remaining() < n {
            return Err(CursorError::UnexpectedEof);
        }

        buf.copy_from_slice(&self.data[self.pos..self.pos + n]);
        self.pos += n;
        Ok(())
    }
}
