//! GIFT-256 linear diffusion layers
//! P1 and P2 are standalone functions, DA and DB are inlined in encrypt

use crate::util::nibble_half_swap;

/// P1: byte-ROT2 permutation (even rounds)
/// Corresponds to gift256_linear_P1_byteROT2 @ 0x800172b2
///
/// Core op: rot = ROR(x, 2) & 0xC0C0C0C0 | ROR(x, 10) & 0x3F3F3F3F
/// Processing order: s[6], s[7], s[5], s[4], s[1], s[2], s[0], s[3]
/// Extra: s[7]'s tmp value (t7) is XORed into s[0], s[4], s[3], s[1]
pub fn linear_p1(s: &mut [u32; 8]) {
    let byte_rot2 = |x: u32| -> u32 {
        (x.rotate_left(22) & 0x3F3F3F3F) | (x.rotate_left(30) & 0xC0C0C0C0)
    };

    let r6 = byte_rot2(s[6]);
    let r7 = byte_rot2(s[7]);
    let t6 = s[6] ^ r6;
    let t7 = s[7] ^ r7;
    s[7] = r7 ^ t6 ^ nibble_half_swap(t7);

    let r5 = byte_rot2(s[5]);
    let t5 = s[5] ^ r5;
    s[6] = r6 ^ t5 ^ nibble_half_swap(t6);

    let r4 = byte_rot2(s[4]);
    let t4 = s[4] ^ r4;
    s[5] = r5 ^ t4 ^ nibble_half_swap(t5);

    let r1 = byte_rot2(s[1]);
    let t1 = r1 ^ s[1];
    let r2 = byte_rot2(s[2]);
    let t2 = s[2] ^ r2;
    s[2] = r2 ^ t1 ^ nibble_half_swap(t2);

    let r0 = byte_rot2(s[0]);
    let t0 = r0 ^ s[0];
    s[0] = nibble_half_swap(t0) ^ r0 ^ t7; // NOTE: XOR with t7

    let r3 = byte_rot2(s[3]);
    let t3 = s[3] ^ r3;
    s[4] = r4 ^ t3 ^ nibble_half_swap(t4) ^ t7;
    s[3] = t2 ^ nibble_half_swap(t3) ^ r3 ^ t7;
    s[1] = t0 ^ nibble_half_swap(t1) ^ r1 ^ t7;
}

/// P2: byte-ROT6 permutation (odd rounds)
/// Corresponds to gift256_linear_P2_byteROT6 @ 0x80006e1f
/// Identical structure to P1, only rotation amount changes:
///   rot = ROR(x, 6) & 0xFCFCFCFC | ROR(x, 14) & 0x03030303
pub fn linear_p2(s: &mut [u32; 8]) {
    let byte_rot6 = |x: u32| -> u32 {
        (x.rotate_left(18) & 0x03030303) | (x.rotate_left(26) & 0xFCFCFCFC)
    };

    let r6 = byte_rot6(s[6]);
    let r7 = byte_rot6(s[7]);
    let t6 = s[6] ^ r6;
    let t7 = s[7] ^ r7;
    s[7] = r7 ^ t6 ^ nibble_half_swap(t7);

    let r5 = byte_rot6(s[5]);
    let t5 = s[5] ^ r5;
    s[6] = r6 ^ t5 ^ nibble_half_swap(t6);

    let r4 = byte_rot6(s[4]);
    let t4 = s[4] ^ r4;
    s[5] = r5 ^ t4 ^ nibble_half_swap(t5);

    let r1 = byte_rot6(s[1]);
    let t1 = r1 ^ s[1];
    let r2 = byte_rot6(s[2]);
    let t2 = s[2] ^ r2;
    s[2] = r2 ^ t1 ^ nibble_half_swap(t2);

    let r0 = byte_rot6(s[0]);
    let t0 = r0 ^ s[0];
    s[0] = nibble_half_swap(t0) ^ r0 ^ t7;

    let r3 = byte_rot6(s[3]);
    let t3 = s[3] ^ r3;
    s[4] = r4 ^ t3 ^ nibble_half_swap(t4) ^ t7;
    s[3] = t2 ^ nibble_half_swap(t3) ^ r3 ^ t7;
    s[1] = t0 ^ nibble_half_swap(t1) ^ r1 ^ t7;
}

