use std::f32::consts::FRAC_2_PI;

use roofy::{Affine3, Mat3, Mat4, Quat, Vec3};

#[test]
fn const_nan() {
    let a: Affine3 = Affine3::NAN;
    let m: [f32; 9] = a.matrix.to_array();
    let v: [f32; 3] = a.translation.to_array();

    assert!(m.iter().all(|x| x.is_nan()));
    assert!(v.iter().all(|x| x.is_nan()));
}

#[test]
fn const_zero() {
    assert_eq!(Affine3::ZERO, Affine3::from_mat3(Mat3::ZERO));
}

#[test]
fn const_identity() {
    assert_eq!(Affine3::IDENTITY, Affine3::from_mat3(Mat3::IDENTITY));
}

#[test]
fn from_mat3() {
    assert_eq!(
        Affine3::from_mat3(Mat3::IDENTITY),
        Affine3::from_mat3(Mat3::IDENTITY)
    );
}

#[test]
fn from_mat3_translation() {
    assert_eq!(
        Affine3::from_mat3_translation(Mat3::IDENTITY, Vec3::ZERO),
        Affine3 {
            matrix: Mat3::IDENTITY,
            translation: Vec3::ZERO
        }
    );
}

#[test]
fn from_cols() {
    assert_eq!(
        Affine3::from_cols(Vec3::X, Vec3::Y, Vec3::Z, Vec3::ZERO),
        Affine3::IDENTITY
    );
}

#[test]
fn from_array() {
    assert_eq!(
        Affine3::from_array([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]),
        Affine3::IDENTITY
    );
}

#[test]
fn to_array() {
    assert_eq!(
        Affine3::IDENTITY.to_array(),
        [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]
    );
}

#[test]
fn from_slice() {
    assert_eq!(
        Affine3::from_slice(&[1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]),
        Affine3::IDENTITY
    );
}

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

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

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

#[test]
fn from_translation() {
    assert_eq!(
        Affine3::from_translation(Vec3::ONE),
        Affine3::from_mat3_translation(Mat3::IDENTITY, Vec3::ONE)
    );
}

#[test]
fn from_scale() {
    assert_eq!(
        Affine3::from_scale(Vec3::ONE),
        Affine3::from_mat3(Mat3::from_diagonal(Vec3::ONE))
    );
}

#[test]
fn from_rotation_x() {
    assert_eq!(
        Affine3::from_rotation_x(FRAC_2_PI),
        Affine3::from_mat3(Mat3::from_rotation_x(FRAC_2_PI))
    );
}

#[test]
fn from_rotation_x_degrees() {
    assert_eq!(
        Affine3::from_rotation_x_degrees(90.0),
        Affine3::from_mat3(Mat3::from_rotation_x_degrees(90.0))
    );
}

#[test]
fn from_rotation_y() {
    assert_eq!(
        Affine3::from_rotation_y(FRAC_2_PI),
        Affine3::from_mat3(Mat3::from_rotation_y(FRAC_2_PI))
    );
}

#[test]
fn from_rotation_y_degrees() {
    assert_eq!(
        Affine3::from_rotation_y_degrees(90.0),
        Affine3::from_mat3(Mat3::from_rotation_y_degrees(90.0))
    );
}

#[test]
fn from_rotation_z() {
    assert_eq!(
        Affine3::from_rotation_z(FRAC_2_PI),
        Affine3::from_mat3(Mat3::from_rotation_z(FRAC_2_PI))
    );
}

#[test]
fn from_rotation_z_degrees() {
    assert_eq!(
        Affine3::from_rotation_z_degrees(90.0),
        Affine3::from_mat3(Mat3::from_rotation_z_degrees(90.0))
    );
}

#[test]
fn from_trs() {
    assert_eq!(
        Affine3::from_trs(Vec3::ZERO, Quat::IDENTITY, Vec3::ONE),
        Affine3::from_mat3(Mat3::from_rs(Quat::IDENTITY, Vec3::ONE))
    );
}

#[test]
fn from_trs_euler() {
    assert_eq!(
        Affine3::from_trs_euler(Vec3::ZERO, Vec3::ONE, Vec3::ONE),
        Affine3::from_mat3(Mat3::from_rs_euler(Vec3::ONE, Vec3::ONE))
    );
}

#[test]
fn from_trs_euler_degrees() {
    assert_eq!(
        Affine3::from_trs_euler_degrees(Vec3::ZERO, Vec3::ONE, Vec3::ONE),
        Affine3::from_mat3(Mat3::from_rs_euler_degrees(Vec3::ONE, Vec3::ONE))
    );
}

#[test]
fn from_quat() {
    assert_eq!(
        Affine3::from_quat(Quat::IDENTITY),
        Affine3::from_mat3_translation(Mat3::from_quat(Quat::IDENTITY), Vec3::ZERO)
    );
}

