use std::f32::consts::FRAC_PI_2;

use roofy::{Affine2, Mat2, Mat3, Vec2};

#[test]
fn const_nan() {
    let a: Affine2 = Affine2::NAN;
    let m: [f32; 4] = a.matrix.to_array();
    let v: [f32; 2] = 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!(
        Affine2::ZERO,
        Affine2::from_mat2_translation(Mat2::ZERO, Vec2::ZERO)
    );
}

#[test]
fn const_identity() {
    assert_eq!(
        Affine2::IDENTITY,
        Affine2::from_mat2_translation(Mat2::IDENTITY, Vec2::ZERO)
    );
}

#[test]
fn from_mat2() {
    assert_eq!(
        Affine2::from_mat2(Mat2::IDENTITY),
        Affine2::from_mat2_translation(Mat2::IDENTITY, Vec2::ZERO)
    );
}

#[test]
fn from_mat2_translation() {
    assert_eq!(
        Affine2::from_mat2_translation(Mat2::IDENTITY, Vec2::ZERO),
        Affine2 {
            matrix: Mat2::IDENTITY,
            translation: Vec2::ZERO
        }
    );
}

#[test]
fn from_cols() {
    assert_eq!(
        Affine2::from_cols(Vec2::X, Vec2::Y, Vec2::ZERO),
        Affine2::IDENTITY
    );
}

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

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

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

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

#[test]
fn write_to_slice() {
    let mut slice: [f32; 6] = [0.0; 6];
    Affine2::IDENTITY.write_to_slice(&mut slice);
    assert_eq!(slice, [1.0, 0.0, 0.0, 1.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; 5] = [0.0; 5];
    Affine2::IDENTITY.write_to_slice(&mut slice);
}

#[test]
fn from_translation() {
    assert_eq!(
        Affine2::from_translation(Vec2::ONE),
        Affine2::from_mat2_translation(Mat2::IDENTITY, Vec2::ONE)
    );
}

#[test]
fn from_scale() {
    assert_eq!(
        Affine2::from_scale(Vec2::ONE),
        Affine2::from_mat2(Mat2::from_diagonal(Vec2::ONE))
    );
}

#[test]
fn from_angle() {
    assert_eq!(
        Affine2::from_angle(FRAC_PI_2),
        Affine2::from_mat2(Mat2::from_angle(FRAC_PI_2))
    );
}

#[test]
fn from_angle_degrees() {
    assert_eq!(
        Affine2::from_angle(FRAC_PI_2),
        Affine2::from_angle_degrees(90.0)
    );
}

#[test]
fn from_tas() {
    assert_eq!(
        Affine2::from_tas(Vec2::ZERO, FRAC_PI_2, Vec2::ONE),
        Affine2::from_mat2(Mat2::from_scale_angle(Vec2::ONE, FRAC_PI_2))
    );
}

#[test]
fn from_tas_degrees() {
    assert_eq!(
        Affine2::from_tas_degrees(Vec2::ZERO, 90.0, Vec2::ONE),
        Affine2::from_tas(Vec2::ZERO, FRAC_PI_2, Vec2::ONE)
    );
}

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

#[test]
fn to_mat3_identity_is_identity() {
    assert_eq!(Affine2::IDENTITY.to_mat3(), Mat3::IDENTITY);
}

#[test]
fn to_mat3() {
    assert_eq!(Affine2::from_mat2(Mat2::IDENTITY).to_mat3(), Mat3::IDENTITY);
}

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

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

#[test]
fn inverse_times_original_is_identity() {
    let cases: [Affine2; 3] = [
        Affine2::IDENTITY,
        Affine2::from_scale(Vec2::new(2.0, 0.5)),
        Affine2::from_translation(Vec2::ONE),
    ];

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

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

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

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

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

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

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

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

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

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

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

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

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

#[test]
fn transform_vector_ignores_translation() {
    let a: Affine2 = Affine2::from_translation(Vec2::new(10.0, 20.0));
    let v: Vec2 = Vec2::new(1.0, 2.0);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#[test]
fn mul_identity_is_noop() {
    let t: Affine2 = Affine2::from_translation(Vec2::new(1.0, 2.0));

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

#[test]
fn mul_combines_matrix_and_translation_correctly() {
    let a: Affine2 = Affine2::from_mat2_translation(Mat2::from_diagonal(Vec2::splat(2.0)), Vec2::X);
    let b: Affine2 = Affine2::from_mat2_translation(Mat2::IDENTITY, Vec2::Y);

    let result: Affine2 = a * b;

    let expected_matrix: Mat2 = a.matrix * b.matrix;
    let expected_translation: Vec2 = 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: Affine2 = Affine2::IDENTITY;
    let v: Vec2 = Vec2::ONE;

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

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

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

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

#[test]
fn from_array_trait() {
    assert_eq!(
        Affine2::from([1.0, 2.0, 3.0, 4.0, 5.0, 6.0,]),
        Affine2::from_cols(
            Vec2::new(1.0, 2.0),
            Vec2::new(3.0, 4.0),
            Vec2::new(5.0, 6.0),
        )
    );
}

#[test]
fn array_from_affine2() {
    assert_eq!(
        <[f32; 6]>::from(Affine2::from_cols(
            Vec2::new(1.0, 2.0),
            Vec2::new(3.0, 4.0),
            Vec2::new(5.0, 6.0),
        )),
        [1.0, 2.0, 3.0, 4.0, 5.0, 6.0,]
    );
}

#[test]
fn from_mat3_trait() {
    assert_eq!(
        Affine2::from(Mat3::IDENTITY),
        Affine2::from_mat2(Mat2::IDENTITY)
    );
}
