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

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

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

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

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

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

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

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

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

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

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

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

#[test]
fn new() {
    assert_eq!(Vec2::new(1.0, 2.0), Vec2 { x: 1.0, y: 2.0 });
}

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

#[test]
fn length_one_is_sqrt_2() {
    assert_eq!(Vec2::ONE.length(), 2_f32.sqrt());
}

#[test]
fn length_squared_one_is_2() {
    assert_eq!(Vec2::ONE.length_squared(), 2.0);
}

#[test]
fn normalize_sets_length_approx_1() {
    let mut v: Vec2 = Vec2::ONE;
    v.normalize();
    assert!((v.length() - 1.0).abs() < 1e-5);
}

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

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

#[test]
fn normalized_near_zero_is_zero() {
    assert_eq!(Vec2::new(1e-7, 0.0).normalized(), Vec2::ZERO);
}

#[test]
fn is_normalized() {
    assert!(Vec2::X.is_normalized());
    assert!(!Vec2::ONE.is_normalized());
}

#[test]
fn dot_perpendicular_is_zero() {
    assert_eq!(Vec2::X.dot(Vec2::Y), 0.0);
}

#[test]
fn dot_parallel_is_one() {
    assert_eq!(Vec2::X.dot(Vec2::X), 1.0);
}

#[test]
fn angle() {
    assert_eq!(Vec2::X.angle(Vec2::Y), 90_f32.to_radians());
}

#[test]
fn angle_degrees() {
    assert_eq!(Vec2::X.angle_degrees(Vec2::Y), 90.0);
}

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

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

#[test]
fn perpendicular() {
    assert_eq!(Vec2::X.perpendicular(), Vec2::Y);
}

#[test]
fn reflect() {
    assert_eq!(Vec2::new(1.0, -1.0).reflect(Vec2::Y), Vec2::new(1.0, 1.0));
}

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

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

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

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

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

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

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

#[test]
fn cos() {
    assert!(Vec2::new(PI / 2.0, PI).cos().approx_eq(Vec2::NEG_Y));
}

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

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

#[test]
fn clamp_length_scales_down() {
    assert_eq!(
        Vec2::new(6.0, 0.0).clamp_length(1.0, 3.0),
        Vec2::new(3.0, 0.0)
    );
}

#[test]
fn clamp_length_scales_up() {
    assert_eq!(
        Vec2::new(0.5, 0.0).clamp_length(1.0, 3.0),
        Vec2::new(1.0, 0.0)
    );
}

#[test]
fn clamp_length_in_range() {
    assert_eq!(
        Vec2::new(2.0, 0.0).clamp_length(1.0, 3.0),
        Vec2::new(2.0, 0.0)
    );
}

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

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

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

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

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

#[test]
fn move_towards_reaches_target() {
    assert_eq!(Vec2::ZERO.move_towards(Vec2::X, 1.0), Vec2::X);
}

#[test]
fn move_towards_does_not_overshoot() {
    assert_eq!(Vec2::ZERO.move_towards(Vec2::X, 5.0), Vec2::X);
}

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

#[test]
fn move_towards_already_at_end() {
    assert_eq!(Vec2::X.move_towards(Vec2::X, 1.0), Vec2::X);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    r[0] = 10.0;

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

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

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

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

#[test]
fn array_from_vec2() {
    assert_eq!(<[f32; 2]>::from(Vec2::new(1.0, 2.0)), [1.0, 2.0]);
}

#[test]
fn from_slice() {
    let data: [f32; 2] = [1.0, 2.0];
    let v: Vec2 = Vec2::from(&data[..]);
    assert_eq!(v, Vec2::new(1.0, 2.0));
}

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

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

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

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