#[test]
fn from_axis_angle() {
    assert_eq!(
        Affine3::from_axis_angle(Vec3::X, FRAC_2_PI),
        Affine3::from_mat3_translation(Mat3::from_axis_angle(Vec3::X, FRAC_2_PI), Vec3::ZERO)
    );
}

#[test]
fn from_axis_angle_degrees() {
    assert_eq!(
        Affine3::from_axis_angle_degrees(Vec3::X, 90.0),
        Affine3::from_mat3(Mat3::from_axis_angle_degrees(Vec3::X, 90.0))
    );
}

#[test]
fn from_mat4() {
    assert_eq!(
        Affine3::from_mat4(Mat4::IDENTITY),
        Affine3::from_mat3(Mat3::IDENTITY)
    );
}

#[test]
fn to_mat4_identity_is_identity() {
    assert_eq!(Affine3::IDENTITY.to_mat4(), Mat4::IDENTITY);
}

#[test]
fn to_mat4() {
    assert_eq!(
        Affine3::from_translation(Vec3::ONE).to_mat4(),
        Mat4::from_translation(Vec3::ONE)
    );
}

#[test]
fn look_to_rh_matches_mat4() {
    let eye: Vec3 = Vec3::new(1.0, 2.0, 3.0);
    let dir: Vec3 = Vec3::new(0.0, 0.0, -1.0);
    let up: Vec3 = Vec3::Y;

    let a: Affine3 = Affine3::look_to_rh(eye, dir, up);
    let m: Mat4 = Mat4::look_to_rh(eye, dir, up);

    assert_eq!(a.to_mat4(), m);
}

#[test]
fn look_to_lh_matches_mat4() {
    let eye: Vec3 = Vec3::new(1.0, 2.0, 3.0);
    let dir: Vec3 = Vec3::new(0.0, 0.0, 1.0);
    let up: Vec3 = Vec3::Y;

    let a: Affine3 = Affine3::look_to_lh(eye, dir, up);
    let m: Mat4 = Mat4::look_to_lh(eye, dir, up);

    assert_eq!(a.to_mat4(), m);
}

#[test]
fn look_at_rh_matches_mat4() {
    let eye: Vec3 = Vec3::new(0.0, 0.0, 5.0);
    let center: Vec3 = Vec3::ZERO;
    let up: Vec3 = Vec3::Y;

    let a: Affine3 = Affine3::look_at_rh(eye, center, up);
    let m: Mat4 = Mat4::look_at_rh(eye, center, up);

    assert_eq!(a.to_mat4(), m);
}

#[test]
fn look_at_lh_matches_mat4() {
    let eye: Vec3 = Vec3::new(0.0, 0.0, -5.0);
    let center: Vec3 = Vec3::ZERO;
    let up: Vec3 = Vec3::Y;

    let a: Affine3 = Affine3::look_at_lh(eye, center, up);
    let m: Mat4 = Mat4::look_at_lh(eye, center, up);

    assert_eq!(a.to_mat4(), m);
}

#[test]
fn try_inverse_singular_is_none() {
    assert!(Affine3::ZERO.try_inverse().is_none());
}

#[test]
fn inverse_identity_is_identity() {
    assert_eq!(Affine3::IDENTITY.inverse(), Affine3::IDENTITY);
}

#[test]
fn inverse_times_original_is_identity() {
    let cases: [Affine3; 4] = [
        Affine3::IDENTITY,
        Affine3::from_scale(Vec3::new(2.0, 0.5, -3.0)),
        Affine3::from_quat(Quat::IDENTITY),
        Affine3::from_translation(Vec3::ONE),
    ];

    for m in cases {
        let id: Affine3 = m * m.inverse();
        assert!(id.approx_eq(Affine3::IDENTITY));
    }
}

#[test]
fn inverse_scale() {
    assert_eq!(
        Affine3::from_scale(Vec3::new(2.0, 3.0, 4.0)).inverse(),
        Affine3::from_scale(Vec3::new(0.5, 1.0 / 3.0, 1.0 / 4.0))
    );
}

#[test]
#[should_panic(expected = "affine transform not invertible")]
fn inverse_singular_panics() {
    let _ = Affine3::ZERO.inverse();
}

#[test]
fn is_invertible_identity() {
    assert!(Affine3::IDENTITY.is_invertible());
}

#[test]
fn is_invertible_zero_is_not_invertible() {
    assert!(!Affine3::ZERO.is_invertible());
}

#[test]
fn transform_point_identity_is_self() {
    let v: Vec3 = Vec3::new(1.0, 2.0, 3.0);

    assert_eq!(Affine3::IDENTITY.transform_point(v), v);
}

#[test]
fn transform_point_applies_translation() {
    let a: Affine3 = Affine3::from_translation(Vec3::new(10.0, 20.0, 30.0));
    let r: Vec3 = a.transform_point(Vec3::new(1.0, 2.0, 3.0));

    assert_eq!(r, Vec3::new(11.0, 22.0, 33.0));
}

