use std::f32::consts::{FRAC_PI_4, PI};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#[test]
fn length_one_is_sqrt_3() {
    assert_eq!(Vec3::ONE.length(), 3_f32.sqrt());
}

#[test]
fn length_squared_one_is_3() {
    assert_eq!(Vec3::ONE.length_squared(), 3.0);
}

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

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

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

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

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

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

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

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

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

#[test]
fn cross_right_up_is_forward() {
    assert_eq!(Vec3::X.cross(Vec3::Y), Vec3::Z);
}

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

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

#[test]
fn project_parallel_stays_same() {
    assert_eq!((Vec3::Y * 5.0).project(Vec3::Y), Vec3::Y * 5.0);
}

#[test]
fn project_scaled_on_normal() {
    assert_eq!((Vec3::Y * 5.0).project(Vec3::Y * 2.0), Vec3::Y * 5.0);
}

#[test]
fn project_perpendicular_is_zero() {
    assert_eq!((Vec3::X * 5.0).project(Vec3::Y), Vec3::ZERO);
}

#[test]
fn project_on_plane_removes_normal_component() {
    assert_eq!(
        Vec3::new(1.0, 2.0, 3.0).project_on_plane(Vec3::Y),
        Vec3::new(1.0, 0.0, 3.0)
    );
}

#[test]
fn project_on_plane_zero_normal_returns_self() {
    assert_eq!(
        Vec3::new(1.0, 2.0, 3.0).project_on_plane(Vec3::ZERO),
        Vec3::new(1.0, 2.0, 3.0)
    );
}

#[test]
fn project_on_plane_parallel_returns_self() {
    assert_eq!(Vec3::X.project_on_plane(Vec3::Y), Vec3::X);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#[test]
fn slerp_at_0_returns_start() {
    assert_eq!(Vec3::X.slerp(Vec3::Y, 0.0), Vec3::X);
}

#[test]
fn slerp_at_1_returns_end() {
    assert_eq!(Vec3::X.slerp(Vec3::Y, 1.0), Vec3::Y);
}

#[test]
fn slerp_at_half_returns_midpoint() {
    assert_eq!(
        Vec3::X.slerp(Vec3::Y, 0.5),
        Vec3::new(0.70710678, 0.70710678, 0.0)
    );
}

#[test]
fn slerp_clamped_at_negative_returns_start() {
    assert_eq!(Vec3::X.slerp_clamped(Vec3::Y, -1.0), Vec3::X);
}

#[test]
fn slerp_clamped_at_2_returns_end() {
    assert_eq!(Vec3::X.slerp_clamped(Vec3::Y, 2.0), Vec3::Y);
}

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

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

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

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

#[test]
fn rotate_towards_does_not_overshoot() {
    assert_eq!(
        Vec3::X.rotate_towards(Vec3::Y, f32::MAX, Vec3::X.length()),
        Vec3::Y
    );
}

#[test]
fn rotate_towards_partial_rotation() {
    assert_eq!(
        Vec3::X.rotate_towards(Vec3::Y, FRAC_PI_4, Vec3::X.length()),
        Vec3::new(0.70710678, 0.70710678, 0.0)
    );
}

#[test]
fn rotate_towards_zero_angle_no_rotation() {
    assert_eq!(
        Vec3::X.rotate_towards(Vec3::Y, 0.0, Vec3::X.length()),
        Vec3::X
    );
}

#[test]
fn rotate_towards_already_aligned() {
    assert_eq!(
        Vec3::X.rotate_towards(Vec3::X, FRAC_PI_4, Vec3::X.length()),
        Vec3::X
    );
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#[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!("{}", Vec3::ONE), "(1, 1, 1)");
}

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

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

#[test]
fn array_from_vec3() {
    assert_eq!(<[f32; 3]>::from(Vec3::new(1.0, 2.0, 3.0)), [1.0, 2.0, 3.0]);
}

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

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

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

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

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