use std::f32::consts::PI;

use roofy::{Quat, Vec2, Vec3, Vec4};

#[test]
fn const_zero() {
    assert_eq!(Vec4::ZERO, Vec4::splat(0.0));
}

#[test]
fn const_one() {
    assert_eq!(Vec4::ONE, Vec4::splat(1.0));
}

#[test]
fn const_neg_one() {
    assert_eq!(Vec4::NEG_ONE, Vec4::splat(-1.0));
}

#[test]
fn const_infinity() {
    assert_eq!(Vec4::INFINITY, Vec4::splat(f32::INFINITY));
}

#[test]
fn const_neg_infinity() {
    assert_eq!(Vec4::NEG_INFINITY, Vec4::splat(f32::NEG_INFINITY));
}

#[test]
fn const_nan() {
    let a: [f32; 4] = Vec4::NAN.into();
    assert!(a.iter().all(|x| x.is_nan()))
}

#[test]
fn const_x() {
    assert_eq!(Vec4::X, Vec4::new(1.0, 0.0, 0.0, 0.0));
}

#[test]
fn const_neg_x() {
    assert_eq!(Vec4::NEG_X, Vec4::new(-1.0, 0.0, 0.0, 0.0));
}

#[test]
fn const_y() {
    assert_eq!(Vec4::Y, Vec4::new(0.0, 1.0, 0.0, 0.0));
}

#[test]
fn const_neg_y() {
    assert_eq!(Vec4::NEG_Y, Vec4::new(0.0, -1.0, 0.0, 0.0));
}

#[test]
fn const_z() {
    assert_eq!(Vec4::Z, Vec4::new(0.0, 0.0, 1.0, 0.0));
}

#[test]
fn const_neg_z() {
    assert_eq!(Vec4::NEG_Z, Vec4::new(0.0, 0.0, -1.0, 0.0));
}

#[test]
fn const_w() {
    assert_eq!(Vec4::W, Vec4::new(0.0, 0.0, 0.0, 1.0));
}

#[test]
fn const_neg_w() {
    assert_eq!(Vec4::NEG_W, Vec4::new(0.0, 0.0, 0.0, -1.0));
}

#[test]
fn new() {
    assert_eq!(
        Vec4::new(1.0, 2.0, 3.0, 4.0),
        Vec4 {
            x: 1.0,
            y: 2.0,
            z: 3.0,
            w: 4.0,
        }
    );
}

#[test]
fn splat() {
    assert_eq!(Vec4::splat(2.0), Vec4::new(2.0, 2.0, 2.0, 2.0));
}

#[test]
fn from_array() {
    assert_eq!(Vec4::from_array([1.0, 1.0, 1.0, 1.0]), Vec4::ONE);
}

#[test]
fn to_array() {
    assert_eq!(
        Vec4::new(1.0, 2.0, 3.0, 4.0).to_array(),
        [1.0, 2.0, 3.0, 4.0]
    );
}

#[test]
fn from_slice() {
    assert_eq!(Vec4::from_slice(&[1.0; 4]), Vec4::ONE);
}

#[test]
#[should_panic(expected = "index out of bounds")]
fn from_slice_panics_if_too_short() {
    let _ = Vec4::from_slice(&[0.0; 3]);
}

#[test]
fn write_to_slice() {
    let mut slice: [f32; 4] = [0.0; 4];
    Vec4::ONE.write_to_slice(&mut slice);
    assert_eq!(slice, [1.0; 4]);
}

#[test]
#[should_panic(expected = "index out of bounds")]
fn write_to_slice_panics_if_to_short() {
    let mut slice: [f32; 3] = [0.0; 3];
    Vec4::ONE.write_to_slice(&mut slice);
}

#[test]
fn from_vec2() {
    assert_eq!(Vec4::from_vec2(Vec2::ONE), Vec4::new(1.0, 1.0, 0.0, 0.0));
}

#[test]
fn to_vec2() {
    assert_eq!(Vec4::ONE.to_vec2(), Vec2::from_vec4(Vec4::ONE));
}

