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

#[test]
fn const_identity() {
    assert_eq!(Quat::IDENTITY, Quat::new(0.0, 0.0, 0.0, 1.0));
}

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

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

#[test]
fn from_axis_angle_identity() {
    assert_eq!(Quat::from_axis_angle(Vec3::Y, 0.0), Quat::IDENTITY);
}

#[test]
fn to_axis_angle_near_identity_returns_x_axis() {
    let (axis, angle): (Vec3, f32) = Quat::IDENTITY.to_axis_angle();

    assert_eq!(axis, Vec3::X);
    assert_eq!(angle, 0.0);
}

#[test]
fn to_axis_angle_degrees_roundtrip() {
    let q: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 90.0);
    let (axis, angle): (Vec3, f32) = q.to_axis_angle_degrees();

    assert!(axis.distance(Vec3::Y) < 1e-5);
    assert_eq!(angle, 90.0);
}

#[test]
fn from_euler_zero_is_identity() {
    assert_eq!(Quat::from_euler_zyx(0.0, 0.0, 0.0), Quat::IDENTITY);
}

#[test]
fn from_euler_zyx_yaw() {
    let q: Quat = Quat::from_euler_degrees_zyx(90.0, 0.0, 0.0);
    let v: Vec3 = q * Vec3::X;

    assert!((v.x).abs() < 1e-5);
    assert!((v.y - 1.0).abs() < 1e-5);
    assert!((v.z).abs() < 1e-5);
}

#[test]
fn from_euler_degrees_zero_is_identity() {
    assert_eq!(Quat::from_euler_degrees_zyx(0.0, 0.0, 0.0), Quat::IDENTITY);
}

#[test]
fn from_euler_degrees_zyx_yaw() {
    let q: Quat = Quat::from_euler_degrees_zyx(90.0, 0.0, 0.0);
    let v: Vec3 = q * Vec3::X;

    assert!((v.x).abs() < 1e-5);
    assert!((v.y - 1.0).abs() < 1e-5);
    assert!((v.z).abs() < 1e-5);
}

#[test]
fn to_euler_zyx() {
    let q: Quat = Quat::from_euler_zyx(0.3, 0.5, -0.8);
    let e: Vec3 = q.to_euler_zyx();
    let q2: Quat = Quat::from_euler_zyx(e.x, e.y, e.z);

    assert!((q.x - q2.x).abs() < 1e-5);
    assert!((q.y - q2.y).abs() < 1e-5);
    assert!((q.z - q2.z).abs() < 1e-5);
    assert!((q.w - q2.w).abs() < 1e-5);
}

#[test]
fn to_euler_degrees_zyx() {
    let q: Quat = Quat::from_euler_zyx(0.3, 0.5, -0.8);
    let e: Vec3 = q.to_euler_degrees_zyx();
    let q2: Quat = Quat::from_euler_degrees_zyx(e.x, e.y, e.z);

    assert!((q.x - q2.x).abs() < 1e-5);
    assert!((q.y - q2.y).abs() < 1e-5);
    assert!((q.z - q2.z).abs() < 1e-5);
    assert!((q.w - q2.w).abs() < 1e-5);
}

#[test]
fn from_look_rotation_forward_is_z() {
    assert_eq!(
        Quat::from_look_rotation(Vec3::Z, Vec3::Y) * Vec3::Z,
        Vec3::Z
    );
}

#[test]
fn from_look_rotation_rotates_to_x() {
    let q: Quat = Quat::from_look_rotation(Vec3::X, Vec3::Y);

    assert!((q * Vec3::Z).distance(Vec3::X) < 1e-5);
}

#[test]
fn from_look_rotation_near_zero_is_identity() {
    assert_eq!(
        Quat::from_look_rotation(Vec3::ZERO, Vec3::Y),
        Quat::IDENTITY
    );
}

#[test]
fn from_look_rotation_result_is_normalized() {
    assert!(Quat::from_look_rotation(Vec3::ONE.normalized(), Vec3::Y).is_normalized());
}

