#[cfg(feature = "alloc")]
use alloc::fmt::{self, Debug, Display, Formatter};

use crate::{constants::crc_u8::*, lookup_table::LookUpTable};

#[allow(clippy::upper_case_acronyms)]
/// This struct can help you compute a CRC-8 (or CRC-x where **x** is equal or less than `8`) value.
pub struct CRCu8 {
    by_table:        bool,
    poly:            u8,
    lookup_table:    LookUpTable<u8>,
    sum:             u8,
    #[cfg(any(feature = "alloc", feature = "heapless"))]
    pub(crate) bits: u8,
    high_bit:        u8,
    mask:            u8,
    initial:         u8,
    final_xor:       u8,
    reflect:         bool,
}

#[cfg(feature = "alloc")]
impl Debug for CRCu8 {
    #[inline]
    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
        if self.by_table {
            debug_helper::impl_debug_for_struct!(CRCu8, f, self, let .lookup_table = self.lookup_table.as_ref(), (.sum, "0x{:02X}", self.sum), .bits, (.initial, "0x{:02X}", self.initial), (.final_xor, "0x{:02X}", self.final_xor), .reflect);
        } else {
            debug_helper::impl_debug_for_struct!(CRCu8, f, self, (.poly, "0x{:02X}", self.poly), (.sum, "0x{:02X}", self.sum), .bits, (.initial, "0x{:02X}", self.initial), (.final_xor, "0x{:02X}", self.final_xor), .reflect);
        }
    }
}

#[cfg(feature = "alloc")]
impl Display for CRCu8 {
    #[inline]
    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
        f.write_fmt(format_args!("0x{:01$X}", self.get_crc(), (self.bits as usize + 3) >> 2))
    }
}

impl CRCu8 {
    /// Create a `CRCu8` instance by providing the length of bits, expression, reflection, an initial value and a final xor value.
    pub fn create_crc(poly: u8, bits: u8, initial: u8, final_xor: u8, reflect: bool) -> CRCu8 {
        debug_assert!(bits <= 8 && bits > 0);

        if bits.is_multiple_of(8) {
            let lookup_table = if reflect {
                LookUpTable::Dynamic(Self::crc_reflect_table(poly))
            } else {
                LookUpTable::Dynamic(Self::crc_table(poly))
            };

            Self::create_crc_with_exists_lookup_table(
                lookup_table,
                bits,
                initial,
                final_xor,
                reflect,
            )
        } else {
            Self::create(
                false,
                LookUpTable::Static(&[0; 256]),
                poly,
                bits,
                initial,
                final_xor,
                reflect,
            )
        }
    }

    #[inline]
    pub(crate) fn create_crc_with_exists_lookup_table(
        lookup_table: LookUpTable<u8>,
        bits: u8,
        initial: u8,
        final_xor: u8,
        reflect: bool,
    ) -> CRCu8 {
        debug_assert!(bits.is_multiple_of(8));

        Self::create(true, lookup_table, 0, bits, initial, final_xor, reflect)
    }

    #[inline]
    fn create(
        by_table: bool,
        lookup_table: LookUpTable<u8>,
        mut poly: u8,
        bits: u8,
        initial: u8,
        final_xor: u8,
        reflect: bool,
    ) -> CRCu8 {
        let high_bit = 1 << (bits - 1);
        let mask = ((high_bit - 1) << 1) | 1;

        let sum = if reflect { Self::reflect_function(high_bit, initial) } else { initial };

        if !by_table && reflect {
            poly = Self::reflect_function(high_bit, poly);
        }

        CRCu8 {
            by_table,
            poly,
            lookup_table,
            sum,
            #[cfg(any(feature = "alloc", feature = "heapless"))]
            bits,
            high_bit,
            mask,
            initial,
            final_xor,
            reflect,
        }
    }

    #[inline]
    pub(crate) fn reflect_function(high_bit: u8, n: u8) -> u8 {
        let bits = high_bit.trailing_zeros() + 1;

        n.reverse_bits() >> (u8::BITS - bits)
    }

    #[inline]
    fn reflect_method(&self, n: u8) -> u8 {
        Self::reflect_function(self.high_bit, n)
    }

    /// Update the current CRC state with bytes.
    #[inline]
    pub fn update(&mut self, data: &[u8]) {
        if self.by_table {
            let table = &self.lookup_table;
            let mut sum = self.sum;

            let mut chunks = data.chunks_exact(8);

            for chunk in &mut chunks {
                sum = table[(sum ^ chunk[0]) as usize];
                sum = table[(sum ^ chunk[1]) as usize];
                sum = table[(sum ^ chunk[2]) as usize];
                sum = table[(sum ^ chunk[3]) as usize];
                sum = table[(sum ^ chunk[4]) as usize];
                sum = table[(sum ^ chunk[5]) as usize];
                sum = table[(sum ^ chunk[6]) as usize];
                sum = table[(sum ^ chunk[7]) as usize];
            }

            for n in chunks.remainder().iter().copied() {
                sum = table[(sum ^ n) as usize];
            }

            self.sum = sum;
        } else if self.reflect {
            for n in data.iter().copied() {
                let n = Self::reflect_function(0x80, n);

                let mut i = 0x80;

                while i != 0 {
                    let mut bit = self.sum & self.high_bit;

                    self.sum <<= 1;

                    if n & i != 0 {
                        bit ^= self.high_bit;
                    }

                    if bit != 0 {
                        self.sum ^= self.poly;
                    }

                    i >>= 1;
                }
            }
        } else {
            for n in data.iter().copied() {
                let mut i = 0x80;

                while i != 0 {
                    let mut bit = self.sum & self.high_bit;

                    self.sum <<= 1;

                    if n & i != 0 {
                        bit ^= self.high_bit;
                    }

                    if bit != 0 {
                        self.sum ^= self.poly;
                    }

                    i >>= 1;
                }
            }
        }
    }