#[test]
fn from_vec3() {
    assert_eq!(Vec4::from_vec3(Vec3::ONE), Vec4::new(1.0, 1.0, 1.0, 0.0));
}

#[test]
fn to_vec3() {
    assert_eq!(Vec4::ONE.to_vec3(), Vec3::from_vec4(Vec4::ONE));
}

#[test]
fn from_quat() {
    assert_eq!(Vec4::from_quat(Quat::IDENTITY), Vec4::W);
}

#[test]
fn map() {
    assert_eq!(Vec4::NEG_ONE.map(f32::abs), Vec4::ONE);
}

#[test]
fn map_lambda() {
    assert_eq!(Vec4::ZERO.map(|x| x + 1.0), Vec4::ONE);
}

#[test]
fn with_x() {
    assert_eq!(Vec4::ZERO.with_x(1.0), Vec4::X);
}

#[test]
fn with_y() {
    assert_eq!(Vec4::ZERO.with_y(1.0), Vec4::Y);
}

#[test]
fn with_z() {
    assert_eq!(Vec4::ZERO.with_z(1.0), Vec4::Z);
}

#[test]
fn with_w() {
    assert_eq!(Vec4::ZERO.with_w(1.0), Vec4::W);
}

#[test]
fn dot() {
    assert_eq!(
        Vec4::new(1.0, 2.0, 3.0, 4.0).dot(Vec4::new(2.0, 4.0, 6.0, 8.0)),
        60.0
    );
}

#[test]
fn length_one_is_sqrt_4() {
    assert_eq!(Vec4::ONE.length(), 4_f32.sqrt());
}

#[test]
fn length_squared_one_is_4() {
    assert_eq!(Vec4::ONE.length_squared(), 4.0);
}

#[test]
fn normalize_sets_length_approx_1() {
    let mut v: Vec4 = Vec4::ONE;
    v.normalize();
    assert!(v.is_normalized());
}

#[test]
fn normalized_sets_length_approx_1() {
    assert!(Vec4::ONE.normalized().is_normalized());
}

#[test]
fn try_normalize_matches() {
    let mut v1: Vec4 = Vec4::ONE;
    let mut v2: Vec4 = Vec4::ONE;
    let _ = v1.try_normalize();
    v2.normalize();
    assert_eq!(v1, v2);
}

#[test]
fn try_normalize_one_is_true() {
    let mut v: Vec4 = Vec4::ONE;
    assert!(v.try_normalize());
}

#[test]
fn try_normalize_zero_is_false() {
    let mut v: Vec4 = Vec4::ZERO;
    assert!(!v.try_normalize());
}

#[test]
fn try_normalize_infinity_is_false() {
    let mut v: Vec4 = Vec4::INFINITY;
    assert!(!v.try_normalize());
}

#[test]
fn try_normalized_matches() {
    assert_eq!(Vec4::ONE.try_normalized().unwrap(), Vec4::ONE.normalized());
}

#[test]
fn try_normalized_zero_is_none() {
    assert!(Vec4::ZERO.try_normalized().is_none());
}

#[test]
fn try_normalized_infinity_is_none() {
    assert!(Vec4::INFINITY.try_normalized().is_none());
}

#[test]
fn normalize_or() {
    assert_eq!(Vec4::ZERO.normalized_or(Vec4::Y), Vec4::Y);
}

#[test]
fn normalize_or_zero() {
    assert_eq!(Vec4::ZERO.normalized_or_zero(), Vec4::ZERO);
}

#[test]
fn is_normalized() {
    assert!(Vec4::new(1.0, 0.0, 0.0, 0.0).is_normalized());
    assert!(!Vec4::ONE.is_normalized());
}

#[test]
fn distance() {
    assert_eq!(Vec4::ZERO.distance(Vec4::new(1.0, 0.0, 0.0, 0.0)), 1.0);
}

