use crate::{crc::Crc, ffi::ref_to_ptr};

pub type ChecksumCrc32 = u32; // uint32_t in C

unsafe extern "C" {
    fn crc32_new() -> Crc32;
    fn crc32_process(data: *const u8, len: u32, crc32: *mut Crc32);

    fn crc32_checksum(data: *const u8, len: u32) -> ChecksumCrc32;
    fn crc32_checksum_n_times(n: u32, data: *const u8, len: u32) -> ChecksumCrc32;
}

#[repr(C)]
pub struct Crc32 {
    buf: ChecksumCrc32,
}

impl Crc for Crc32 {
    type Input = [u8];

    type Checksum = ChecksumCrc32;

    #[inline(always)]
    fn new() -> Self {
        unsafe { crc32_new() }
    }

    #[inline(always)]
    fn process(&mut self, data: &Self::Input) {
        unsafe { crc32_process(ref_to_ptr(data), data.len() as u32, self as *mut Self) }
    }

    #[inline(always)]
    fn shift_reg(&mut self) -> &mut Self::Checksum {
        &mut self.buf
    }

    #[inline(always)]
    fn checksum(data: &Self::Input) -> Self::Checksum {
        unsafe { crc32_checksum(ref_to_ptr(data), data.len() as u32) }
    }
}

impl Crc32 {
    #[inline(always)]
    pub fn checksum_n_times(n: u32, data: &<Crc32 as Crc>::Input) {
        unsafe {
            crc32_checksum_n_times(n, ref_to_ptr(data), data.len() as u32);
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::crc::CHECK_DATA;

    use super::*;

    #[test]
    fn test_check() {
        // see https://reveng.sourceforge.io/crc-catalogue/17plus.htm#crc.cat-bits.32 for the check
        // value
        assert_eq!(Crc32::checksum(&CHECK_DATA), 0xCBF43926);
    }
}
