// Derived from PDFium's core/fxcrt/cfx_bitstream.h/.cpp
// Original: Copyright 2014 The PDFium Authors
// Licensed under BSD-3-Clause / Apache-2.0
// See pdfium-upstream/LICENSE for the original license.

//! Bit-level reader for binary data streams.
//!
//! Corresponds to PDFium's `CFX_BitStream` class in `core/fxcrt/cfx_bitstream.h`.
//! Reads bits MSB-first from a byte slice, with byte alignment and position tracking.

/// A bit-level reader for parsing binary streams MSB-first.
///
/// Corresponds to `CFX_BitStream` in PDFium.
pub struct BitReader<'a> {
    data: &'a [u8],
    /// Current bit position (0-based from the start of `data`).
    bit_pos: usize,
}

impl<'a> BitReader<'a> {
    /// Create a new `BitReader` over the given byte slice.
    ///
    /// Corresponds to `CFX_BitStream::CFX_BitStream(pdfium::span<const uint8_t>)`.
    pub fn new(data: &'a [u8]) -> Self {
        Self { data, bit_pos: 0 }
    }

    /// Read `n` bits (0 ≤ n ≤ 32) as a `u32`, MSB first.
    ///
    /// Returns `None` if there are not enough bits remaining.
    /// Corresponds to `CFX_BitStream::GetBits()`.
    pub fn read_bits(&mut self, n: u8) -> Option<u32> {
        if n == 0 {
            return Some(0);
        }
        if n > 32 {
            return None;
        }
        if self.bits_remaining() < n as usize {
            return None;
        }

        let mut value: u32 = 0;
        for _ in 0..n {
            let byte_idx = self.bit_pos / 8;
            let bit_idx = 7 - (self.bit_pos % 8); // MSB first
            let bit = (self.data[byte_idx] >> bit_idx) & 1;
            value = (value << 1) | u32::from(bit);
            self.bit_pos += 1;
        }
        Some(value)
    }

    /// Upstream-aligned alias for [`read_bits`](Self::read_bits).
    ///
    /// Corresponds to `CFX_BitStream::GetBits()` in PDFium upstream.
    #[inline]
    pub fn get_bits(&mut self, n: u8) -> Option<u32> {
        self.read_bits(n)
    }

    /// Read 32 bits as a `u32`.
    pub fn read_u32(&mut self) -> Option<u32> {
        self.read_bits(32)
    }

    /// Read 32 bits and interpret as a signed `i32`.
    pub fn read_i32(&mut self) -> Option<i32> {
        self.read_u32().map(|v| v as i32)
    }

    /// Read 16 bits as a `u16`.
    pub fn read_u16(&mut self) -> Option<u16> {
        self.read_bits(16).map(|v| v as u16)
    }

    /// Align the bit position to the next byte boundary.
    ///
    /// If already aligned, this is a no-op.
    /// Corresponds to `CFX_BitStream::ByteAlign()`.
    pub fn byte_align(&mut self) {
        let rem = self.bit_pos % 8;
        if rem != 0 {
            self.bit_pos += 8 - rem;
        }
    }

    /// Returns `true` if all bits have been consumed.
    ///
    /// Corresponds to `CFX_BitStream::IsEOF()`.
    pub fn is_eof(&self) -> bool {
        self.bit_pos >= self.data.len() * 8
    }

    /// Returns the current bit offset from the start of data.
    ///
    /// Corresponds to `CFX_BitStream::GetPos()`.
    pub fn position(&self) -> usize {
        self.bit_pos
    }

    /// Upstream-aligned alias for [`position`](Self::position).
    ///
    /// Corresponds to `CFX_BitStream::GetPos()` in PDFium upstream.
    #[inline]
    pub fn get_pos(&self) -> usize {
        self.position()
    }

    /// Skip `n` bits ahead.
    ///
    /// Clamps to end-of-stream if `n` exceeds remaining bits.
    /// Corresponds to `CFX_BitStream::SkipBits()`.
    pub fn skip(&mut self, n: usize) {
        let total = self.data.len() * 8;
        self.bit_pos = (self.bit_pos + n).min(total);
    }

    /// Upstream-aligned alias for [`skip`](Self::skip).
    ///
    /// Corresponds to `CFX_BitStream::SkipBits()` in PDFium upstream.
    #[inline]
    pub fn skip_bits(&mut self, n: usize) {
        self.skip(n);
    }

    /// Returns the number of bits remaining (not yet consumed).
    ///
    /// Corresponds to `CFX_BitStream::BitsRemaining()`.
    pub fn bits_remaining(&self) -> usize {
        let total = self.data.len() * 8;
        total.saturating_sub(self.bit_pos)
    }

