//! AUT64 block cipher implementation
//!
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/aut64.c` and `aut64.h`.
//! Encrypt/decrypt, pack/unpack, tables (table_ln, table_un, table_offset, table_sub), and
//! round logic match the reference. Optional key validation matches `aut64_validate_key` when
//! `AUT64_ENABLE_VALIDATIONS` is set in the reference.
//!
//! AUT64 algorithm: 12 rounds, 8-byte block/key size.
//! See: https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_garcia.pdf

pub const AUT64_NUM_ROUNDS: usize = 12;
pub const AUT64_BLOCK_SIZE: usize = 8;
pub const AUT64_KEY_SIZE: usize = 8;
pub const AUT64_PBOX_SIZE: usize = 8;
pub const AUT64_SBOX_SIZE: usize = 16;
/// Packed key size in bytes (aut64.h: AUT64_PACKED_KEY_SIZE)
#[allow(dead_code)]
pub const AUT64_KEY_STRUCT_PACKED_SIZE: usize = 16;

/// AUT64 key structure (aut64.h: struct aut64_key)
#[derive(Debug, Clone)]
pub struct Aut64Key {
    pub index: u8,
    pub key: [u8; AUT64_KEY_SIZE],
    pub pbox: [u8; AUT64_PBOX_SIZE],
    pub sbox: [u8; AUT64_SBOX_SIZE],
}

impl Default for Aut64Key {
    fn default() -> Self {
        Self {
            index: 0,
            key: [0u8; AUT64_KEY_SIZE],
            pbox: [0u8; AUT64_PBOX_SIZE],
            sbox: [0u8; AUT64_SBOX_SIZE],
        }
    }
}

/// Round-dependent upper-nibble lookup table
static TABLE_LN: [[u8; 8]; AUT64_NUM_ROUNDS] = [
    [0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3], // Round 0
    [0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2], // Round 1
    [0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1], // Round 2
    [0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0], // Round 3
    [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7], // Round 4
    [0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6], // Round 5
    [0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5], // Round 6
    [0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4], // Round 7
    [0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2], // Round 8
    [0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3], // Round 9
    [0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0], // Round 10
    [0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1], // Round 11
];

/// Round-dependent lower-nibble lookup table
static TABLE_UN: [[u8; 8]; AUT64_NUM_ROUNDS] = [
    [0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6], // Round 0
    [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7], // Round 1
    [0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4], // Round 2
    [0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5], // Round 3
    [0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2], // Round 4
    [0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3], // Round 5
    [0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0], // Round 6
    [0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1], // Round 7
    [0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4], // Round 8
    [0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5], // Round 9
    [0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6], // Round 10
    [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7], // Round 11
];

/// GF(2^4) multiplication table (nibble offset table)
#[rustfmt::skip]
static TABLE_OFFSET: [u8; 256] = [
    // 0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
    0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // 0
    0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, // 1
    0x0, 0x2, 0x4, 0x6, 0x8, 0xA, 0xC, 0xE, 0x3, 0x1, 0x7, 0x5, 0xB, 0x9, 0xF, 0xD, // 2
    0x0, 0x3, 0x6, 0x5, 0xC, 0xF, 0xA, 0x9, 0xB, 0x8, 0xD, 0xE, 0x7, 0x4, 0x1, 0x2, // 3
    0x0, 0x4, 0x8, 0xC, 0x3, 0x7, 0xB, 0xF, 0x6, 0x2, 0xE, 0xA, 0x5, 0x1, 0xD, 0x9, // 4
    0x0, 0x5, 0xA, 0xF, 0x7, 0x2, 0xD, 0x8, 0xE, 0xB, 0x4, 0x1, 0x9, 0xC, 0x3, 0x6, // 5
    0x0, 0x6, 0xC, 0xA, 0xB, 0xD, 0x7, 0x1, 0x5, 0x3, 0x9, 0xF, 0xE, 0x8, 0x2, 0x4, // 6
    0x0, 0x7, 0xE, 0x9, 0xF, 0x8, 0x1, 0x6, 0xD, 0xA, 0x3, 0x4, 0x2, 0x5, 0xC, 0xB, // 7
    0x0, 0x8, 0x3, 0xB, 0x6, 0xE, 0x5, 0xD, 0xC, 0x4, 0xF, 0x7, 0xA, 0x2, 0x9, 0x1, // 8
    0x0, 0x9, 0x1, 0x8, 0x2, 0xB, 0x3, 0xA, 0x4, 0xD, 0x5, 0xC, 0x6, 0xF, 0x7, 0xE, // 9
    0x0, 0xA, 0x7, 0xD, 0xE, 0x4, 0x9, 0x3, 0xF, 0x5, 0x8, 0x2, 0x1, 0xB, 0x6, 0xC, // A
    0x0, 0xB, 0x5, 0xE, 0xA, 0x1, 0xF, 0x4, 0x7, 0xC, 0x2, 0x9, 0xD, 0x6, 0x8, 0x3, // B
    0x0, 0xC, 0xB, 0x7, 0x5, 0x9, 0xE, 0x2, 0xA, 0x6, 0x1, 0xD, 0xF, 0x3, 0x4, 0x8, // C
    0x0, 0xD, 0x9, 0x4, 0x1, 0xC, 0x8, 0x5, 0x2, 0xF, 0xB, 0x6, 0x3, 0xE, 0xA, 0x7, // D
    0x0, 0xE, 0xF, 0x1, 0xD, 0x3, 0x2, 0xC, 0x9, 0x7, 0x6, 0x8, 0x4, 0xA, 0xB, 0x5, // E
    0x0, 0xF, 0xD, 0x2, 0x9, 0x6, 0x4, 0xB, 0x1, 0xE, 0xC, 0x3, 0x8, 0x7, 0x5, 0xA, // F
];

/// S-box substitution table
static TABLE_SUB: [u8; 16] = [
    0x0, 0x1, 0x9, 0xE, 0xD, 0xB, 0x7, 0x6,
    0xF, 0x2, 0xC, 0x5, 0xA, 0x4, 0x3, 0x8,
];

/// Key nibble operation: apply key-dependent GF offset
fn key_nibble(key: &Aut64Key, nibble: u8, table: &[u8; 8], iteration: usize) -> u8 {
    let key_value = key.key[table[iteration] as usize];
    let offset = ((key_value as usize) << 4) | (nibble as usize);
    TABLE_OFFSET[offset]
}

/// Compute round key from state
fn round_key(key: &Aut64Key, state: &[u8], round_n: usize) -> u8 {
    let mut result_hi: u8 = 0;
    let mut result_lo: u8 = 0;

    for i in 0..(AUT64_BLOCK_SIZE - 1) {
        result_hi ^= key_nibble(key, state[i] >> 4, &TABLE_UN[round_n], i);
        result_lo ^= key_nibble(key, state[i] & 0x0F, &TABLE_LN[round_n], i);
    }

    (result_hi << 4) | result_lo
}

/// Final byte nibble for key schedule
fn final_byte_nibble(key: &Aut64Key, table: &[u8; 8]) -> u8 {
    let key_value = key.key[table[AUT64_BLOCK_SIZE - 1] as usize];
    TABLE_SUB[key_value as usize] << 4
}

/// Encrypt final byte nibble (inverse S-box lookup through offset table)
fn encrypt_final_byte_nibble(key: &Aut64Key, nibble: u8, table: &[u8; 8]) -> u8 {
    let offset = final_byte_nibble(key, table) as usize;

    for i in 0u8..16 {
        if TABLE_OFFSET[offset + i as usize] == nibble {
            return i;
        }
    }
    0 // Should not reach here for valid inputs
}

/// Encrypt compress: compute encrypted output byte for a round
fn encrypt_compress(key: &Aut64Key, state: &[u8], round_n: usize) -> u8 {
    let round_k = round_key(key, state, round_n);
    let mut result_hi = round_k >> 4;
    let mut result_lo = round_k & 0x0F;

    result_hi ^= encrypt_final_byte_nibble(key, state[AUT64_BLOCK_SIZE - 1] >> 4, &TABLE_UN[round_n]);
    result_lo ^= encrypt_final_byte_nibble(key, state[AUT64_BLOCK_SIZE - 1] & 0x0F, &TABLE_LN[round_n]);

    (result_hi << 4) | result_lo
}

/// Decrypt final byte nibble (forward S-box through offset table)
fn decrypt_final_byte_nibble(key: &Aut64Key, nibble: u8, table: &[u8; 8], result: u8) -> u8 {
    let offset = final_byte_nibble(key, table) as usize;
    TABLE_OFFSET[(result ^ nibble) as usize + offset]
}

/// Decrypt compress: compute decrypted output byte for a round
fn decrypt_compress(key: &Aut64Key, state: &[u8], round_n: usize) -> u8 {
    let round_k = round_key(key, state, round_n);
    let result_hi = round_k >> 4;
    let result_lo = round_k & 0x0F;

    let hi = decrypt_final_byte_nibble(
        key,
        state[AUT64_BLOCK_SIZE - 1] >> 4,
        &TABLE_UN[round_n],
        result_hi,
    );
    let lo = decrypt_final_byte_nibble(
        key,
        state[AUT64_BLOCK_SIZE - 1] & 0x0F,
        &TABLE_LN[round_n],
        result_lo,
    );

    (hi << 4) | lo
}

/// S-box substitution on a full byte (applies S-box to each nibble independently)
fn substitute(key: &Aut64Key, byte: u8) -> u8 {
    (key.sbox[(byte >> 4) as usize] << 4) | key.sbox[(byte & 0x0F) as usize]
}

/// Byte-level permutation using P-box
fn permute_bytes(key: &Aut64Key, state: &mut [u8]) {
    let mut result = [0u8; AUT64_PBOX_SIZE];
    for i in 0..AUT64_PBOX_SIZE {
        result[key.pbox[i] as usize] = state[i];
    }
    state[..AUT64_PBOX_SIZE].copy_from_slice(&result);
}

/// Bit-level permutation using P-box
fn permute_bits(key: &Aut64Key, byte: u8) -> u8 {
    let mut result: u8 = 0;
    for i in 0..8 {
        if byte & (1 << i) != 0 {
            result |= 1 << key.pbox[i];
        }
    }
    result
}

/// Compute inverse permutation box (reference: reverse_box). Used for encrypt key.
fn reverse_box(box_in: &[u8], len: usize) -> Vec<u8> {
    let mut reversed = vec![0u8; len];
    for i in 0..len {
        for j in 0..len {
            if box_in[j] == i as u8 {
                reversed[i] = j as u8;
                break;
            }
        }
    }
    reversed
}

/// Validate key: key nibbles in 0..16, pbox permutation of 0..7, sbox permutation of 0..15.
/// Matches `aut64_validate_key` when AUT64_ENABLE_VALIDATIONS is set in the reference.
#[allow(dead_code)]
pub fn aut64_validate_key(key: &Aut64Key) -> bool {
    for i in 0..AUT64_KEY_SIZE {
        if key.key[i] >= AUT64_SBOX_SIZE as u8 {
            return false;
        }
    }
    if !box_is_permutation(&key.pbox, AUT64_PBOX_SIZE) {
        return false;
    }
    if !box_is_permutation(&key.sbox, AUT64_SBOX_SIZE) {
        return false;
    }
    true
}

fn box_is_permutation(box_in: &[u8], len: usize) -> bool {
    if box_in.len() < len {
        return false;
    }
    let mut seen = vec![false; len];
    for &v in &box_in[..len] {
        let v = v as usize;
        if v >= len || seen[v] {
            return false;
        }
        seen[v] = true;
    }
    seen.iter().all(|&b| b)
}

/// Encrypt one 8-byte block in place. Matches `aut64_encrypt` in reference.
/// Message buffer must be at least AUT64_BLOCK_SIZE bytes.
pub fn aut64_encrypt(key: &Aut64Key, message: &mut [u8]) {
    // Create reverse key for encryption
    let mut reverse_key = key.clone();
    let rev_pbox = reverse_box(&key.pbox, AUT64_PBOX_SIZE);
    let rev_sbox = reverse_box(&key.sbox, AUT64_SBOX_SIZE);
    reverse_key.pbox.copy_from_slice(&rev_pbox);
    reverse_key.sbox.copy_from_slice(&rev_sbox);

    for i in 0..AUT64_NUM_ROUNDS {
        permute_bytes(&reverse_key, message);
        message[7] = encrypt_compress(&reverse_key, message, i);
        message[7] = substitute(&reverse_key, message[7]);
        message[7] = permute_bits(&reverse_key, message[7]);
        message[7] = substitute(&reverse_key, message[7]);
    }
}

/// Decrypt one 8-byte block in place. Matches `aut64_decrypt` in reference.
/// Message buffer must be at least AUT64_BLOCK_SIZE bytes.
pub fn aut64_decrypt(key: &Aut64Key, message: &mut [u8]) {
    for i in (0..AUT64_NUM_ROUNDS).rev() {
        message[7] = substitute(key, message[7]);
        message[7] = permute_bits(key, message[7]);
        message[7] = substitute(key, message[7]);
        message[7] = decrypt_compress(key, message, i);
        permute_bytes(key, message);
    }
}

/// Serialize key into 16-byte packed format. Matches `aut64_pack` (when AUT64_PACK_SUPPORT).
#[allow(dead_code)]
pub fn aut64_pack(src: &Aut64Key) -> [u8; AUT64_KEY_STRUCT_PACKED_SIZE] {
    let mut dest = [0u8; AUT64_KEY_STRUCT_PACKED_SIZE];
    dest[0] = src.index;

    for i in 0..(src.key.len() / 2) {
        dest[i + 1] = (src.key[i * 2] << 4) | src.key[i * 2 + 1];
    }

    let mut pbox: u32 = 0;
    for i in 0..src.pbox.len() {
        pbox = (pbox << 3) | src.pbox[i] as u32;
    }
    dest[5] = (pbox >> 16) as u8;
    dest[6] = ((pbox >> 8) & 0xFF) as u8;
    dest[7] = (pbox & 0xFF) as u8;

    for i in 0..(src.sbox.len() / 2) {
        dest[i + 8] = (src.sbox[i * 2] << 4) | src.sbox[i * 2 + 1];
    }

    dest
}

/// Deserialize 16-byte packed key into key structure. Matches `aut64_unpack` in reference.
#[allow(dead_code)]
pub fn aut64_unpack(src: &[u8]) -> Aut64Key {
    let mut dest = Aut64Key::default();
    dest.index = src[0];

    for i in 0..(dest.key.len() / 2) {
        dest.key[i * 2] = src[i + 1] >> 4;
        dest.key[i * 2 + 1] = src[i + 1] & 0xF;
    }

    let mut pbox: u32 = (u32::from(src[5]) << 16) | (u32::from(src[6]) << 8) | u32::from(src[7]);
    for i in (0..dest.pbox.len()).rev() {
        dest.pbox[i] = (pbox & 0x7) as u8;
        pbox >>= 3;
    }

    for i in 0..(dest.sbox.len() / 2) {
        dest.sbox[i * 2] = src[i + 8] >> 4;
        dest.sbox[i * 2 + 1] = src[i + 8] & 0xF;
    }

    dest
}
