//! Native implementation of `LibreMetaverse`'s incremental CRC-32 accumulator.

#![allow(clippy::needless_pass_by_value)] // Public signature mirrors the mapped C# byte array.

use crate::Error;

/// Incremental reflected CRC-32 accumulator using polynomial `0xedb88320`.
///
/// `LibreMetaverse` exposes the running (not final-XORed) accumulator. A new
/// value therefore starts at `0xffff_ffff`, and callers may apply a final XOR
/// themselves if they need the conventional ISO-HDLC checksum.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct CRC32 {
    /// Current running CRC value.
    pub crc: u32,
}

impl CRC32 {
    /// Creates an accumulator initialized to `0xffff_ffff`.
    ///
    /// # Errors
    ///
    /// This compatibility signature is infallible and always returns `Ok`.
    pub fn new() -> Result<Self, Error> {
        Ok(Self { crc: u32::MAX })
    }

    /// Incorporates one byte into the running accumulator.
    ///
    /// # Errors
    ///
    /// This compatibility signature is infallible and always returns `Ok`.
    pub fn update_with_byte(&mut self, value: u8) -> Result<(), Error> {
        self.crc = update(self.crc, value);
        Ok(())
    }

    /// Updates bytes in the half-open range `pos..length`.
    ///
    /// The final argument intentionally preserves the pinned C# implementation:
    /// it is an exclusive end index, despite being named `length`.
    ///
    /// # Errors
    ///
    /// Returns [`Error::IndexOutOfRange`] when an iterated index is outside
    /// `value`. As in C#, bytes before the invalid index remain incorporated.
    pub fn update_with_bytes_int32_int32(
        &mut self,
        value: Vec<u8>,
        pos: i32,
        length: i32,
    ) -> Result<(), Error> {
        if pos >= length {
            return Ok(());
        }
        for index in pos..length {
            let index = usize::try_from(index).map_err(|_| Error::IndexOutOfRange)?;
            let byte = *value.get(index).ok_or(Error::IndexOutOfRange)?;
            self.crc = update(self.crc, byte);
        }
        Ok(())
    }
}

fn update(mut crc: u32, byte: u8) -> u32 {
    crc ^= u32::from(byte);
    for _ in 0..8 {
        let mask = 0_u32.wrapping_sub(crc & 1);
        crc = (crc >> 1) ^ (0xedb8_8320 & mask);
    }
    crc
}

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

    #[test]
    fn crc32_matches_standard_and_incremental_golden_vectors() {
        let mut crc = CRC32::new().unwrap();
        crc.update_with_bytes_int32_int32(b"123456789".to_vec(), 0, 9)
            .unwrap();
        assert_eq!(crc.crc, 0x340b_c6d9);
        assert_eq!(crc.crc ^ u32::MAX, 0xcbf4_3926);

        let mut incremental = CRC32::new().unwrap();
        for byte in b"123456789" {
            incremental.update_with_byte(*byte).unwrap();
        }
        assert_eq!(incremental, crc);
    }

    #[test]
    fn range_argument_is_an_exclusive_end_index() {
        let mut selected = CRC32::new().unwrap();
        selected
            .update_with_bytes_int32_int32(b"x123y".to_vec(), 1, 4)
            .unwrap();
        let mut direct = CRC32::new().unwrap();
        direct
            .update_with_bytes_int32_int32(b"123".to_vec(), 0, 3)
            .unwrap();
        assert_eq!(selected, direct);
    }

    #[test]
    fn invalid_end_index_preserves_csharp_partial_update_behavior() {
        let mut partial = CRC32::new().unwrap();
        assert_eq!(
            partial.update_with_bytes_int32_int32(b"12".to_vec(), 0, 3),
            Err(Error::IndexOutOfRange)
        );
        let mut expected = CRC32::new().unwrap();
        expected
            .update_with_bytes_int32_int32(b"12".to_vec(), 0, 2)
            .unwrap();
        assert_eq!(partial, expected);
    }
}
