use std::{
    cmp::{Eq, PartialEq},
    ops::{AddAssign, BitAndAssign, BitOrAssign, BitXorAssign, DivAssign, MulAssign, SubAssign},
};

use crate::utils::ulps_eq;
use crate::{difference_of_products, two_diff, two_prod, two_sum};

//-------------------------------------------------------------
// Which implementation to use.

mod fallback;
#[cfg(not(any(target_arch = "x86_64")))]
pub use fallback::{Bool4, Float4};

#[cfg(target_arch = "x86_64")]
mod sse;
#[cfg(target_arch = "x86_64")]
pub use sse::{Bool4, Float4};

//-------------------------------------------------------------

impl Float4 {
    /// 3D dot product (only uses the first 3 components).
    #[inline(always)]
    pub fn dot_3(a: Self, b: Self) -> f32 {
        let (p, p_err) = two_prod(a, b);

        // Products.
        let (x, x_err) = (p.a(), p_err.a());
        let (y, y_err) = (p.b(), p_err.b());
        let (z, z_err) = (p.c(), p_err.c());

        // Sums.
        let (s1, s1_err) = two_sum(x, y);
        let err1 = x_err + (y_err + s1_err);

        let (s2, s2_err) = two_sum(s1, z);
        let err2 = z_err + (err1 + s2_err);

        // Final result with rounding error compensation.
        s2 + err2
    }

    /// Faster but less precise version of `dot_3()`.
    #[inline(always)]
    pub fn dot_3_fast(a: Self, b: Self) -> f32 {
        let c = a * b;
        c.a() + c.b() + c.c()
    }

    /// 3D cross product (only uses the first 3 components).
    #[inline(always)]
    pub fn cross_3(a: Self, b: Self) -> Self {
        difference_of_products(a.bcad(), b.cabd(), a.cabd(), b.bcad())
    }

    /// Faster but less precise version `cross_3()`.
    #[inline(always)]
    pub fn cross_3_fast(a: Self, b: Self) -> Self {
        (a.bcad() * b.cabd()) - (a.cabd() * b.bcad())
    }

    #[inline(always)]
    pub fn transpose_3x3(m: &[Self; 3]) -> [Self; 3] {
        [
            // The fourth component in each row below is arbitrary,
            // but in this case chosen so that it matches the
            // behavior of the SSE version of transpose_3x3.
            Self::new(m[0].a(), m[1].a(), m[2].a(), m[2].d()),
            Self::new(m[0].b(), m[1].b(), m[2].b(), m[2].d()),
            Self::new(m[0].c(), m[1].c(), m[2].c(), m[2].d()),
        ]
    }

    /// Invert a 3x3 matrix.
    ///
    /// Returns `None` if not invertible.
    #[inline]
    pub fn invert_3x3(m: &[Self; 3]) -> Option<[Self; 3]> {
        let m0_bca = m[0].bcad();
        let m1_bca = m[1].bcad();
        let m2_bca = m[2].bcad();
        let m0_cab = m[0].cabd();
        let m1_cab = m[1].cabd();
        let m2_cab = m[2].cabd();
        let abc = difference_of_products(m1_bca, m2_cab, m1_cab, m2_bca);
        let def = difference_of_products(m2_bca, m0_cab, m2_cab, m0_bca);
        let ghi = difference_of_products(m0_bca, m1_cab, m0_cab, m1_bca);

        let det = Self::dot_3(
            Self::new(abc.a(), def.a(), ghi.a(), 0.0),
            Self::new(m[0].a(), m[1].a(), m[2].a(), 0.0),
        );

        if det == 0.0 {
            None
        } else {
            Some(Self::transpose_3x3(&[abc / det, def / det, ghi / det]))
        }
    }

    /// Faster but less precise version of `invert_3x3()`.
    #[inline]
    pub fn invert_3x3_fast(m: &[Self; 3]) -> Option<[Self; 3]> {
        let m0_bca = m[0].bcad();
        let m1_bca = m[1].bcad();
        let m2_bca = m[2].bcad();
        let m0_cab = m[0].cabd();
        let m1_cab = m[1].cabd();
        let m2_cab = m[2].cabd();
        let abc = (m1_bca * m2_cab) - (m1_cab * m2_bca);
        let def = (m2_bca * m0_cab) - (m2_cab * m0_bca);
        let ghi = (m0_bca * m1_cab) - (m0_cab * m1_bca);

        let det = Self::dot_3_fast(
            Self::new(abc.a(), def.a(), ghi.a(), 0.0),
            Self::new(m[0].a(), m[1].a(), m[2].a(), 0.0),
        );

        if det == 0.0 {
            None
        } else {
            Some(Self::transpose_3x3(&[abc / det, def / det, ghi / det]))
        }
    }