#[test]
fn transform_point_applies_scale() {
    let a: Affine3 = Affine3::from_scale(Vec3::new(2.0, 3.0, 4.0));
    let r: Vec3 = a.transform_point(Vec3::ONE);

    assert_eq!(r, Vec3::new(2.0, 3.0, 4.0));
}

#[test]
fn transform_vector_identity_is_self() {
    let v: Vec3 = Vec3::new(1.0, 2.0, 3.0);

    assert_eq!(Affine3::IDENTITY.transform_vector(v), v);
}

#[test]
fn transform_vector_ignores_translation() {
    let a: Affine3 = Affine3::from_translation(Vec3::new(10.0, 20.0, 30.0));
    let v: Vec3 = Vec3::new(1.0, 2.0, 3.0);

    assert_eq!(a.transform_vector(v), v);
}

#[test]
fn transform_vector_applies_scale() {
    let a: Affine3 = Affine3::from_scale(Vec3::new(2.0, 3.0, 4.0));
    let r: Vec3 = a.transform_vector(Vec3::ONE);

    assert_eq!(r, Vec3::new(2.0, 3.0, 4.0));
}

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

#[test]
fn approx_eq_zero_is_not_identity() {
    assert!(!Affine3::ZERO.approx_eq(Affine3::IDENTITY));
}

#[test]
fn approx_eq_tiny_difference_is_equal() {
    let mut a: Affine3 = Affine3::IDENTITY;
    a.matrix.col0.x += Affine3::EPSILON * 0.5;

    assert!(Affine3::IDENTITY.approx_eq(a));
}

#[test]
fn is_finite_identity_and_zero() {
    assert!(Affine3::IDENTITY.is_finite());
    assert!(Affine3::ZERO.is_finite());
}

#[test]
fn is_finite_nan() {
    let mut a: Affine3 = Affine3::IDENTITY;
    a.matrix.col0.x = f32::NAN;

    assert!(!a.is_finite());
}

#[test]
fn is_finite_infinity() {
    let mut a: Affine3 = Affine3::IDENTITY;
    a.matrix.col0.x = f32::INFINITY;

    assert!(!a.is_finite());
}

#[test]
fn is_finite_neg_infinity() {
    let mut a: Affine3 = Affine3::IDENTITY;
    a.matrix.col0.x = f32::NEG_INFINITY;

    assert!(!a.is_finite());
}

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

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

#[test]
fn mul_identity_is_noop() {
    let t: Affine3 = Affine3::from_translation(Vec3::new(1.0, 2.0, 3.0));

    assert_eq!(Affine3::IDENTITY * t, t);
    assert_eq!(t * Affine3::IDENTITY, t);
}

#[test]
fn mul_combines_matrix_and_translation_correctly() {
    let a: Affine3 = Affine3::from_mat3_translation(Mat3::from_diagonal(Vec3::splat(2.0)), Vec3::X);
    let b: Affine3 = Affine3::from_mat3_translation(Mat3::IDENTITY, Vec3::Y);

    let result: Affine3 = a * b;

    let expected_matrix: Mat3 = a.matrix * b.matrix;
    let expected_translation: Vec3 = a.matrix.transform(b.translation) + a.translation;

    assert!(result.matrix.approx_eq(expected_matrix));
    assert!(result.translation.approx_eq(expected_translation));
}

#[test]
fn mul_vec3_matches() {
    let a: Affine3 = Affine3::IDENTITY;
    let v: Vec3 = Vec3::ONE;

    assert_eq!(a * v, a.transform_point(v));
}

#[test]
fn display() {
    assert_eq!(
        format!("{}", Affine3::IDENTITY),
        "[(1, 0, 0), (0, 1, 0), (0, 0, 1), (0, 0, 0)]"
    );
}

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

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

#[test]
fn from_array_trait() {
    assert_eq!(
        Affine3::from([
            1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0
        ]),
        Affine3::from_cols(
            Vec3::new(1.0, 2.0, 3.0),
            Vec3::new(4.0, 5.0, 6.0),
            Vec3::new(7.0, 8.0, 9.0),
            Vec3::new(10.0, 11.0, 12.0),
        )
    );
}

#[test]
fn array_from_affine3() {
    assert_eq!(
        <[f32; 12]>::from(Affine3::from_cols(
            Vec3::new(1.0, 2.0, 3.0),
            Vec3::new(4.0, 5.0, 6.0),
            Vec3::new(7.0, 8.0, 9.0),
            Vec3::new(10.0, 11.0, 12.0)
        )),
        [
            1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0
        ]
    );
}

#[test]
fn from_mat4_trait() {
    assert_eq!(
        Affine3::from(Mat4::IDENTITY),
        Affine3::from_mat3(Mat3::IDENTITY)
    );
}