    /// Digest some data.
    ///
    /// This is a compatibility wrapper around [`CRCu8::update`].
    #[inline]
    pub fn digest<T: ?Sized + AsRef<[u8]>>(&mut self, data: &T) {
        self.update(data.as_ref());
    }

    /// Reset the sum.
    #[inline]
    pub fn reset(&mut self) {
        self.sum = if self.reflect {
            Self::reflect_function(self.high_bit, self.initial)
        } else {
            self.initial
        };
    }

    /// Get the current CRC value (it always returns a `u8` value). You can continue calling `update` or `digest` even after getting a CRC value.
    #[inline]
    pub fn get_crc(&self) -> u8 {
        if self.by_table || !self.reflect {
            (self.sum ^ self.final_xor) & self.mask
        } else {
            (self.reflect_method(self.sum) ^ self.final_xor) & self.mask
        }
    }

    fn crc_reflect_table(poly_rev: u8) -> [u8; 256] {
        let mut lookup_table = [0u8; 256];

        for (i, e) in lookup_table.iter_mut().enumerate() {
            let mut v = i as u8;

            #[allow(clippy::branches_sharing_code)]
            for _ in 0..8u8 {
                if v & 1 != 0 {
                    v >>= 1;
                    v ^= poly_rev;
                } else {
                    v >>= 1;
                }
            }

            *e = v;
        }

        lookup_table
    }

    fn crc_table(poly: u8) -> [u8; 256] {
        let mut lookup_table = [0u8; 256];

        for (i, e) in lookup_table.iter_mut().enumerate() {
            let mut v = i as u8;

            #[allow(clippy::branches_sharing_code)]
            for _ in 0..8 {
                if v & 0x80 == 0 {
                    v <<= 1;
                } else {
                    v <<= 1;
                    v ^= poly;
                }
            }

            *e = v;
        }

        lookup_table
    }
}

