use super::Word;
#[cfg(feature="simd")]
use core::{
    mem,
    simd::Simd,
};
const SHIFT_0: u32 = 32;
const SHIFT_1: u32 = 24;
const SHIFT_2: u32 = 16;
const SHIFT_3: u32 = 63;
#[cfg(any(feature="simd", test))]
#[doc(cfg(any(feature="simd", test)))]
const PERMUTATION_LEN: usize = 16;
const PERMUTATION_LOW_BITS: Word = 0x_0000_0000_ffff_ffff;
#[cfg(not(feature="simd"))]
#[doc(cfg(not(feature="simd")))]
pub (super) fn permutation(array: &mut [Word]) {
    const fn mul(a: Word, b: Word) -> Word {
        ((a & PERMUTATION_LOW_BITS) * (b & PERMUTATION_LOW_BITS)) << 1
    }
    #[cfg(test)]
    assert_eq!(array.len(), PERMUTATION_LEN);
    macro_rules! g { ($a: literal, $b: literal, $c: literal, $d: literal) => {
        #[cfg(test)]
        assert!($a < PERMUTATION_LEN && $b < PERMUTATION_LEN && $c < PERMUTATION_LEN && $d < PERMUTATION_LEN);
        unsafe {
            *array.get_unchecked_mut($a) += array.get_unchecked($b) + mul(*array.get_unchecked($a), *array.get_unchecked($b));
            *array.get_unchecked_mut($d) = (array.get_unchecked($d) ^ array.get_unchecked($a)).rotate_right(SHIFT_0);
            *array.get_unchecked_mut($c) += array.get_unchecked($d) + mul(*array.get_unchecked($c), *array.get_unchecked($d));
            *array.get_unchecked_mut($b) = (array.get_unchecked($b) ^ array.get_unchecked($c)).rotate_right(SHIFT_1);
            *array.get_unchecked_mut($a) += array.get_unchecked($b) + mul(*array.get_unchecked($a), *array.get_unchecked($b));
            *array.get_unchecked_mut($d) = (array.get_unchecked($d) ^ array.get_unchecked($a)).rotate_right(SHIFT_2);
            *array.get_unchecked_mut($c) += array.get_unchecked($d) + mul(*array.get_unchecked($c), *array.get_unchecked($d));
            *array.get_unchecked_mut($b) = (array.get_unchecked($b) ^ array.get_unchecked($c)).rotate_right(SHIFT_3);
        }
    }}
    g!(0, 4,  8, 12);
    g!(1, 5,  9, 13);
    g!(2, 6, 10, 14);
    g!(3, 7, 11, 15);
    g!(0, 5, 10, 15);
    g!(1, 6, 11, 12);
    g!(2, 7,  8, 13);
    g!(3, 4,  9, 14);
}
#[cfg(feature="simd")]
#[doc(cfg(feature="simd"))]
pub (super) fn permutation(array: &mut [Word]) {
    const LANES: usize = 4;
    const PERMUTATION_LOW_BITS: Simd::<Word, LANES> = Simd::from_array([self::PERMUTATION_LOW_BITS; LANES]);
    #[cfg(test)]
    assert_eq!(array.len(), PERMUTATION_LEN);
    let [mut a, mut b, mut c, mut d] = [
        Simd::from_slice(&array[..4]), Simd::from_slice(&array[4..8]), Simd::from_slice(&array[8..12]), Simd::from_slice(&array[12..]),
    ];
    let mul = |a, b| ((a & PERMUTATION_LOW_BITS) * (b & PERMUTATION_LOW_BITS)) << Simd::from_array([1; LANES]);
    let rotate = |v, shift|
        (v << Simd::from_array([Word::from(Word::BITS - shift); LANES])) | (v >> Simd::from_array([Word::from(shift); LANES]));
    macro_rules! g { () => {
        a += b + mul(a, b);
        d = rotate(d ^ a, SHIFT_0);
        c += d + mul(c, d);
        b = rotate(b ^ c, SHIFT_1);
        a += b + mul(a, b);
        d = rotate(d ^ a, SHIFT_2);
        c += d + mul(c, d);
        b = rotate(b ^ c, SHIFT_3);
    }}
    g!();
    b = b.rotate_elements_left::<1>();  c = c.rotate_elements_left::<2>();  d = d.rotate_elements_right::<1>(); g!();
    b = b.rotate_elements_right::<1>();
    c = c.rotate_elements_right::<2>();
    d = d.rotate_elements_left::<1>();
    array.copy_from_slice(unsafe {
        &mem::transmute::<_, [Word; PERMUTATION_LEN]>([a.to_array(), b.to_array(), c.to_array(), d.to_array()])[..]
    });
}