#[test]
fn distance_squared() {
    assert_eq!(Vec4::ZERO.distance_squared(Vec4::ONE), 4.0);
}

#[test]
fn project_parallel_stays_same() {
    assert_eq!(
        (Vec4::new(0.0, 1.0, 0.0, 0.0) * 5.0).project(Vec4::new(0.0, 1.0, 0.0, 0.0)),
        Vec4::new(0.0, 1.0, 0.0, 0.0) * 5.0
    );
}

#[test]
fn project_scaled_on_normal() {
    assert_eq!(
        (Vec4::new(0.0, 1.0, 0.0, 0.0) * 5.0).project(Vec4::new(0.0, 1.0, 0.0, 0.0) * 2.0),
        Vec4::new(0.0, 1.0, 0.0, 0.0) * 5.0
    );
}

#[test]
fn project_perpendicular_is_zero() {
    assert_eq!(
        (Vec4::new(0.0, 1.0, 0.0, 0.0) * 5.0).project(Vec4::new(1.0, 0.0, 0.0, 0.0)),
        Vec4::ZERO
    );
}

#[test]
fn min() {
    assert_eq!(
        Vec4::ONE.min(Vec4::new(2.0, -2.0, 2.0, -2.0)),
        Vec4::new(1.0, -2.0, 1.0, -2.0)
    );
}

#[test]
fn max() {
    assert_eq!(
        Vec4::ONE.max(Vec4::new(2.0, -2.0, 2.0, -2.0)),
        Vec4::new(2.0, 1.0, 2.0, 1.0)
    );
}

#[test]
fn clamp() {
    assert_eq!(
        Vec4::new(2.0, -1.0, 1.0, 1.0).clamp(Vec4::ZERO, Vec4::ONE),
        Vec4::new(1.0, 0.0, 1.0, 1.0)
    );
}

#[test]
fn abs() {
    assert_eq!(Vec4::NEG_ONE.abs(), Vec4::ONE);
}

#[test]
fn signum_positive() {
    assert_eq!(Vec4::ONE.signum(), Vec4::ONE);
}

#[test]
fn signum_negative() {
    assert_eq!(Vec4::NEG_ONE.signum(), Vec4::NEG_ONE);
}

#[test]
fn signum_nan() {
    let v: Vec4 = Vec4::NAN.signum();
    assert!(v.is_nan());
}

#[test]
fn powf() {
    assert_eq!(Vec4::splat(2.0).powf(2.0), Vec4::splat(4.0));
}

#[test]
fn sqrt() {
    assert_eq!(Vec4::splat(4.0).sqrt(), Vec4::splat(2.0));
}

#[test]
fn sin() {
    assert!(Vec4::new(PI / 2.0, PI, 0.0, 0.0).sin().approx_eq(Vec4::X));
}

#[test]
fn cos() {
    assert!(
        Vec4::new(PI / 2.0, PI, 0.0, 0.0)
            .cos()
            .approx_eq(Vec4::new(0.0, -1.0, 1.0, 1.0))
    );
}

#[test]
fn sin_cos() {
    let (sin, cos): (Vec4, Vec4) = Vec4::new(PI / 2.0, PI, 0.0, 0.0).sin_cos();
    assert!(sin.approx_eq(Vec4::X));
    assert!(cos.approx_eq(Vec4::new(0.0, -1.0, 1.0, 1.0)));
}

#[test]
fn recip() {
    assert_eq!(Vec4::splat(2.0).recip(), Vec4::splat(0.5));
}

#[test]
fn round() {
    assert_eq!(
        Vec4::new(0.5, 1.4, 2.0, 1.6).round(),
        Vec4::new(1.0, 1.0, 2.0, 2.0)
    );
}

#[test]
fn floor() {
    assert_eq!(
        Vec4::new(0.5, 1.4, 2.0, 1.6).floor(),
        Vec4::new(0.0, 1.0, 2.0, 1.0)
    );
}

#[test]
fn ceil() {
    assert_eq!(
        Vec4::new(0.5, 1.4, 2.0, 1.6).ceil(),
        Vec4::new(1.0, 2.0, 2.0, 2.0)
    );
}

