use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Index, Mul, Neg, Not, Sub};

use crate::FMulAdd;

//=============================================================
// Float4

#[derive(Debug, Copy, Clone)]
#[repr(C, align(16))]
pub struct Float4([f32; 4]);

impl Float4 {
    /// Create a new `Float4` with the given components.
    #[inline(always)]
    pub fn new(a: f32, b: f32, c: f32, d: f32) -> Self {
        Self([a, b, c, d])
    }

    /// Create a new `Float4` with all elements set to `n`.
    #[inline(always)]
    pub fn splat(n: f32) -> Self {
        Self([n, n, n, n])
    }

    /// Component-wise fused multiply-add.
    ///
    /// `(self * a) + b` with only one rounding error.
    #[inline(always)]
    pub fn mul_add(self, a: Self, b: Self) -> Self {
        Self([
            self.0[0].mul_add(a.0[0], b.0[0]),
            self.0[1].mul_add(a.0[1], b.0[1]),
            self.0[2].mul_add(a.0[2], b.0[2]),
            self.0[3].mul_add(a.0[3], b.0[3]),
        ])
    }

    /// Vertical minimum.
    #[inline(always)]
    pub fn min(self, a: Self) -> Self {
        // Custom min to match behavior of SSE.
        #[inline(always)]
        pub fn minf(a: f32, b: f32) -> f32 {
            if a < b {
                a
            } else {
                b
            }
        }
        Self([
            minf(self.0[0], a.0[0]),
            minf(self.0[1], a.0[1]),
            minf(self.0[2], a.0[2]),
            minf(self.0[3], a.0[3]),
        ])
    }

    /// Vertical maximum.
    #[inline(always)]
    pub fn max(self, a: Self) -> Self {
        // Custom max to match behavior of SSE.
        #[inline(always)]
        pub fn maxf(a: f32, b: f32) -> f32 {
            if a > b {
                a
            } else {
                b
            }
        }
        Self([
            maxf(self.0[0], a.0[0]),
            maxf(self.0[1], a.0[1]),
            maxf(self.0[2], a.0[2]),
            maxf(self.0[3], a.0[3]),
        ])
    }

    /// Horizontal minimum.
    #[inline(always)]
    pub fn min_element(self) -> f32 {
        let a = self.0[0].min(self.0[1]);
        let b = self.0[2].min(self.0[3]);
        a.min(b)
    }

    /// Horizontal maximum.
    #[inline(always)]
    pub fn max_element(self) -> f32 {
        let a = self.0[0].max(self.0[1]);
        let b = self.0[2].max(self.0[3]);
        a.max(b)
    }

    /// 1.0 / self
    #[inline(always)]
    pub fn recip(self) -> Self {
        Float4::splat(1.0) / self
    }

    #[inline(always)]
    pub fn abs(self) -> Self {
        Float4::new(
            self.a().abs(),
            self.b().abs(),
            self.c().abs(),
            self.d().abs(),
        )
    }

    //-----------------------------------------------------
    // Comparisons.

    /// Less than.
    #[inline(always)]
    pub fn cmplt(self, rhs: Self) -> Bool4 {
        Bool4([
            self.0[0] < rhs.0[0],
            self.0[1] < rhs.0[1],
            self.0[2] < rhs.0[2],
            self.0[3] < rhs.0[3],
        ])
    }

    /// Less than or equal.
    #[inline(always)]
    pub fn cmplte(self, rhs: Self) -> Bool4 {
        Bool4([
            self.0[0] <= rhs.0[0],
            self.0[1] <= rhs.0[1],
            self.0[2] <= rhs.0[2],
            self.0[3] <= rhs.0[3],
        ])
    }

    /// Greater than.
    #[inline(always)]
    pub fn cmpgt(self, rhs: Self) -> Bool4 {
        Bool4([
            self.0[0] > rhs.0[0],
            self.0[1] > rhs.0[1],
            self.0[2] > rhs.0[2],
            self.0[3] > rhs.0[3],
        ])
    }