    /// Multiplies a 3D vector with a 3x3 matrix.
    #[inline]
    pub fn vec_mul_3x3(self, m: &[Self; 3]) -> Self {
        let x = self.aaaa();
        let y = self.bbbb();
        let z = self.cccc();

        // Products.
        let (a, a_err) = two_prod(x, m[0]);
        let (b, b_err) = two_prod(y, m[1]);
        let (c, c_err) = two_prod(z, m[2]);

        // Sums.
        let (s1, s1_err) = two_sum(a, b);
        let err1 = a_err + (b_err + s1_err);

        let (s2, s2_err) = two_sum(c, s1);
        let err2 = c_err + (err1 + s2_err);

        s2 + err2
    }

    /// Faster but less precise version of `vec_mul_3x3()`.
    #[inline]
    pub fn vec_mul_3x3_fast(self, m: &[Self; 3]) -> Self {
        let x = self.aaaa();
        let y = self.bbbb();
        let z = self.cccc();

        (x * m[0]) + (y * m[1]) + (z * m[2])
    }

    /// Transforms a 3d point by an affine transform.
    ///
    /// `m` is the 3x3 part of the affine transform, `t` is the translation part.
    #[inline]
    pub fn vec_mul_affine(self, m: &[Self; 3], t: Self) -> Self {
        let x = self.aaaa();
        let y = self.bbbb();
        let z = self.cccc();

        // Products.
        let (a, a_err) = two_prod(x, m[0]);
        let (b, b_err) = two_prod(y, m[1]);
        let (c, c_err) = two_prod(z, m[2]);

        // Sums.
        let (s1, s1_err) = two_sum(a, b);
        let err1 = a_err + (b_err + s1_err);

        let (s2, s2_err) = two_sum(c, s1);
        let err2 = c_err + (err1 + s2_err);

        let (s3, s3_err) = two_sum(t, s2);
        let err3 = err2 + s3_err;

        s3 + err3
    }

    /// Faster but less precise version of `vec_mul_affine()`.
    #[inline]
    pub fn vec_mul_affine_fast(self, m: &[Self; 3], t: Self) -> Self {
        let x = self.aaaa();
        let y = self.bbbb();
        let z = self.cccc();

        (x * m[0]) + (y * m[1]) + (z * m[2]) + t
    }

    /// Transforms a 3d point by an affine transform, except it does
    /// `(vec - t) * inv_m` instead of `vec * m + t`.
    ///
    /// This is useful for performing efficient inverse transforms while
    /// only having to invert the 3x3 part of the transform itself.
    ///
    /// `inv_m` is the inverse 3x3 part of the affine transform, `t` is
    /// the forward translation part.
    #[inline]
    pub fn vec_mul_affine_rev(self, inv_m: &[Self; 3], t: Self) -> Self {
        let (v, v_err) = two_diff(self, t);

        let (x, x_err) = (v.aaaa(), v_err.aaaa());
        let (y, y_err) = (v.bbbb(), v_err.bbbb());
        let (z, z_err) = (v.cccc(), v_err.cccc());

        // Products.
        let ((a, a_err1), a_err2) = (two_prod(x, inv_m[0]), x_err * inv_m[0]);
        let ((b, b_err1), b_err2) = (two_prod(y, inv_m[1]), y_err * inv_m[1]);
        let ((c, c_err1), c_err2) = (two_prod(z, inv_m[2]), z_err * inv_m[2]);
        let a_err = a_err1 + a_err2;
        let b_err = b_err1 + b_err2;
        let c_err = c_err1 + c_err2;

        // Sums.
        let (s1, s1_err) = two_sum(a, b);
        let err1 = a_err + (b_err + s1_err);

        let (s2, s2_err) = two_sum(c, s1);
        let err2 = c_err + (err1 + s2_err);

        s2 + err2
    }

    /// Faster but less precise version of `vec_mul_affine_rev()`.
    #[inline]
    pub fn vec_mul_affine_rev_fast(self, inv_m: &[Self; 3], t: Self) -> Self {
        let v = self - t;

        let x = v.aaaa();
        let y = v.bbbb();
        let z = v.cccc();

        (x * inv_m[0]) + (y * inv_m[1]) + (z * inv_m[2])
    }

