#![feature(portable_simd)]
use core::{fmt, ops::RangeInclusive};
use proptest;
use test_helpers::{self, biteq, make_runner, prop_assert_biteq};

fn swizzle_dyn_scalar_ver<const N: usize>(values: [u8; N], idxs: [u8; N]) -> [u8; N] {
    let mut array = [0; N];
    for (i, k) in idxs.into_iter().enumerate() {
        if (k as usize) < N {
            array[i] = values[k as usize];
        };
    }
    array
}

test_helpers::test_lanes! {
    fn swizzle_dyn<const N: usize>() {
        match_simd_with_fallback(
            &core_simd::simd::Simd::<u8, N>::swizzle_dyn,
            &swizzle_dyn_scalar_ver,
            &|_, _| true,
        );
    }
}

fn match_simd_with_fallback<Scalar, ScalarResult, Vector, VectorResult, const N: usize>(
    fv: &dyn Fn(Vector, Vector) -> VectorResult,
    fs: &dyn Fn([Scalar; N], [Scalar; N]) -> [ScalarResult; N],
    check: &dyn Fn([Scalar; N], [Scalar; N]) -> bool,
) where
    Scalar: Copy + fmt::Debug + SwizzleStrategy,
    ScalarResult: Copy + biteq::BitEq + fmt::Debug + SwizzleStrategy,
    Vector: Into<[Scalar; N]> + From<[Scalar; N]> + Copy,
    VectorResult: Into<[ScalarResult; N]> + From<[ScalarResult; N]> + Copy,
{
    test_swizzles_2(&|x: [Scalar; N], y: [Scalar; N]| {
        proptest::prop_assume!(check(x, y));
        let result_v: [ScalarResult; N] = fv(x.into(), y.into()).into();
        let result_s: [ScalarResult; N] = fs(x, y);
        crate::prop_assert_biteq!(result_v, result_s);
        Ok(())
    });
}

fn test_swizzles_2<A: fmt::Debug + SwizzleStrategy, B: fmt::Debug + SwizzleStrategy>(
    f: &dyn Fn(A, B) -> proptest::test_runner::TestCaseResult,
) {
    let mut runner = make_runner();
    runner
        .run(
            &(A::swizzled_strategy(), B::swizzled_strategy()),
            |(a, b)| f(a, b),
        )
        .unwrap();
}

pub trait SwizzleStrategy {
    type Strategy: proptest::strategy::Strategy<Value = Self>;
    fn swizzled_strategy() -> Self::Strategy;
}

impl SwizzleStrategy for u8 {
    type Strategy = RangeInclusive<u8>;
    fn swizzled_strategy() -> Self::Strategy {
        0..=64
    }
}

impl<T: fmt::Debug + SwizzleStrategy, const N: usize> SwizzleStrategy for [T; N] {
    type Strategy = test_helpers::array::UniformArrayStrategy<T::Strategy, Self>;
    fn swizzled_strategy() -> Self::Strategy {
        Self::Strategy::new(T::swizzled_strategy())
    }
}