#[test]
fn from_look_rotation_lh_forward_is_neg_z() {
    assert_eq!(
        Quat::from_look_rotation_lh(Vec3::NEG_Z, Vec3::Y) * Vec3::NEG_Z,
        Vec3::NEG_Z
    );
}

#[test]
fn from_to_rotation_same_vector_is_identity() {
    assert_eq!(Quat::from_to_rotation(Vec3::X, Vec3::X) * Vec3::X, Vec3::X);
}

#[test]
fn from_to_rotation_near_zero_from_is_identity() {
    assert_eq!(Quat::from_to_rotation(Vec3::ZERO, Vec3::Y), Quat::IDENTITY);
}

#[test]
fn from_to_rotation_near_zero_to_is_identity() {
    assert_eq!(Quat::from_to_rotation(Vec3::X, Vec3::ZERO), Quat::IDENTITY);
}

#[test]
fn from_to_rotation_x_to_y() {
    let q: Quat = Quat::from_to_rotation(Vec3::X, Vec3::Y);

    assert!((q * Vec3::X).distance(Vec3::Y) < 1e-5);
}

#[test]
fn from_to_rotation_x_to_neg_x() {
    let q: Quat = Quat::from_to_rotation(Vec3::X, Vec3::NEG_X);

    assert!((q * Vec3::X).distance(Vec3::NEG_X) < 1e-5);
}

#[test]
fn from_to_rotation_opposite_vectors() {
    let q: Quat = Quat::from_to_rotation(Vec3::X, Vec3::NEG_X);

    assert!((q * Vec3::X).distance(Vec3::NEG_X) < 1e-5);
}

#[test]
fn from_to_rotation_preserves_length() {
    let q: Quat = Quat::from_to_rotation(Vec3::X, Vec3::ONE).normalized();
    let v: Vec3 = Vec3::splat(2.0);

    assert!((v.length() - (q * v).length()).abs() < 1e-5);
}

#[test]
fn from_to_rotation_result_is_normalized() {
    assert!(Quat::from_to_rotation(Vec3::X, Vec3::Y).is_normalized());
}

#[test]
fn length_identity_is_1() {
    assert_eq!(Quat::IDENTITY.length(), 1.0);
}

#[test]
fn length_squared_identity_is_1() {
    assert_eq!(Quat::IDENTITY.length_squared(), 1.0);
}

#[test]
fn normalize_sets_length_approx_1() {
    let mut q: Quat = Quat::new(1.0, 2.0, 3.0, 4.0);
    q.normalize();
    assert!((q.length() - 1.0).abs() < 1e-5);
}

#[test]
fn normalize_near_zero_sets_identity() {
    let mut q: Quat = Quat::new(1e-7, 0.0, 0.0, 0.0);
    q.normalize();
    assert_eq!(q, Quat::IDENTITY);
}

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

#[test]
fn normalized_near_zero_is_identity() {
    assert_eq!(Quat::new(1e-7, 0.0, 0.0, 0.0).normalized(), Quat::IDENTITY);
}

#[test]
fn is_normalized() {
    assert!(Quat::IDENTITY.is_normalized());
    assert!(!Quat::new(1.0, 1.0, 1.0, 1.0).is_normalized());
}

#[test]
fn dot() {
    assert_eq!(Quat::IDENTITY.dot(Quat::IDENTITY), 1.0);
}

#[test]
fn conjugate() {
    let mut q: Quat = Quat::new(1.0, 2.0, 3.0, 4.0);
    q.conjugate();
    assert_eq!(q, Quat::new(-1.0, -2.0, -3.0, 4.0));
}

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

#[test]
fn inverse_identity() {
    let mut q: Quat = Quat::IDENTITY;
    q.inverse();
    assert_eq!(q, Quat::IDENTITY);
}