#[test]
fn lerp_at_0_returns_start() {
    assert_eq!(Vec4::ZERO.lerp(Vec4::ONE, 0.0), Vec4::ZERO);
}

#[test]
fn lerp_at_1_returns_end() {
    assert_eq!(Vec4::ZERO.lerp(Vec4::ONE, 1.0), Vec4::ONE);
}

#[test]
fn lerp_at_half_returns_midpoint() {
    assert_eq!(Vec4::ZERO.lerp(Vec4::ONE, 0.5), Vec4::splat(0.5));
}

#[test]
fn lerp_clamped_at_negative_returns_start() {
    assert_eq!(Vec4::ZERO.lerp_clamped(Vec4::ONE, -1.0), Vec4::ZERO);
}

#[test]
fn lerp_clamped_at_2_returns_end() {
    assert_eq!(Vec4::ZERO.lerp_clamped(Vec4::ONE, 2.0), Vec4::ONE);
}

#[test]
fn move_towards_reaches_target() {
    assert_eq!(
        Vec4::ZERO.move_towards(Vec4::new(1.0, 0.0, 0.0, 0.0), 1.0),
        Vec4::new(1.0, 0.0, 0.0, 0.0)
    );
}

#[test]
fn move_towards_does_not_overshoot() {
    assert_eq!(
        Vec4::ZERO.move_towards(Vec4::new(1.0, 0.0, 0.0, 0.0), 5.0),
        Vec4::new(1.0, 0.0, 0.0, 0.0)
    );
}

#[test]
fn move_towards_moves_correct_distance() {
    assert_eq!(
        Vec4::ZERO.move_towards(Vec4::new(1.0, 0.0, 0.0, 0.0) * 4.0, 1.0),
        Vec4::new(1.0, 0.0, 0.0, 0.0)
    );
}

#[test]
fn move_towards_already_at_end() {
    assert_eq!(
        Vec4::new(1.0, 0.0, 0.0, 0.0).move_towards(Vec4::new(1.0, 0.0, 0.0, 0.0), 1.0),
        Vec4::new(1.0, 0.0, 0.0, 0.0)
    );
}

#[test]
fn approx_eq_one_is_equal() {
    assert!(Vec4::ONE.approx_eq(Vec4::ONE));
}

#[test]
fn approx_eq_zero_is_not_one() {
    assert!(!Vec4::ZERO.approx_eq(Vec4::ONE));
}

#[test]
fn approx_eq_tiny_difference_is_equal() {
    assert!(Vec4::ZERO.approx_eq(Vec4::new(1e-7, 0.0, 0.0, 0.0)));
}

#[test]
fn is_finite_zero_returns_true() {
    assert!(Vec4::ZERO.is_finite());
}

#[test]
fn is_finite_infinity_returns_false() {
    assert!(!Vec4::INFINITY.is_finite());
}

#[test]
fn is_nan_zero_returns_false() {
    assert!(!Vec4::ZERO.is_nan());
}

#[test]
fn is_nan_nan_returns_true() {
    assert!(Vec4::NAN.is_nan());
}

#[test]
fn add_self() {
    assert_eq!(Vec4::ONE + Vec4::ONE, Vec4::splat(2.0));
}

#[test]
fn add_assign_self() {
    let mut v: Vec4 = Vec4::ONE;
    v += Vec4::ONE;
    assert_eq!(v, Vec4::splat(2.0));
}

#[test]
fn add_f32() {
    assert_eq!(Vec4::ONE + 1.0, Vec4::splat(2.0));
}

#[test]
fn add_assign_f32() {
    let mut v: Vec4 = Vec4::ONE;
    v += 1.0;
    assert_eq!(v, Vec4::splat(2.0));
}

#[test]
fn f32_add_vec4() {
    assert_eq!(1.0 + Vec4::ONE, Vec4::splat(2.0));
}