    /// Greater than or equal.
    #[inline(always)]
    pub fn cmpgte(self, rhs: Self) -> Bool4 {
        Bool4([
            self.0[0] >= rhs.0[0],
            self.0[1] >= rhs.0[1],
            self.0[2] >= rhs.0[2],
            self.0[3] >= rhs.0[3],
        ])
    }

    /// Equal.
    #[inline(always)]
    pub fn cmpeq(self, rhs: Self) -> Bool4 {
        Bool4([
            self.0[0] == rhs.0[0],
            self.0[1] == rhs.0[1],
            self.0[2] == rhs.0[2],
            self.0[3] == rhs.0[3],
        ])
    }

    //-----------------------------------------------------
    // Individual components.

    #[inline(always)]
    pub fn a(self) -> f32 {
        self.0[0]
    }

    #[inline(always)]
    pub fn b(self) -> f32 {
        self.0[1]
    }

    #[inline(always)]
    pub fn c(self) -> f32 {
        self.0[2]
    }

    #[inline(always)]
    pub fn d(self) -> f32 {
        self.0[3]
    }

    #[inline(always)]
    #[must_use]
    pub fn set_a(self, n: f32) -> Self {
        Self([n, self.0[1], self.0[2], self.0[3]])
    }

    #[inline(always)]
    #[must_use]
    pub fn set_b(self, n: f32) -> Self {
        Self([self.0[0], n, self.0[2], self.0[3]])
    }

    #[inline(always)]
    #[must_use]
    pub fn set_c(self, n: f32) -> Self {
        Self([self.0[0], self.0[1], n, self.0[3]])
    }

    #[inline(always)]
    #[must_use]
    pub fn set_d(self, n: f32) -> Self {
        Self([self.0[0], self.0[1], self.0[2], n])
    }

    //-----------------------------------------------------
    // Shuffles.

    #[inline(always)]
    pub fn aaaa(self) -> Self {
        let a = self.0[0];
        Self([a, a, a, a])
    }

    #[inline(always)]
    pub fn bbbb(self) -> Self {
        let b = self.0[1];
        Self([b, b, b, b])
    }

    #[inline(always)]
    pub fn cccc(self) -> Self {
        let c = self.0[2];
        Self([c, c, c, c])
    }

    #[inline(always)]
    pub fn dddd(self) -> Self {
        let d = self.0[3];
        Self([d, d, d, d])
    }

    #[inline(always)]
    pub fn bcad(self) -> Self {
        let a = self.0[0];
        let b = self.0[1];
        let c = self.0[2];
        let d = self.0[3];
        Self([b, c, a, d])
    }

    #[inline(always)]
    pub fn cabd(self) -> Self {
        let a = self.0[0];
        let b = self.0[1];
        let c = self.0[2];
        let d = self.0[3];
        Self([c, a, b, d])
    }
}

impl Index<usize> for Float4 {
    type Output = f32;

    #[inline(always)]
    fn index(&self, idx: usize) -> &f32 {
        &self.0[idx]
    }
}

impl Add for Float4 {
    type Output = Self;

    #[inline(always)]
    fn add(self, rhs: Self) -> Self {
        Self([
            self.0[0] + rhs.0[0],
            self.0[1] + rhs.0[1],
            self.0[2] + rhs.0[2],
            self.0[3] + rhs.0[3],
        ])
    }
}

impl Sub for Float4 {
    type Output = Self;

    #[inline(always)]
    fn sub(self, rhs: Self) -> Self {
        Self([
            self.0[0] - rhs.0[0],
            self.0[1] - rhs.0[1],
            self.0[2] - rhs.0[2],
            self.0[3] - rhs.0[3],
        ])
    }
}

impl Mul for Float4 {
    type Output = Self;

