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

use roofy::math::{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 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.length() - 1.0).abs() < 1e-5);
}

#[test]
fn normalize_near_zero_sets_zero() {
    let mut v: Vec4 = Vec4::new(1e-7, 0.0, 0.0, 0.0);
    v.normalize();
    assert_eq!(v, Vec4::ZERO);
}

#[test]
fn normalized_length_approx_1() {
    assert!((Vec4::ONE.normalized().length() - 1.0).abs() < 1e-5);
}

#[test]
fn normalized_near_zero_is_zero() {
    assert_eq!(Vec4::new(1e-7, 0.0, 0.0, 0.0).normalized(), 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 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 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 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 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 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 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 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 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 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 from_array() {
    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() {
    let data: [f32; 4] = [1.0, 2.0, 3.0, 4.0];
    let v: Vec4 = Vec4::from(&data[..]);
    assert_eq!(v, Vec4::new(1.0, 2.0, 3.0, 4.0));
}

#[test]
#[should_panic(expected = "index out of bounds")]
fn from_slice_panics_if_too_short() {
    let data: [f32; 1] = [1.0];
    let _v: Vec4 = Vec4::from(&data[..]);
}

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

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

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