impl CRCu8 {
    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x4|0x3|0x0|false|0x7|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc3gsm();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x4\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc3gsm() -> CRCu8 {
        Self::create_crc(0x03, 3, 0x00, 0x07, false)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x7|0x3 (rev: 0xC)|0x0|true|0x0|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc4itu();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x7\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc4itu() -> CRCu8 {
        Self::create_crc(0x0C, 4, 0x00, 0x00, true)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0xB|0x3|0xF|false|0xF|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc4interlaken();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0xB\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc4interlaken() -> CRCu8 {
        Self::create_crc(0x03, 4, 0x0F, 0x0F, false)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x00|0x09|0x09|false|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc5epc();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x00\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc5epc() -> CRCu8 {
        Self::create_crc(0x09, 5, 0x09, 0x00, false)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x07|0x15 (rev: 0x15)|0x00|true|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc5itu();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x07\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc5itu() -> CRCu8 {
        Self::create_crc(0x15, 5, 0x00, 0x00, true)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x19|0x05 (rev: 0x14)|0x1F|true|0x1F|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc5usb();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x19\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc5usb() -> CRCu8 {
        Self::create_crc(0x14, 5, 0x1F, 0x1F, true)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x0D|0x27|0x3F|false|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc6cdma2000_a();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x0D\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc6cdma2000_a() -> CRCu8 {
        Self::create_crc(0x27, 6, 0x3F, 0x00, false)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x3B|0x07|0x3F|false|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc6cdma2000_b();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x3B\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc6cdma2000_b() -> CRCu8 {
        Self::create_crc(0x07, 6, 0x3F, 0x00, false)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x26|0x19 (rev: 0x26)|0x00|true|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc6darc();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x26\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc6darc() -> CRCu8 {
        Self::create_crc(0x26, 6, 0x00, 0x00, true)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x13|0x2F|0x00|false|0x3F|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc6gsm();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x13\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc6gsm() -> CRCu8 {
        Self::create_crc(0x2F, 6, 0x00, 0x3F, false)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x06|0x03 (rev: 0x30)|0x00|true|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc6itu();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x06\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc6itu() -> CRCu8 {
        Self::create_crc(0x30, 6, 0x00, 0x00, true)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x75|0x09|0x00|false|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc7();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x75\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc7() -> CRCu8 {
        Self::create_crc(0x09, 7, 0x00, 0x00, false)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x61|0x45|0x00|false|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc7umts();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x61\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc7umts() -> CRCu8 {
        Self::create_crc(0x45, 7, 0x00, 0x00, false)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0xF4|0x07|0x00|false|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc8();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0xF4\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc8() -> CRCu8 {
        // Self::create_crc(0x07, 8, 0x00, 0x00, false)

        let lookup_table = LookUpTable::Static(&NO_REF_8_07);
        Self::create_crc_with_exists_lookup_table(lookup_table, 8, 0x00, 0x00, false)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0xDA|0x9B|0xFF|false|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc8cdma2000();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0xDA\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc8cdma2000() -> CRCu8 {
        // Self::create_crc(0x9B, 8, 0xFF, 0x00, false)

        let lookup_table = LookUpTable::Static(&NO_REF_8_9B);
        Self::create_crc_with_exists_lookup_table(lookup_table, 8, 0xFF, 0x00, false)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x15|0x39 (rev: 0x9C)|0x00|true|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc8darc();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x15\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc8darc() -> CRCu8 {
        // Self::create_crc(0x9C, 8, 0x00, 0x00, true)

        let lookup_table = LookUpTable::Static(&REF_8_9C);
        Self::create_crc_with_exists_lookup_table(lookup_table, 8, 0x00, 0x00, true)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0xBC|0xD5|0x00|false|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc8dvb_s2();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0xBC\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc8dvb_s2() -> CRCu8 {
        // Self::create_crc(0xD5, 8, 0x00, 0x00, false)

        let lookup_table = LookUpTable::Static(&NO_REF_8_D5);
        Self::create_crc_with_exists_lookup_table(lookup_table, 8, 0x00, 0x00, false)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x97|0x1D (rev: 0xB8)|0xFF|true|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc8ebu();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x97\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc8ebu() -> CRCu8 {
        // Self::create_crc(0xB8, 8, 0xFF, 0x00, true)

        let lookup_table = LookUpTable::Static(&REF_8_B8);
        Self::create_crc_with_exists_lookup_table(lookup_table, 8, 0xFF, 0x00, true)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x7E|0x1D|0xFD|false|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc8icode();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x7E\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc8icode() -> CRCu8 {
        // Self::create_crc(0x1D, 8, 0xFD, 0x00, false)

        let lookup_table = LookUpTable::Static(&NO_REF_8_1D);
        Self::create_crc_with_exists_lookup_table(lookup_table, 8, 0xFD, 0x00, false)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0xA1|0x07|0x00|false|0x55|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc8itu();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0xA1\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc8itu() -> CRCu8 {
        // Self::create_crc(0x07, 8, 0x00, 0x55, false)

        let lookup_table = LookUpTable::Static(&NO_REF_8_07);
        Self::create_crc_with_exists_lookup_table(lookup_table, 8, 0x00, 0x55, false)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0xA1|0x31 (rev: 0x8C)|0x00|true|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc8maxim();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0xA1\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc8maxim() -> CRCu8 {
        // Self::create_crc(0x8C, 8, 0x00, 0x00, true)

        let lookup_table = LookUpTable::Static(&REF_8_8C);
        Self::create_crc_with_exists_lookup_table(lookup_table, 8, 0x00, 0x00, true)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0xD0|0x07 (rev: 0xE0)|0xFF|true|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc8rohc();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0xD0\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc8rohc() -> CRCu8 {
        // Self::create_crc(0xE0, 8, 0xFF, 0x00, true)

        let lookup_table = LookUpTable::Static(&REF_8_E0);
        Self::create_crc_with_exists_lookup_table(lookup_table, 8, 0xFF, 0x00, true)
    }

    /// |Check|Poly|Init|Ref|XorOut|
    /// |---|---|---|---|---|
    /// |0x25|0x9B (rev: 0xD9)|0x00|true|0x00|
    ///
    /// ```
    /// # use crc_any::CRCu8;
    /// let mut crc = CRCu8::crc8wcdma();
    /// crc.digest(b"123456789");
    #[cfg_attr(feature = "alloc", doc = "assert_eq!(\"0x25\", &crc.to_string());")]
    /// ```
    #[inline]
    pub fn crc8wcdma() -> CRCu8 {
        // Self::create_crc(0xD9, 8, 0x00, 0x00, true)

        let lookup_table = LookUpTable::Static(&REF_8_D9);
        Self::create_crc_with_exists_lookup_table(lookup_table, 8, 0x00, 0x00, true)
    }
}

#[cfg(all(feature = "development", test))]
mod tests {
    use alloc::{fmt::Write, string::String};

    use super::CRCu8;

    #[test]
    fn print_lookup_table() {
        let crc = CRCu8::crc4interlaken();

        let mut s = String::new();

        for n in crc.lookup_table.iter().take(255) {
            s.write_fmt(format_args!("{}u8, ", n)).unwrap();
        }

        s.write_fmt(format_args!("{}u8", crc.lookup_table[255])).unwrap();

        println!("let lookup_table = [{}];", s);
    }
}