#[test]
fn inverse_near_zero_returns_identity() {
    let mut q: Quat = Quat::new(0.0, 0.0, 0.0, 0.0);
    q.inverse();
    assert_eq!(q, Quat::IDENTITY);
}

#[test]
fn inversed_identity() {
    assert_eq!(Quat::IDENTITY.inversed(), Quat::IDENTITY);
}

#[test]
fn inversed_cancels_rotation() {
    let q: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 90.0);

    assert_eq!(q * q.inversed(), Quat::IDENTITY);
}

#[test]
fn inversed_near_zero_returns_identity() {
    assert_eq!(Quat::new(0.0, 0.0, 0.0, 0.0).inversed(), Quat::IDENTITY);
}

#[test]
fn angle_identity() {
    assert_eq!(Quat::IDENTITY.angle(), 0.0)
}

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

#[test]
fn angle_half_turn() {
    let q: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 45.0);

    assert!((q.angle_degrees() - 45.0).abs() < 1e-5);
}

#[test]
fn lerp_at_0_returns_start() {
    let a: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 0.0);
    let b: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 90.0);

    assert_eq!(a.lerp(b, 0.0), a);
}

#[test]
fn lerp_at_1_returns_end() {
    let a: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 0.0);
    let b: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 90.0);
    let result: Quat = a.lerp(b, 1.0);

    assert!((result.x - b.x).abs() < 1e-5);
    assert!((result.y - b.y).abs() < 1e-5);
    assert!((result.z - b.z).abs() < 1e-5);
    assert!((result.w - b.w).abs() < 1e-5);
}

#[test]
fn lerp_clamped_at_negative_returns_start() {
    let a: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 0.0);
    let b: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 90.0);

    assert_eq!(a.lerp_clamped(b, -1.0), a);
}

#[test]
fn lerp_clamped_at_2_returns_end() {
    let a: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 0.0);
    let b: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 90.0);
    let result: Quat = a.lerp_clamped(b, 2.0);

    assert!((result.x - b.x).abs() < 1e-5);
    assert!((result.y - b.y).abs() < 1e-5);
    assert!((result.z - b.z).abs() < 1e-5);
    assert!((result.w - b.w).abs() < 1e-5);
}

#[test]
fn lerp_result_is_normalized() {
    let a: Quat = Quat::from_axis_angle_degrees(Vec3::X, 0.0);
    let b: Quat = Quat::from_axis_angle_degrees(Vec3::X, 90.0);

    assert!(a.lerp(b, 0.5).is_normalized());
}

#[test]
fn slerp_at_0_returns_start() {
    let a: Quat = Quat::from_axis_angle_degrees(Vec3::Z, 0.0);
    let b: Quat = Quat::from_axis_angle_degrees(Vec3::Z, 90.0);

    assert_eq!(a.slerp(b, 0.0), a);
}

#[test]
fn slerp_at_1_returns_end() {
    let a: Quat = Quat::from_axis_angle_degrees(Vec3::Z, 0.0);
    let b: Quat = Quat::from_axis_angle_degrees(Vec3::Z, 90.0);
    let result: Quat = a.slerp(b, 1.0);

    assert!((result.x - b.x).abs() < 1e-5);
    assert!((result.y - b.y).abs() < 1e-5);
    assert!((result.z - b.z).abs() < 1e-5);
    assert!((result.w - b.w).abs() < 1e-5);
}

#[test]
fn slerp_midpoint_angle() {
    let a: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 0.0);
    let b: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 90.0);
    let angle: f32 = a.slerp(b, 0.5).angle_degrees();

    assert!((angle - 90.0 * 0.5).abs() < 1e-4);
}

#[test]
fn slerp_clamped_at_negative_returns_start() {
    let a: Quat = Quat::from_axis_angle_degrees(Vec3::Z, 0.0);
    let b: Quat = Quat::from_axis_angle_degrees(Vec3::Z, 90.0);

    assert_eq!(a.slerp_clamped(b, -1.0), a);
}