    /// Returns whether the `Float4`s are approximately equal to each
    /// other.
    ///
    /// Each corresponding element cannot have a relative error exceeding
    /// `epsilon`.
    pub(crate) fn aprx_eq(a: Self, b: Self, max_ulps: u32) -> bool {
        let mut eq = true;
        eq &= ulps_eq(a.a(), b.a(), max_ulps);
        eq &= ulps_eq(a.b(), b.b(), max_ulps);
        eq &= ulps_eq(a.c(), b.c(), max_ulps);
        eq &= ulps_eq(a.d(), b.d(), max_ulps);
        eq
    }

    /// Transforms one affine transform by another.
    ///
    /// The result is an affine transform that acts as a sequence of the
    /// first followed by the second.
    ///
    /// `m#` is the 3x3 part of the affine transform, `t#` is the translation part.
    #[inline]
    pub fn affine_mul_affine(
        m1: &[Self; 3],
        t1: Self,
        m2: &[Self; 3],
        t2: Self,
    ) -> ([Self; 3], Self) {
        (
            [
                m1[0].vec_mul_3x3(&m2),
                m1[1].vec_mul_3x3(&m2),
                m1[2].vec_mul_3x3(&m2),
            ],
            t1.vec_mul_affine(&m2, t2),
        )
    }

    /// Faster but less precise version of `affine_mul_affine()`.
    #[inline]
    pub fn affine_mul_affine_fast(
        m1: &[Self; 3],
        t1: Self,
        m2: &[Self; 3],
        t2: Self,
    ) -> ([Self; 3], Self) {
        (
            [
                m1[0].vec_mul_3x3_fast(&m2),
                m1[1].vec_mul_3x3_fast(&m2),
                m1[2].vec_mul_3x3_fast(&m2),
            ],
            t1.vec_mul_affine_fast(&m2, t2),
        )
    }
}

impl From<Float4> for (f32, f32, f32, f32) {
    fn from(v: Float4) -> (f32, f32, f32, f32) {
        (v.a(), v.b(), v.c(), v.d())
    }
}

impl AddAssign for Float4 {
    #[inline(always)]
    fn add_assign(&mut self, rhs: Self) {
        *self = *self + rhs;
    }
}

impl SubAssign for Float4 {
    #[inline(always)]
    fn sub_assign(&mut self, rhs: Self) {
        *self = *self - rhs;
    }
}

impl MulAssign for Float4 {
    #[inline(always)]
    fn mul_assign(&mut self, rhs: Self) {
        *self = *self * rhs;
    }
}

impl MulAssign<f32> for Float4 {
    #[inline(always)]
    fn mul_assign(&mut self, rhs: f32) {
        *self = *self * rhs;
    }
}

impl DivAssign for Float4 {
    #[inline(always)]
    fn div_assign(&mut self, rhs: Self) {
        *self = *self / rhs;
    }
}

impl DivAssign<f32> for Float4 {
    #[inline(always)]
    fn div_assign(&mut self, rhs: f32) {
        *self = *self / rhs;
    }
}

impl PartialEq for Float4 {
    #[inline(always)]
    fn eq(&self, rhs: &Self) -> bool {
        self.cmpeq(*rhs).bitmask() == 0b1111
    }
}

//--------

impl BitAndAssign for Bool4 {
    #[inline(always)]
    fn bitand_assign(&mut self, rhs: Self) {
        *self = *self & rhs;
    }
}

impl BitOrAssign for Bool4 {
    #[inline(always)]
    fn bitor_assign(&mut self, rhs: Self) {
        *self = *self | rhs;
    }
}

impl BitXorAssign for Bool4 {
    #[inline(always)]
    fn bitxor_assign(&mut self, rhs: Self) {
        *self = *self ^ rhs;
    }
}

impl PartialEq for Bool4 {
    #[inline(always)]
    fn eq(&self, rhs: &Self) -> bool {
        self.bitmask() == rhs.bitmask()
    }
}

impl Eq for Bool4 {}

impl std::fmt::Debug for Bool4 {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        f.write_str("Bool4(")?;
        f.debug_list().entries(self.to_bools().iter()).finish()?;
        f.write_str(")")?;

        Ok(())
    }
}

