//! Converted to Rust from the original C code generated by https://github.com/corsix/fast-crc32/
//! with the help of Claude.ai.
//!
//! MIT licensed.

#![cfg(target_arch = "x86_64")]

/// Converted to Rust from the original C code generated by https://github.com/corsix/fast-crc32/
/// with the help of Claude.ai using:
///
/// ./generate -i avx512_vpclmulqdq -p crc32c -a v3x2
///
/// Modified as necessary for this Rust implementation.
///
/// Uses AVX-512 VPCLMULQDQ instructions, so only available after Rust 1.89 (when AVX-512
/// stabilized)
#[rustversion::since(1.89)]
#[inline]
#[target_feature(enable = "avx512vl,vpclmulqdq")]
pub unsafe fn crc32_iscsi_avx512_vpclmulqdq_v3x2(
    mut crc0: u32,
    mut buf: *const u8,
    mut len: usize,
) -> u32 {
    use crate::fusion::x86::*;

    // Align to 8-byte boundary
    while len > 0 && (buf as usize & 7) != 0 {
        crc0 = _mm_crc32_u8(crc0, *buf);
        buf = buf.add(1);
        len -= 1;
    }

    // Align to 64-byte boundary (cache line)
    while (buf as usize & 56) != 0 && len >= 8 {
        crc0 = _mm_crc32_u64(crc0.into(), *(buf as *const u64)) as u32;
        buf = buf.add(8);
        len -= 8;
    }

    if len >= 384 {
        // First vector chunk - load three 512-bit vectors (192 bytes total)
        let mut x0 = _mm512_loadu_si512(buf as *const __m512i);
        let mut x1 = _mm512_loadu_si512(buf.add(64) as *const __m512i);
        let mut x2 = _mm512_loadu_si512(buf.add(128) as *const __m512i);

        // Create the multiplication constant vector
        // Pattern: [0xa87ab8a8, 0, 0xab7aff2a, 0] repeated across all 128-bit lanes
        let k_128 = _mm_setr_epi32(0xa87ab8a8u32 as i32, 0, 0xab7aff2au32 as i32, 0);
        let mut k = _mm512_broadcast_i32x4(k_128);

        // XOR the CRC into the first vector's low 32 bits
        let crc_vec = _mm512_castsi128_si512(_mm_cvtsi32_si128(crc0 as i32));
        x0 = _mm512_xor_si512(crc_vec, x0);

        // First round of polynomial multiplication
        let mut y0 = clmul_lo_avx512_vpclmulqdq(x0, k);
        x0 = clmul_hi_avx512_vpclmulqdq(x0, k);
        let mut y1 = clmul_lo_avx512_vpclmulqdq(x1, k);
        x1 = clmul_hi_avx512_vpclmulqdq(x1, k);
        let mut y2 = clmul_lo_avx512_vpclmulqdq(x2, k);
        x2 = clmul_hi_avx512_vpclmulqdq(x2, k);

        // XOR with next chunk of data using ternary logic (A XOR B XOR C)
        // 0x96 = A XOR B XOR C in ternary logic notation
        x0 = _mm512_ternarylogic_epi64(
            x0,
            y0,
            _mm512_loadu_si512(buf.add(192) as *const __m512i),
            0x96,
        );
        x1 = _mm512_ternarylogic_epi64(
            x1,
            y1,
            _mm512_loadu_si512(buf.add(256) as *const __m512i),
            0x96,
        );
        x2 = _mm512_ternarylogic_epi64(
            x2,
            y2,
            _mm512_loadu_si512(buf.add(320) as *const __m512i),
            0x96,
        );

        buf = buf.add(384);
        len -= 384;

        // Main loop - process 384 bytes at a time
        while len >= 384 {
            // First folding step
            y0 = clmul_lo_avx512_vpclmulqdq(x0, k);
            x0 = clmul_hi_avx512_vpclmulqdq(x0, k);
            y1 = clmul_lo_avx512_vpclmulqdq(x1, k);
            x1 = clmul_hi_avx512_vpclmulqdq(x1, k);
            y2 = clmul_lo_avx512_vpclmulqdq(x2, k);
            x2 = clmul_hi_avx512_vpclmulqdq(x2, k);

            x0 = _mm512_ternarylogic_epi64(x0, y0, _mm512_loadu_si512(buf as *const __m512i), 0x96);
            x1 = _mm512_ternarylogic_epi64(
                x1,
                y1,
                _mm512_loadu_si512(buf.add(64) as *const __m512i),
                0x96,
            );
            x2 = _mm512_ternarylogic_epi64(
                x2,
                y2,
                _mm512_loadu_si512(buf.add(128) as *const __m512i),
                0x96,
            );

            // Second folding step
            y0 = clmul_lo_avx512_vpclmulqdq(x0, k);
            x0 = clmul_hi_avx512_vpclmulqdq(x0, k);
            y1 = clmul_lo_avx512_vpclmulqdq(x1, k);
            x1 = clmul_hi_avx512_vpclmulqdq(x1, k);
            y2 = clmul_lo_avx512_vpclmulqdq(x2, k);
            x2 = clmul_hi_avx512_vpclmulqdq(x2, k);

            x0 = _mm512_ternarylogic_epi64(
                x0,
                y0,
                _mm512_loadu_si512(buf.add(192) as *const __m512i),
                0x96,
            );
            x1 = _mm512_ternarylogic_epi64(
                x1,
                y1,
                _mm512_loadu_si512(buf.add(256) as *const __m512i),
                0x96,
            );
            x2 = _mm512_ternarylogic_epi64(
                x2,
                y2,
                _mm512_loadu_si512(buf.add(320) as *const __m512i),
                0x96,
            );

            buf = buf.add(384);
            len -= 384;
        }

        // Reduce x0, x1, x2 to just x0
        let k_128 = _mm_setr_epi32(0x740eef02u32 as i32, 0, 0x9e4addf8u32 as i32, 0);
        k = _mm512_broadcast_i32x4(k_128);

        y0 = clmul_lo_avx512_vpclmulqdq(x0, k);
        x0 = clmul_hi_avx512_vpclmulqdq(x0, k);
        x0 = _mm512_ternarylogic_epi64(x0, y0, x1, 0x96);
        x1 = x2;

        y0 = clmul_lo_avx512_vpclmulqdq(x0, k);
        x0 = clmul_hi_avx512_vpclmulqdq(x0, k);
        x0 = _mm512_ternarylogic_epi64(x0, y0, x1, 0x96);

        // Reduce 512 bits to 128 bits
        // Multiple reduction constants for different parts of the 512-bit vector
        k = _mm512_setr_epi32(
            0x1c291d04u32 as i32,
            0,
            0xddc0152bu32 as i32,
            0, // Lane 0
            0x3da6d0cbu32 as i32,
            0,
            0xba4fc28eu32 as i32,
            0, // Lane 1
            0xf20c0dfeu32 as i32,
            0,
            0x493c7d27u32 as i32,
            0, // Lane 2
            0,
            0,
            0,
            0, // Lane 3 (unused)
        );

        y0 = clmul_lo_avx512_vpclmulqdq(x0, k);
        k = clmul_hi_avx512_vpclmulqdq(x0, k);
        y0 = _mm512_xor_si512(y0, k);

        // Extract 128-bit lanes and combine them
        let lane0 = _mm512_castsi512_si128(y0);
        let lane1 = _mm512_extracti32x4_epi32(y0, 1);
        let lane2 = _mm512_extracti32x4_epi32(y0, 2);
        let lane3 = _mm512_extracti32x4_epi32(x0, 3);

        // Combine all lanes using ternary logic
        let mut z0 = _mm_ternarylogic_epi64(lane0, lane1, lane2, 0x96);
        z0 = _mm_xor_si128(z0, lane3);

        // Reduce 128 bits to 32 bits using CRC32 instructions
        crc0 = _mm_crc32_u64(0, _mm_extract_epi64(z0, 0) as u64) as u32;
        crc0 = _mm_crc32_u64(crc0.into(), _mm_extract_epi64(z0, 1) as u64) as u32;
    }

    // Process remaining 8-byte chunks
    while len >= 8 {
        crc0 = _mm_crc32_u64(crc0.into(), *(buf as *const u64)) as u32;
        buf = buf.add(8);
        len -= 8;
    }

    // Process remaining bytes
    while len > 0 {
        crc0 = _mm_crc32_u8(crc0, *buf);
        buf = buf.add(1);
        len -= 1;
    }

    crc0
}