#[test]
fn sub_self() {
    assert_eq!(Vec4::ONE - Vec4::ONE, Vec4::ZERO);
}

#[test]
fn sub_assign_self() {
    let mut v: Vec4 = Vec4::ONE;
    v -= Vec4::ONE;
    assert_eq!(v, Vec4::ZERO);
}

#[test]
fn sub_f32() {
    assert_eq!(Vec4::ONE - 1.0, Vec4::ZERO);
}

#[test]
fn sub_assign_f32() {
    let mut v: Vec4 = Vec4::ONE;
    v -= 1.0;
    assert_eq!(v, Vec4::ZERO);
}

#[test]
fn mul_self() {
    assert_eq!(Vec4::splat(2.0) * Vec4::splat(2.0), Vec4::splat(4.0));
}

#[test]
fn mul_assign_self() {
    let mut v: Vec4 = Vec4::splat(2.0);
    v *= Vec4::splat(2.0);
    assert_eq!(v, Vec4::splat(4.0));
}

#[test]
fn mul_f32() {
    assert_eq!(Vec4::splat(2.0) * 2.0, Vec4::splat(4.0));
}

#[test]
fn mul_assign_f32() {
    let mut v: Vec4 = Vec4::splat(2.0);
    v *= 2.0;
    assert_eq!(v, Vec4::splat(4.0));
}

#[test]
fn f32_mul_vec4() {
    assert_eq!(2.0 * Vec4::splat(2.0), Vec4::splat(4.0));
}

#[test]
fn div_self() {
    assert_eq!(Vec4::splat(2.0) / Vec4::splat(2.0), Vec4::ONE);
}

#[test]
fn div_assign_self() {
    let mut v: Vec4 = Vec4::splat(2.0);
    v /= Vec4::splat(2.0);
    assert_eq!(v, Vec4::ONE);
}

#[test]
fn div_f32() {
    assert_eq!(Vec4::splat(2.0) / 2.0, Vec4::ONE);
}

#[test]
fn div_assign_f32() {
    let mut v: Vec4 = Vec4::splat(2.0);
    v /= 2.0;
    assert_eq!(v, Vec4::ONE);
}

#[test]
fn rem_self() {
    assert_eq!(Vec4::splat(3.0) % Vec4::splat(2.0), Vec4::ONE);
}

#[test]
fn rem_assign_self() {
    let mut v: Vec4 = Vec4::splat(3.0);
    v %= Vec4::splat(2.0);
    assert_eq!(v, Vec4::ONE);
}

#[test]
fn rem_f32() {
    assert_eq!(Vec4::splat(3.0) % 2.0, Vec4::ONE);
}

#[test]
fn rem_assign_f32() {
    let mut v: Vec4 = Vec4::splat(3.0);
    v %= 2.0;
    assert_eq!(v, Vec4::ONE);
}

#[test]
fn partial_eq() {
    assert!(Vec4::ZERO == Vec4::ZERO);
}

#[test]
fn not_partial_eq() {
    assert!(Vec4::ZERO != Vec4::ONE);
}

#[test]
fn neg() {
    assert_eq!(-Vec4::ONE, Vec4::splat(-1.0));
}

#[test]
fn index() {
    assert_eq!(Vec4::ONE[0], 1.0);
    assert_eq!(Vec4::ONE[1], 1.0);
    assert_eq!(Vec4::ONE[2], 1.0);
    assert_eq!(Vec4::ONE[3], 1.0)
}

#[test]
#[should_panic(expected = "index out of bounds")]
fn index_panics_out_of_bounds() {
    let _ = Vec3::ONE[4];
}

#[test]
fn index_mut() {
    let mut v: Vec4 = Vec4::ONE;
    v[0] = 10.0;
    assert_eq!(v, Vec4::new(10.0, 1.0, 1.0, 1.0));
}

#[test]
fn as_ref_array() {
    assert_eq!(Vec4::ONE.as_ref(), &[1.0; 4]);
}