//-------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;

    //------------
    // Float4

    #[test]
    fn approximate_equality_test() {
        let a = Float4::new(1.0, 2.0, 3.0, 4.0);
        let b = Float4::new(1.00001, 2.00002, 3.00003, 4.00004);
        let c = Float4::new(1.0e-43, 2.0e-43, 3.0e-43, 4.0e-43);
        let d = Float4::new(-1.0e-43, -2.0e-43, -3.0e-43, -4.0e-43);

        assert!(Float4::aprx_eq(a, a, 0));

        assert!(Float4::aprx_eq(a, b, 130));
        assert!(!Float4::aprx_eq(a, b, 120));

        assert!(Float4::aprx_eq(c, d, 575));
        assert!(!Float4::aprx_eq(c, d, 565));
    }

    #[test]
    fn index() {
        let v = Float4::new(0.0, 1.0, 2.0, 3.0);
        assert_eq!(v[0], 0.0);
        assert_eq!(v[1], 1.0);
        assert_eq!(v[2], 2.0);
        assert_eq!(v[3], 3.0);
    }

    #[test]
    fn get() {
        let v = Float4::new(0.0, 1.0, 2.0, 3.0);

        assert_eq!(v.a(), 0.0);
        assert_eq!(v.b(), 1.0);
        assert_eq!(v.c(), 2.0);
        assert_eq!(v.d(), 3.0);
    }

    #[test]
    fn set() {
        let v = Float4::new(0.0, 1.0, 2.0, 3.0);

        assert_eq!(v.set_a(9.0), Float4::new(9.0, 1.0, 2.0, 3.0));
        assert_eq!(v.set_b(9.0), Float4::new(0.0, 9.0, 2.0, 3.0));
        assert_eq!(v.set_c(9.0), Float4::new(0.0, 1.0, 9.0, 3.0));
        assert_eq!(v.set_d(9.0), Float4::new(0.0, 1.0, 2.0, 9.0));
    }

    #[test]
    fn shuffle() {
        let v = Float4::new(0.0, 1.0, 2.0, 3.0);

        assert_eq!(v.aaaa(), Float4::splat(0.0));
        assert_eq!(v.bbbb(), Float4::splat(1.0));
        assert_eq!(v.cccc(), Float4::splat(2.0));
        assert_eq!(v.dddd(), Float4::splat(3.0));

        assert_eq!(v.bcad(), Float4::new(1.0, 2.0, 0.0, 3.0));
        assert_eq!(v.cabd(), Float4::new(2.0, 0.0, 1.0, 3.0));
    }

    #[test]
    fn abs() {
        let v1 = Float4::new(-1.0, 2.0, -3.0, 4.0);
        let v2 = Float4::new(1.0, -2.0, 3.0, -4.0);

        let r = Float4::new(1.0, 2.0, 3.0, 4.0);

        assert_eq!(v1.abs(), r);
        assert_eq!(v2.abs(), r);
    }

    #[test]
    fn neg() {
        let v1 = Float4::new(-1.0, 2.0, -3.0, 4.0);
        let v2 = Float4::new(1.0, -2.0, 3.0, -4.0);

        assert_eq!(-v1, v2);
        assert_eq!(-v2, v1);
    }

    #[test]
    fn cmp_ops() {
        let a = Float4::new(1.0, 2.0, -2.0, 0.0);
        let b = Float4::new(1.0, -2.0, 2.0, -0.0);

        assert_eq!(a.cmplt(b), Bool4::new(false, false, true, false));
        assert_eq!(a.cmplte(b), Bool4::new(true, false, true, true));
        assert_eq!(a.cmpgt(b), Bool4::new(false, true, false, false));
        assert_eq!(a.cmpgte(b), Bool4::new(true, true, false, true));
        assert_eq!(a.cmpeq(b), Bool4::new(true, false, false, true));
    }

    #[test]
    fn min_max() {
        let a = Float4::new(1.0, 2.0, -2.0, 4.0);
        let b = Float4::new(1.0, -2.0, 2.0, 5.0);

        assert_eq!(a.min(b), Float4::new(1.0, -2.0, -2.0, 4.0));
        assert_eq!(a.max(b), Float4::new(1.0, 2.0, 2.0, 5.0));

        let c = Float4::new(std::f32::INFINITY, 2.0, std::f32::NAN, 4.0);
        let d = Float4::new(1.0, -std::f32::INFINITY, 2.0, std::f32::NAN);
        let r_min = c.min(d);
        let r_max = c.max(d);

        assert_eq!(r_min.a(), 1.0);
        assert_eq!(r_min.b(), -std::f32::INFINITY);
        assert_eq!(r_min.c(), 2.0);
        assert!(r_min.d().is_nan());

        assert_eq!(r_max.a(), std::f32::INFINITY);
        assert_eq!(r_max.b(), 2.0);
        assert_eq!(r_max.c(), 2.0);
        assert!(r_max.d().is_nan());
    }

    #[test]
    fn dot_3() {
        let v1 = Float4::new(1.0, 2.0, -3.0, 0.0);
        let v2 = Float4::new(4.0, -5.0, 6.0, 0.0);

        assert_eq!(Float4::dot_3(v1, v2), -24.0);
        assert_eq!(Float4::dot_3_fast(v1, v2), -24.0);
    }

    #[test]
    fn cross_3() {
        let v1 = Float4::new(1.0, 2.0, -3.0, 0.0);
        let v2 = Float4::new(4.0, -5.0, 6.0, 0.0);

        let r = Float4::new(-3.0, -18.0, -13.0, 0.0);

        assert_eq!(Float4::cross_3(v1, v2), r);
        assert_eq!(Float4::cross_3(v2, v1), -r);
        assert_eq!(Float4::cross_3_fast(v1, v2), r);
        assert_eq!(Float4::cross_3_fast(v2, v1), -r);
    }

    #[test]
    fn transpose_3x3() {
        let m1 = [
            Float4::new(1.0, 4.0, 7.0, 0.0),
            Float4::new(2.0, 5.0, 8.0, 0.0),
            Float4::new(3.0, 6.0, 9.0, 0.0),
        ];
        let m2 = [
            Float4::new(1.0, 2.0, 3.0, 0.0),
            Float4::new(4.0, 5.0, 6.0, 0.0),
            Float4::new(7.0, 8.0, 9.0, 0.0),
        ];

        assert_eq!(Float4::transpose_3x3(&m1), m2);
        assert_eq!(Float4::transpose_3x3(&m2), m1);
    }

    #[test]
    fn invert_3x3() {
        let m = [
            Float4::new(1.0, 3.0, 9.0, 0.0),
            Float4::new(2.0, 6.0, 2.0, 0.0),
            Float4::new(2.0, 7.0, 11.0, 0.0),
        ];
        let inv_m = [
            Float4::new(3.25, 1.875, -3.0, 0.0),
            Float4::new(-1.125, -0.4375, 1.0, 0.0),
            Float4::new(0.125, -0.0625, 0.0, 0.0),
        ];

        assert_eq!(Float4::invert_3x3(&m).unwrap(), inv_m);
        assert_eq!(Float4::invert_3x3(&inv_m).unwrap(), m);
        assert_eq!(Float4::invert_3x3_fast(&m).unwrap(), inv_m);
        assert_eq!(Float4::invert_3x3_fast(&inv_m).unwrap(), m);
    }

    #[test]
    fn vec_mul_3x3() {
        let v = Float4::new(1.0, 2.5, 4.0, 0.0);
        let m = [
            Float4::new(1.0, 3.0, 9.0, 0.0),
            Float4::new(2.0, 6.0, 2.0, 0.0),
            Float4::new(2.0, 7.0, 11.0, 0.0),
        ];

        let r = Float4::new(14.0, 46.0, 58.0, 0.0);

        assert_eq!(v.vec_mul_3x3(&m), r);
        assert_eq!(v.vec_mul_3x3_fast(&m), r);
    }

    #[test]
    fn vec_mul_affine() {
        let p = Float4::new(1.0, 2.5, 4.0, 0.0);
        let xform = (
            [
                Float4::new(1.0, 3.0, 9.0, 0.0),
                Float4::new(2.0, 6.0, 2.0, 0.0),
                Float4::new(2.0, 7.0, 11.0, 0.0),
            ],
            Float4::new(1.5, 8.0, 12.0, 0.0),
        );

        let r = Float4::new(15.5, 54.0, 70.0, 0.0);

        assert_eq!(p.vec_mul_affine(&xform.0, xform.1), r);
    }

    #[test]
    fn vec_mul_affine_rev() {
        let p = Float4::new(15.5, 54.0, 70.0, 0.0);
        let inv_m = [
            Float4::new(3.25, 1.875, -3.0, 0.0),
            Float4::new(-1.125, -0.4375, 1.0, 0.0),
            Float4::new(0.125, -0.0625, 0.0, 0.0),
        ];
        let t = Float4::new(1.5, 8.0, 12.0, 0.0);

        let r = Float4::new(1.0, 2.5, 4.0, 0.0);

        assert_eq!(p.vec_mul_affine_rev(&inv_m, t), r);
        assert_eq!(p.vec_mul_affine_rev_fast(&inv_m, t), r);
    }

    #[test]
    fn affine_mul_affine() {
        let a = (
            [
                Float4::new(1.0, 3.0, 9.0, 0.0),
                Float4::new(2.0, 6.0, 2.0, 0.0),
                Float4::new(2.0, 7.0, 11.0, 0.0),
            ],
            Float4::new(1.5, 8.0, 12.0, 0.0),
        );
        let b = (
            [
                Float4::new(1.0, 2.0, 3.0, 0.0),
                Float4::new(5.0, 6.0, 7.0, 0.0),
                Float4::new(9.0, 10.0, 11.0, 0.0),
            ],
            Float4::new(13.0, 14.0, 15.0, 0.0),
        );

        let r = (
            [
                Float4::new(97.0, 110.0, 123.0, 0.0),
                Float4::new(50.0, 60.0, 70.0, 0.0),
                Float4::new(136.0, 156.0, 176.0, 0.0),
            ],
            Float4::new(162.5, 185.0, 207.5, 0.0),
        );

        assert_eq!(Float4::affine_mul_affine(&a.0, a.1, &b.0, b.1), r);
        assert_eq!(Float4::affine_mul_affine_fast(&a.0, a.1, &b.0, b.1), r);
    }

    //------------
    // Bool4

    #[test]
    fn bitmask() {
        assert_eq!(Bool4::new(true, false, false, false).bitmask(), 0b0001);
        assert_eq!(Bool4::new(false, true, false, false).bitmask(), 0b0010);
        assert_eq!(Bool4::new(false, false, true, false).bitmask(), 0b0100);
        assert_eq!(Bool4::new(false, false, false, true).bitmask(), 0b1000);

        assert_eq!(Bool4::new(false, true, false, true).bitmask(), 0b1010);
        assert_eq!(Bool4::new(true, false, true, false).bitmask(), 0b0101);
    }

    #[test]
    fn to_bools() {
        assert_eq!(
            Bool4::new(true, false, false, false).to_bools(),
            [true, false, false, false]
        );
        assert_eq!(
            Bool4::new(false, true, false, false).to_bools(),
            [false, true, false, false]
        );
        assert_eq!(
            Bool4::new(false, false, true, false).to_bools(),
            [false, false, true, false]
        );
        assert_eq!(
            Bool4::new(false, false, false, true).to_bools(),
            [false, false, false, true]
        );

        assert_eq!(
            Bool4::new(false, true, false, true).to_bools(),
            [false, true, false, true]
        );
        assert_eq!(
            Bool4::new(true, false, true, false).to_bools(),
            [true, false, true, false]
        );
    }

    #[test]
    fn any() {
        assert_eq!(Bool4::new(true, false, false, false).any(), true);
        assert_eq!(Bool4::new(false, true, false, false).any(), true);
        assert_eq!(Bool4::new(false, false, true, false).any(), true);
        assert_eq!(Bool4::new(false, false, false, true).any(), true);

        assert_eq!(Bool4::new(false, false, false, false).any(), false);
    }

    #[test]
    fn all() {
        assert_eq!(Bool4::new(false, true, true, true).all(), false);
        assert_eq!(Bool4::new(true, false, true, true).all(), false);
        assert_eq!(Bool4::new(true, true, false, true).all(), false);
        assert_eq!(Bool4::new(true, true, true, false).all(), false);

        assert_eq!(Bool4::new(true, true, true, true).all(), true);
    }

    #[test]
    fn boolean_ops() {
        let all = Bool4::new(true, true, true, true);
        let none = Bool4::new(false, false, false, false);
        let a = Bool4::new(true, false, true, false);
        let b = Bool4::new(false, true, false, true);

        // Not.
        assert_eq!(!a, b);
        assert_eq!(!b, a);
        assert_eq!(!all, none);
        assert_eq!(!none, all);

        // And.
        assert_eq!(a & b, none);
        assert_eq!(all & none, none);
        assert_eq!(all & all, all);
        assert_eq!(none & none, none);

        // Or.
        assert_eq!(a | b, all);
        assert_eq!(all | none, all);
        assert_eq!(all | all, all);
        assert_eq!(none | none, none);

        // Xor.
        assert_eq!(a ^ b, all);
        assert_eq!(all ^ none, all);
        assert_eq!(all ^ all, none);
        assert_eq!(none ^ none, none);
    }
}