    #[inline(always)]
    fn mul(self, rhs: Self) -> Self {
        Self([
            self.0[0] * rhs.0[0],
            self.0[1] * rhs.0[1],
            self.0[2] * rhs.0[2],
            self.0[3] * rhs.0[3],
        ])
    }
}

impl Mul<f32> for Float4 {
    type Output = Self;

    #[inline(always)]
    fn mul(self, rhs: f32) -> Self {
        Self([
            self.0[0] * rhs,
            self.0[1] * rhs,
            self.0[2] * rhs,
            self.0[3] * rhs,
        ])
    }
}

impl Div for Float4 {
    type Output = Self;

    #[inline(always)]
    fn div(self, rhs: Self) -> Self {
        Self([
            self.0[0] / rhs.0[0],
            self.0[1] / rhs.0[1],
            self.0[2] / rhs.0[2],
            self.0[3] / rhs.0[3],
        ])
    }
}

impl Div<f32> for Float4 {
    type Output = Self;

    #[inline(always)]
    fn div(self, rhs: f32) -> Self {
        Self([
            self.0[0] / rhs,
            self.0[1] / rhs,
            self.0[2] / rhs,
            self.0[3] / rhs,
        ])
    }
}

impl Neg for Float4 {
    type Output = Self;

    #[inline(always)]
    fn neg(self) -> Self {
        Self([-self.0[0], -self.0[1], -self.0[2], -self.0[3]])
    }
}

impl FMulAdd for Float4 {
    #[inline(always)]
    fn fma(self, b: Self, c: Self) -> Self {
        self.mul_add(b, c)
    }
}

//=============================================================
// Bool4

#[derive(Copy, Clone)]
#[repr(transparent)]
pub struct Bool4([bool; 4]);

impl Bool4 {
    #[inline(always)]
    pub fn new(a: bool, b: bool, c: bool, d: bool) -> Self {
        Bool4([a, b, c, d])
    }

    #[inline(always)]
    pub fn new_false() -> Self {
        Self([false, false, false, false])
    }

    #[inline(always)]
    pub fn to_bools(self) -> [bool; 4] {
        self.0
    }

    /// Note: `a` goes to the least significant bit.
    #[inline(always)]
    pub fn bitmask(self) -> u8 {
        self.0[0] as u8
            | ((self.0[1] as u8) << 1)
            | ((self.0[2] as u8) << 2)
            | ((self.0[3] as u8) << 3)
    }

    #[inline(always)]
    pub fn any(self) -> bool {
        self.0[0] | &self.0[1] | self.0[2] | self.0[3]
    }

    #[inline(always)]
    pub fn all(self) -> bool {
        self.0[0] & &self.0[1] & self.0[2] & self.0[3]
    }
}

impl BitAnd for Bool4 {
    type Output = Self;

    #[inline(always)]
    fn bitand(self, rhs: Self) -> Self {
        Self([
            self.0[0] & rhs.0[0],
            self.0[1] & rhs.0[1],
            self.0[2] & rhs.0[2],
            self.0[3] & rhs.0[3],
        ])
    }
}

impl BitOr for Bool4 {
    type Output = Self;

    #[inline(always)]
    fn bitor(self, rhs: Self) -> Self {
        Self([
            self.0[0] | rhs.0[0],
            self.0[1] | rhs.0[1],
            self.0[2] | rhs.0[2],
            self.0[3] | rhs.0[3],
        ])
    }
}

impl BitXor for Bool4 {
    type Output = Self;

    #[inline(always)]
    fn bitxor(self, rhs: Self) -> Self {
        Self([
            self.0[0] ^ rhs.0[0],
            self.0[1] ^ rhs.0[1],
            self.0[2] ^ rhs.0[2],
            self.0[3] ^ rhs.0[3],
        ])
    }
}

impl Not for Bool4 {
    type Output = Self;

    #[inline(always)]
    fn not(self) -> Self {
        Self([!self.0[0], !self.0[1], !self.0[2], !self.0[3]])
    }
}