#[test]
fn as_mut_array() {
    let mut v: Vec3 = Vec3::ONE;
    let r: &mut [f32; 3] = v.as_mut();

    r[0] = 10.0;

    assert_eq!(v, Vec3::new(10.0, 1.0, 1.0));
}

#[test]
fn sum() {
    let vecs: [Vec4; 3] = [
        Vec4::new(1.0, 2.0, 3.0, 1.0),
        Vec4::new(4.0, 5.0, 6.0, 1.0),
        Vec4::new(-1.0, 0.0, 1.0, 1.0),
    ];

    let total: Vec4 = vecs.into_iter().sum();
    assert_eq!(total, Vec4::new(4.0, 7.0, 10.0, 3.0));
}

#[test]
fn sum_borrowed() {
    let vecs: [Vec4; 3] = [
        Vec4::new(1.0, 2.0, 3.0, 1.0),
        Vec4::new(4.0, 5.0, 6.0, 1.0),
        Vec4::new(-1.0, 0.0, 1.0, 1.0),
    ];

    let total: Vec4 = vecs.iter().sum();
    assert_eq!(total, Vec4::new(4.0, 7.0, 10.0, 3.0));
}

#[test]
fn product() {
    let vecs: [Vec4; 3] = [
        Vec4::new(1.0, 2.0, 3.0, 1.0),
        Vec4::new(4.0, 5.0, 6.0, 1.0),
        Vec4::new(-1.0, 0.0, 1.0, 1.0),
    ];

    let total: Vec4 = vecs.into_iter().product();
    assert_eq!(total, Vec4::new(-4.0, 0.0, 18.0, 1.0));
}

#[test]
fn product_borrowed() {
    let vecs: [Vec4; 3] = [
        Vec4::new(1.0, 2.0, 3.0, 1.0),
        Vec4::new(4.0, 5.0, 6.0, 1.0),
        Vec4::new(-1.0, 0.0, 1.0, 1.0),
    ];

    let total: Vec4 = vecs.iter().product();
    assert_eq!(total, Vec4::new(-4.0, 0.0, 18.0, 1.0));
}

#[test]
fn display() {
    assert_eq!(format!("{}", Vec4::ONE), "(1, 1, 1, 1)");
}

#[test]
fn display_with_precision() {
    assert_eq!(format!("{:.2}", Vec4::ONE), "(1.00, 1.00, 1.00, 1.00)");
}

#[test]
fn default() {
    assert_eq!(Vec4::default(), Vec4::ZERO);
}

#[test]
fn from_array_trait() {
    assert_eq!(
        Vec4::from([1.0, 2.0, 3.0, 4.0]),
        Vec4::new(1.0, 2.0, 3.0, 4.0)
    );
}

#[test]
fn array_from_vec4() {
    assert_eq!(
        <[f32; 4]>::from(Vec4::new(1.0, 2.0, 3.0, 4.0)),
        [1.0, 2.0, 3.0, 4.0]
    );
}

#[test]
fn from_slice_trait() {
    let slice: &[f32] = &[1.0, 2.0, 3.0, 4.0];
    assert_eq!(Vec4::from(slice), Vec4::new(1.0, 2.0, 3.0, 4.0));
}

#[test]
#[should_panic(expected = "index out of bounds")]
fn from_slice_trait_panics_if_too_short() {
    let slice: &[f32] = &[1.0, 2.0, 3.0];
    let _ = Vec4::from(slice);
}

#[test]
fn from_vec2_trait() {
    assert_eq!(
        Vec4::from(Vec2::new(1.0, 2.0)),
        Vec4::new(1.0, 2.0, 0.0, 0.0)
    );
}

#[test]
fn from_vec3_trait() {
    assert_eq!(
        Vec4::from(Vec3::new(1.0, 2.0, 3.0)),
        Vec4::new(1.0, 2.0, 3.0, 0.0)
    );
}

#[test]
fn from_quat_trait() {
    assert_eq!(Vec4::from(Quat::IDENTITY), Vec4::W);
}