    /// Reset the bit position to the start of the data (bit 0).
    ///
    /// Corresponds to `CFX_BitStream::Rewind()`.
    pub fn rewind(&mut self) {
        self.bit_pos = 0;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new_creates_reader_at_position_zero() {
        let data = [0xABu8, 0xCD];
        let reader = BitReader::new(&data);
        assert_eq!(reader.position(), 0);
        assert!(!reader.is_eof());
    }

    #[test]
    fn test_read_bits_msb_first() {
        // 0b10110100 = 0xB4
        let data = [0xB4u8];
        let mut reader = BitReader::new(&data);
        assert_eq!(reader.read_bits(1), Some(1)); // MSB
        assert_eq!(reader.read_bits(1), Some(0));
        assert_eq!(reader.read_bits(3), Some(0b110));
        assert_eq!(reader.read_bits(3), Some(0b100));
    }

    #[test]
    fn test_read_bits_zero_returns_zero() {
        let data = [0xFFu8];
        let mut reader = BitReader::new(&data);
        assert_eq!(reader.read_bits(0), Some(0));
        assert_eq!(reader.position(), 0);
    }

    #[test]
    fn test_read_bits_exceeds_32_returns_none() {
        let data = [0u8; 8];
        let mut reader = BitReader::new(&data);
        assert_eq!(reader.read_bits(33), None);
        assert_eq!(reader.position(), 0); // position unchanged
    }

    #[test]
    fn test_read_bits_past_end_returns_none() {
        let data = [0xFFu8];
        let mut reader = BitReader::new(&data);
        assert_eq!(reader.read_bits(8), Some(0xFF));
        assert_eq!(reader.read_bits(1), None);
    }

    #[test]
    fn test_byte_align_advances_to_next_byte() {
        let data = [0xFF, 0x00];
        let mut reader = BitReader::new(&data);
        reader.read_bits(3).unwrap();
        assert_eq!(reader.position(), 3);
        reader.byte_align();
        assert_eq!(reader.position(), 8);
    }

    #[test]
    fn test_byte_align_already_aligned_is_noop() {
        let data = [0xFF, 0x00];
        let mut reader = BitReader::new(&data);
        reader.byte_align();
        assert_eq!(reader.position(), 0);
        reader.read_bits(8).unwrap();
        reader.byte_align();
        assert_eq!(reader.position(), 8);
    }

    #[test]
    fn test_is_eof_false_when_bits_remain() {
        let data = [0xFFu8];
        let reader = BitReader::new(&data);
        assert!(!reader.is_eof());
    }

    #[test]
    fn test_is_eof_true_when_exhausted() {
        let data = [0xFFu8];
        let mut reader = BitReader::new(&data);
        reader.read_bits(8).unwrap();
        assert!(reader.is_eof());
    }

    #[test]
    fn test_is_eof_true_for_empty_data() {
        let reader = BitReader::new(&[]);
        assert!(reader.is_eof());
    }

    #[test]
    fn test_skip_advances_position() {
        let data = [0xFF, 0xFF];
        let mut reader = BitReader::new(&data);
        reader.skip(5);
        assert_eq!(reader.position(), 5);
    }

    #[test]
    fn test_skip_clamps_to_eof() {
        let data = [0xFFu8];
        let mut reader = BitReader::new(&data);
        reader.skip(100);
        assert_eq!(reader.position(), 8);
        assert!(reader.is_eof());
    }

    #[test]
    fn test_bits_remaining_counts_unread_bits() {
        let data = [0xFF, 0xFF];
        let mut reader = BitReader::new(&data);
        assert_eq!(reader.bits_remaining(), 16);
        reader.read_bits(5).unwrap();
        assert_eq!(reader.bits_remaining(), 11);
    }

    #[test]
    fn test_bits_remaining_zero_when_eof() {
        let data = [0xFFu8];
        let mut reader = BitReader::new(&data);
        reader.read_bits(8).unwrap();
        assert_eq!(reader.bits_remaining(), 0);
    }

    #[test]
    fn test_bits_remaining_empty_data() {
        let reader = BitReader::new(&[]);
        assert_eq!(reader.bits_remaining(), 0);
    }

    #[test]
    fn test_read_u32_reads_four_bytes() {
        let data = [0x12, 0x34, 0x56, 0x78];
        let mut reader = BitReader::new(&data);
        assert_eq!(reader.read_u32(), Some(0x12345678));
        assert!(reader.is_eof());
    }

    #[test]
    fn test_read_u16_reads_two_bytes() {
        let data = [0xAB, 0xCD];
        let mut reader = BitReader::new(&data);
        assert_eq!(reader.read_u16(), Some(0xABCD));
        assert!(reader.is_eof());
    }

    #[test]
    fn test_position_advances_with_reads() {
        let data = [0xFF, 0xFF];
        let mut reader = BitReader::new(&data);
        assert_eq!(reader.position(), 0);
        reader.read_bits(4).unwrap();
        assert_eq!(reader.position(), 4);
        reader.read_bits(8).unwrap();
        assert_eq!(reader.position(), 12);
    }

    #[test]
    fn test_rewind_resets_to_position_zero() {
        let data = [0xA5u8, 0x5A];
        let mut reader = BitReader::new(&data);
        reader.read_bits(8).unwrap();
        assert_eq!(reader.position(), 8);
        reader.rewind();
        assert_eq!(reader.position(), 0);
        assert!(!reader.is_eof());
    }

    #[test]
    fn test_rewind_allows_rereading_same_data() {
        let data = [0b10110001u8];
        let mut reader = BitReader::new(&data);
        let first = reader.read_bits(8).unwrap();
        reader.rewind();
        let second = reader.read_bits(8).unwrap();
        assert_eq!(first, second);
    }
}
