#[cfg(feature = "nightly-simd")]
use core::simd::{num::SimdFloat, prelude::SimdPartialOrd, Select as _, Simd, SimdElement};

pub trait Dot {
    type Output;
    fn dot(lhs: Self, rhs: Self) -> Self::Output;
}

#[inline(always)]
pub fn dot<T: Dot>(lhs: T, rhs: T) -> T::Output {
    Dot::dot(lhs, rhs)
}

#[inline(always)]
pub fn length_squared<T: Dot + Copy>(value: T) -> T::Output {
    dot(value, value)
}

#[allow(dead_code)]
pub fn length<T: Dot<Output = f32> + Copy>(value: T) -> T::Output {
    crate::math::sqrt(length_squared(value))
}

pub trait FloorToInt {
    type Output;
    fn floor_to_int(self) -> Self::Output;
}

impl FloorToInt for f32 {
    type Output = i32;

    #[inline(always)]
    fn floor_to_int(self) -> Self::Output {
        if self >= 0.0 {
            self as i32
        } else {
            (self as i32).wrapping_sub(1)
        }
    }
}

#[cfg(feature = "nightly-simd")]
impl<const LANES: usize> FloorToInt for Simd<f32, LANES> {
    type Output = Simd<i32, LANES>;

    #[inline(always)]
    fn floor_to_int(self) -> Self::Output {
        let int = unsafe { self.to_int_unchecked::<i32>() };
        int - self.simd_ge(splat(0.0)).select(splat(0), splat(1))
    }
}

#[inline(always)]
pub fn floor_to_int<T: FloorToInt>(value: T) -> T::Output {
    FloorToInt::floor_to_int(value)
}

pub trait RoundToInt {
    type Output;
    fn round_to_int(self) -> Self::Output;
}

impl RoundToInt for f32 {
    type Output = i32;

    #[inline(always)]
    fn round_to_int(self) -> Self::Output {
        if self >= 0.0 {
            (self + 0.5) as i32
        } else {
            (self - 0.5) as i32
        }
    }
}

#[cfg(feature = "nightly-simd")]
impl<const LANES: usize> RoundToInt for Simd<f32, LANES> {
    type Output = Simd<i32, LANES>;

    #[inline(always)]
    fn round_to_int(self) -> Self::Output {
        let f = self + self.simd_ge(splat(0.0)).select(splat(0.5), splat(-0.5));
        unsafe { f.to_int_unchecked() }
    }
}

#[inline(always)]
pub fn round_to_int<T: RoundToInt>(value: T) -> T::Output {
    RoundToInt::round_to_int(value)
}

pub trait InterpQuintic {
    fn interp_quintic(self) -> Self;
}

impl InterpQuintic for f32 {
    #[inline(always)]
    fn interp_quintic(self) -> Self {
        self * self * self * (self * (self * 6.0 - 15.0) + 10.0)
    }
}

#[cfg(feature = "nightly-simd")]
impl<const LANES: usize> InterpQuintic for Simd<f32, LANES> {
    #[inline(always)]
    fn interp_quintic(self) -> Self {
        self * self * self * (self * (self * splat(6.0) - splat(15.0)) + splat(10.0))
    }
}

#[inline(always)]
pub fn interp_quintic<T: InterpQuintic>(value: T) -> T {
    InterpQuintic::interp_quintic(value)
}

pub trait InterpHermite {
    fn interp_hermite(self) -> Self;
}

impl InterpHermite for f32 {
    #[inline(always)]
    fn interp_hermite(self) -> Self {
        self * self * (3.0 - 2.0 * self)
    }
}

#[cfg(feature = "nightly-simd")]
impl<const LANES: usize> InterpHermite for Simd<f32, LANES> {
    #[inline(always)]
    fn interp_hermite(self) -> Self {
        self * self * (splat(3.0) - splat(2.0) * self)
    }
}

#[inline(always)]
pub fn interp_hermite<T: InterpHermite>(value: T) -> T {
    InterpHermite::interp_hermite(value)
}

pub trait Lerp<T = Self> {
    type Output;
    fn lerp(a: Self, b: Self, t: T) -> Self::Output;
}

impl Lerp for f32 {
    type Output = Self;

    #[inline(always)]
    fn lerp(a: Self, b: Self, t: Self) -> Self::Output {
        a + t * (b - a)
    }
}

#[inline(always)]
pub fn lerp<T, V: Lerp<T>>(a: V, b: V, t: T) -> V::Output {
    Lerp::lerp(a, b, t)
}

#[cfg(feature = "nightly-simd")]
impl<const LANES: usize> Lerp for Simd<f32, LANES> {
    type Output = Self;

    #[inline(always)]
    fn lerp(a: Self, b: Self, t: Self) -> Self::Output {
        a + t * (b - a)
    }
}

#[cfg(feature = "nightly-simd")]
impl<const LANES: usize> Lerp<f32> for Simd<f32, LANES> {
    type Output = Self;

    #[inline(always)]
    fn lerp(a: Self, b: Self, t: f32) -> Self::Output {
        a + Simd::splat(t) * (b - a)
    }
}

#[cfg(feature = "nightly-simd")]
pub(crate) fn splat<T, const LANES: usize>(value: T) -> Simd<T, LANES>
where
    T: SimdElement,
{
    Simd::splat(value)
}

#[inline(always)]
pub fn fast_min(a: f32, b: f32) -> f32 {
    if a < b {
        a
    } else {
        b
    }
}

#[allow(dead_code)]
#[inline(always)]
pub fn fast_max(a: f32, b: f32) -> f32 {
    if a > b {
        a
    } else {
        b
    }
}