#[test]
fn slerp_clamped_at_2_returns_end() {
    let a: Quat = Quat::from_axis_angle_degrees(Vec3::Z, 0.0);
    let b: Quat = Quat::from_axis_angle_degrees(Vec3::Z, 90.0);
    let result: Quat = a.slerp_clamped(b, 2.0);

    assert!((result.x - b.x).abs() < 1e-5);
    assert!((result.y - b.y).abs() < 1e-5);
    assert!((result.z - b.z).abs() < 1e-5);
    assert!((result.w - b.w).abs() < 1e-5);
}

#[test]
fn slerp_result_is_normalized() {
    let a: Quat = Quat::from_axis_angle_degrees(Vec3::Z, 0.0);
    let b: Quat = Quat::from_axis_angle_degrees(Vec3::Z, 90.0);

    assert!(a.slerp(b, 0.5).is_normalized());
}

#[test]
fn approx_eq_identity_is_equal() {
    assert!(Quat::IDENTITY.approx_eq(Quat::IDENTITY));
}

#[test]
fn approx_eq_zero_is_not_identity() {
    assert!(!Quat::new(0.0, 0.0, 0.0, 0.0).approx_eq(Quat::IDENTITY));
}

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

#[test]
fn is_finite_identity_returns_true() {
    assert!(Quat::IDENTITY.is_finite());
}

#[test]
fn is_finite_infinity_returns_false() {
    assert!(!Quat::new(f32::INFINITY, 0.0, 0.0, 0.0).is_finite());
}

#[test]
fn is_nan_identity_returns_false() {
    assert!(!Quat::IDENTITY.is_nan());
}

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

#[test]
fn mul() {
    assert_eq!(
        Quat::new(2.0, 3.0, 4.0, 1.0) * Quat::new(6.0, 7.0, 8.0, 5.0),
        Quat::new(12.0, 30.0, 24.0, -60.0)
    );
}

#[test]
fn mul_identity() {
    assert_eq!(
        Quat::new(2.0, 3.0, 4.0, 1.0) * Quat::IDENTITY,
        Quat::new(2.0, 3.0, 4.0, 1.0)
    );
}

#[test]
fn mul_vec3() {
    let q: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 90.0);
    let result: Vec3 = q * Vec3::X;
    assert!(result.distance(Vec3::NEG_Z) < 1e-5);
}

#[test]
fn mul_vec3_identity() {
    assert_eq!(
        Quat::IDENTITY * Vec3::new(1.0, 2.0, 3.0),
        Vec3::new(1.0, 2.0, 3.0)
    );
}

#[test]
fn neg() {
    assert_eq!(
        -Quat::new(1.0, 2.0, 3.0, 4.0),
        Quat::new(-1.0, -2.0, -3.0, -4.0)
    );
}

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

#[test]
fn array_from_quat() {
    assert_eq!(
        <[f32; 4]>::from(Quat::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 q: Quat = Quat::from(&data[..]);
    assert_eq!(q, Quat::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 _q: Quat = Quat::from(&data[..]);
}

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

#[test]
fn as_ref_array() {
    let r: &[f32; 4] = Quat::IDENTITY.as_ref();
    assert_eq!(r, &[0.0, 0.0, 0.0, 1.0]);
}

#[test]
fn as_ref_slice() {
    let r: &[f32] = Quat::IDENTITY.as_ref();
    assert_eq!(r, &[0.0, 0.0, 0.0, 1.0]);
}

#[test]
fn as_mut_array() {
    let mut q: Quat = Quat::IDENTITY;
    let r: &mut [f32; 4] = q.as_mut();

    r[0] = 10.0;

    assert_eq!(q, Quat::new(10.0, 0.0, 0.0, 1.0));
}

#[test]
fn as_mut_slice() {
    let mut q: Quat = Quat::IDENTITY;
    let r: &mut [f32] = q.as_mut();

    r[0] = 10.0;

    assert_eq!(q, Quat::new(10.0, 0.0, 0.0, 1.0));
}

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