/// DA: nibble-ROT4/12/20/28 diffusion (inlined in encrypt)
/// Corresponds to gift256_encrypt_fixsliced State 2 first part
///
/// Core op:
///   rotated = (x << 0x14 | x >> 0xc) & 0x0F0F0F0F | (x << 0x1c | x >> 4) & 0xF0F0F0F0
///   tmp = x ^ rotated
///   new = rotated ^ key ^ SWAP16(tmp) ^ cross_xor_pattern
pub fn diffusion_a(s: &mut [u32; 8], rk: &[u32; 8]) {
    let nibble_rot = |x: u32| -> u32 {
        (x.rotate_left(20) & 0x0F0F0F0F) | (x.rotate_left(28) & 0xF0F0F0F0)
    };
    let swap16 = |x: u32| -> u32 { x.rotate_left(16) };

    let mut r = [0u32; 8];
    let mut t = [0u32; 8];
    for i in 0..8 {
        r[i] = nibble_rot(s[i]);
        t[i] = s[i] ^ r[i];
    }

    // Cross-XOR pattern (derived from decompilation)
    s[0] = t[7] ^ r[0] ^ rk[0] ^ swap16(t[0]);
    s[2] = r[2] ^ rk[2] ^ swap16(t[2]) ^ t[1];
    s[5] = r[5] ^ rk[5] ^ swap16(t[5]) ^ t[4];
    s[1] = rk[1] ^ swap16(t[1]) ^ t[0] ^ r[1] ^ t[7];
    s[3] = r[3] ^ rk[3] ^ swap16(t[3]) ^ t[2] ^ t[7];
    s[4] = rk[4] ^ swap16(t[4]) ^ t[3] ^ r[4] ^ t[7];
    s[6] = r[6] ^ rk[6] ^ swap16(t[6]) ^ t[5];
    s[7] = rk[7] ^ swap16(t[7]) ^ t[6] ^ r[7];
}

/// DB: byte-ROT8 diffusion (inlined in encrypt)
/// Corresponds to gift256_encrypt_fixsliced State 2 last part
///
/// Core op:
///   rotated = x << 0x18 | x >> 8  (= ROR(x, 8))
///   tmp = rotated ^ x
///   new = rotated ^ key ^ SWAP16(tmp) ^ cross_xor_pattern
pub fn diffusion_b(s: &mut [u32; 8], rk: &[u32; 8]) {
    let swap16 = |x: u32| -> u32 { x.rotate_left(16) };

    let mut r = [0u32; 8];
    let mut t = [0u32; 8];
    for i in 0..8 {
        r[i] = s[i].rotate_right(8);
        t[i] = r[i] ^ s[i];
    }

    // Cross-XOR pattern (same structure as DA but with byte-ROT8)
    s[0] = r[0] ^ rk[0] ^ swap16(t[0]) ^ t[7];
    s[2] = r[2] ^ rk[2] ^ swap16(t[2]) ^ t[1];
    s[1] = rk[1] ^ swap16(t[1]) ^ t[0] ^ r[1] ^ t[7];
    s[3] = r[3] ^ rk[3] ^ swap16(t[3]) ^ t[2] ^ t[7];
    s[4] = rk[4] ^ swap16(t[4]) ^ t[3] ^ r[4] ^ t[7];
    s[5] = r[5] ^ rk[5] ^ swap16(t[5]) ^ t[4];
    s[6] = r[6] ^ rk[6] ^ swap16(t[6]) ^ t[5];
    s[7] = rk[7] ^ swap16(t[7]) ^ t[6] ^ r[7];
}
