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

use roofy::{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 from_array() {
    assert_eq!(Vec2::from_array([1.0, 1.0]), Vec2::ONE);
}

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

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

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

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

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

#[test]
fn from_vec3() {
    assert_eq!(Vec2::from_vec3(Vec3::ONE), Vec2::ONE);
}

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

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

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

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

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

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

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

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

#[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 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.is_normalized());
}

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

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

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

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

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

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

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

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

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

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

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

#[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 angle_between() {
    assert_eq!(Vec2::X.angle_between(Vec2::Y), 90_f32.to_radians());
}

#[test]
fn angle_between_degrees() {
    assert_eq!(Vec2::X.angle_between_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 signum_positive() {
    assert_eq!(Vec2::ONE.signum(), Vec2::ONE);
}

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

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

#[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 round() {
    assert_eq!(Vec2::new(0.5, 1.4).round(), Vec2::new(1.0, 1.0));
}

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

#[test]
fn ceil() {
    assert_eq!(Vec2::new(0.5, 1.4).ceil(), Vec2::new(1.0, 2.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 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 f32_add_vec2() {
    assert_eq!(1.0 + Vec2::ONE, 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 f32_mul_vec2() {
    assert_eq!(2.0 * Vec2::splat(2.0), 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 sum() {
    let vecs: [Vec2; 3] = [
        Vec2::new(1.0, 2.0),
        Vec2::new(4.0, 5.0),
        Vec2::new(-1.0, 0.0),
    ];

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

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

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

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

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

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

    let total: Vec2 = vecs.iter().product();
    assert_eq!(total, Vec2::new(-4.0, 0.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 default() {
    assert_eq!(Vec2::default(), Vec2::ZERO);
}

#[test]
fn from_array_trait() {
    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_trait() {
    let slice: &[f32] = &[1.0, 2.0];
    assert_eq!(Vec2::from(slice), Vec2::new(1.0, 2.0));
}

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

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

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