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

use roofy::{Affine2, Mat2, Mat3, Mat4, Quat, Vec3};

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

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

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

#[test]
fn new() {
    assert_eq!(
        Mat3::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0),
        Mat3::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),
        )
    );
}

#[test]
fn from_cols() {
    assert_eq!(
        Mat3::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),
        ),
        Mat3 {
            col0: Vec3::new(1.0, 2.0, 3.0),
            col1: Vec3::new(4.0, 5.0, 6.0),
            col2: Vec3::new(7.0, 8.0, 9.0),
        }
    );
}

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

#[test]
fn from_array() {
    assert_eq!(
        Mat3::from_array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]),
        Mat3::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),
        )
    );
}

#[test]
fn to_array() {
    assert_eq!(
        Mat3::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),
        )
        .to_array(),
        [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
    );
}

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

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

#[test]
fn write_to_slice() {
    let mut slice: [f32; 9] = [0.0; 9];
    Mat3::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]);
}

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

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

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

#[test]
fn from_scale_one_is_identity() {
    assert_eq!(Mat3::from_diagonal(Vec3::ONE), Mat3::IDENTITY);
}

#[test]
fn from_scale_scales_components() {
    let m: Mat3 = Mat3::from_diagonal(Vec3::new(1.0, 2.0, 3.0));

    assert_eq!(m * Vec3::ONE, Vec3::new(1.0, 2.0, 3.0));
}

#[test]
fn from_rotation_x() {
    let m: Mat3 = Mat3::from_rotation_x(FRAC_PI_2);
    let v: Vec3 = m * Vec3::Y;

    assert!(v.approx_eq(Vec3::Z));
}

#[test]
fn from_rotation_x_does_not_affect_x_axis() {
    let m: Mat3 = Mat3::from_rotation_x(FRAC_PI_2);

    assert_eq!(m * Vec3::X, Vec3::X);
}

#[test]
fn from_rotation_x_degrees_matches() {
    assert_eq!(
        Mat3::from_rotation_x_degrees(90.0),
        Mat3::from_rotation_x(FRAC_PI_2)
    );
}

#[test]
fn from_rotation_y() {
    let m: Mat3 = Mat3::from_rotation_y(FRAC_PI_2);
    let v: Vec3 = m * Vec3::Z;

    assert!(v.approx_eq(Vec3::X));
}

#[test]
fn from_rotation_y_does_not_affect_y_axis() {
    let m: Mat3 = Mat3::from_rotation_y(FRAC_PI_2);

    assert_eq!(m * Vec3::Y, Vec3::Y);
}

#[test]
fn from_rotation_y_degrees_matches() {
    assert_eq!(
        Mat3::from_rotation_y_degrees(90.0),
        Mat3::from_rotation_y(FRAC_PI_2)
    );
}

#[test]
fn from_rotation_z() {
    let m: Mat3 = Mat3::from_rotation_z(FRAC_PI_2);
    let v: Vec3 = m * Vec3::X;

    assert!(v.approx_eq(Vec3::Y));
}

#[test]
fn from_rotation_z_does_not_affect_z_axis() {
    let m: Mat3 = Mat3::from_rotation_z(FRAC_PI_2);

    assert_eq!(m * Vec3::Z, Vec3::Z);
}

#[test]
fn from_rotation_z_degrees_matches() {
    assert_eq!(
        Mat3::from_rotation_z_degrees(90.0),
        Mat3::from_rotation_z(FRAC_PI_2)
    );
}

#[test]
fn from_rs_euler_identity_components_is_identity() {
    assert_eq!(Mat3::from_rs_euler(Vec3::ZERO, Vec3::ONE), Mat3::IDENTITY);
}

#[test]
fn from_rs_euler_scale() {
    assert_eq!(
        Mat3::from_rs_euler(Vec3::ZERO, Vec3::new(1.0, 2.0, 3.0)),
        Mat3::from_diagonal(Vec3::new(1.0, 2.0, 3.0))
    );
}

#[test]
fn from_rs_euler_degrees_matches_from_trs_euler() {
    assert_eq!(
        Mat3::from_rs_euler_degrees(Vec3::splat(90.0), Vec3::ONE),
        Mat3::from_rs_euler(Vec3::splat(FRAC_PI_2), Vec3::ONE)
    );
}

#[test]
fn from_rs_rotation_matches_from_quat() {
    let q: Quat = Quat::from_axis_angle(Vec3::Y, FRAC_PI_2);
    let via_trs: Vec3 = Mat3::from_rs(q, Vec3::ONE).transform(Vec3::X);
    let via_quat: Vec3 = q * Vec3::X;

    assert_eq!(via_trs, via_quat);
}

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

#[test]
fn from_quat_matches_rotation_x() {
    assert!(
        Mat3::from_quat(Quat::from_axis_angle(Vec3::X, FRAC_PI_2))
            .approx_eq(Mat3::from_rotation_x(FRAC_PI_2))
    );
}

#[test]
fn from_quat_matches_rotation_y() {
    assert!(
        Mat3::from_quat(Quat::from_axis_angle(Vec3::Y, FRAC_PI_2))
            .approx_eq(Mat3::from_rotation_y(FRAC_PI_2))
    );
}

#[test]
fn from_quat_matches_rotation_z() {
    assert!(
        Mat3::from_quat(Quat::from_axis_angle(Vec3::Z, FRAC_PI_2))
            .approx_eq(Mat3::from_rotation_z(FRAC_PI_2))
    );
}

#[test]
fn from_quat_preserves_vector_length() {
    let q: Quat = Quat::from_axis_angle(Vec3::new(1.0, 1.0, 0.0).normalized(), 1.23);
    let v: Vec3 = Vec3::new(3.0, 4.0, 0.0);
    let r: Vec3 = Mat3::from_quat(q).transform(v);

    assert!((r.length_squared() - 25.0).abs() < 1e-5);
}

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

#[test]
fn from_axis_angle_matches_rotation_x() {
    assert!(Mat3::from_axis_angle(Vec3::X, FRAC_PI_2).approx_eq(Mat3::from_rotation_x(FRAC_PI_2)));
}

#[test]
fn from_axis_angle_matches_rotation_y() {
    assert!(Mat3::from_axis_angle(Vec3::Y, FRAC_PI_2).approx_eq(Mat3::from_rotation_y(FRAC_PI_2)));
}

#[test]
fn from_axis_angle_matches_rotation_z() {
    assert!(Mat3::from_axis_angle(Vec3::Z, FRAC_PI_2).approx_eq(Mat3::from_rotation_z(FRAC_PI_2)));
}

#[test]
fn from_axis_angle_degrees_matches() {
    assert_eq!(
        Mat3::from_axis_angle_degrees(Vec3::X, 90.0),
        Mat3::from_axis_angle(Vec3::X, FRAC_PI_2)
    );
}

#[test]
fn cols() {
    assert_eq!(Mat3::IDENTITY.cols(), [Vec3::X, Vec3::Y, Vec3::Z]);
}

#[test]
fn col_matches() {
    assert_eq!(Mat3::IDENTITY.col(0), Mat3::IDENTITY.col0);
    assert_eq!(Mat3::IDENTITY.col(1), Mat3::IDENTITY.col1);
    assert_eq!(Mat3::IDENTITY.col(2), Mat3::IDENTITY.col2);
}

#[test]
#[should_panic(expected = "index out of bounds")]
fn col_panics_out_of_bounds() {
    let _ = Mat3::IDENTITY.col(3);
}

#[test]
fn row0() {
    assert_eq!(Mat3::IDENTITY.row0(), Vec3::X);
}

#[test]
fn row1() {
    assert_eq!(Mat3::IDENTITY.row1(), Vec3::Y);
}

#[test]
fn row2() {
    assert_eq!(Mat3::IDENTITY.row2(), Vec3::Z);
}

#[test]
fn rows() {
    assert_eq!(Mat3::IDENTITY.rows(), [Vec3::X, Vec3::Y, Vec3::Z]);
}

#[test]
fn row_matches() {
    assert_eq!(Mat3::IDENTITY.row(0), Mat3::IDENTITY.row0());
    assert_eq!(Mat3::IDENTITY.row(1), Mat3::IDENTITY.row1());
    assert_eq!(Mat3::IDENTITY.row(2), Mat3::IDENTITY.row2());
}

#[test]
#[should_panic(expected = "index out of bounds")]
fn row_panics_out_of_bounds() {
    let _ = Mat3::IDENTITY.row(3);
}

#[test]
fn transpose_identity() {
    assert_eq!(Mat3::IDENTITY.transpose(), Mat3::IDENTITY);
}

#[test]
fn transpose_swaps_rows_and_columns() {
    let m: Mat3 = Mat3::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),
    );

    assert_eq!(
        m.transpose(),
        Mat3::from_cols(
            Vec3::new(1.0, 4.0, 7.0),
            Vec3::new(2.0, 5.0, 8.0),
            Vec3::new(3.0, 6.0, 9.0),
        )
    )
}

#[test]
fn transpose_twice_stays_same() {
    let m: Mat3 = Mat3::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),
    );

    assert_eq!(m.transpose().transpose(), m);
}

#[test]
fn diagonal() {
    assert_eq!(Mat3::IDENTITY.diagonal(), Vec3::ONE);
}

#[test]
fn determinant_zero_is_0() {
    assert_eq!(Mat3::ZERO.determinant(), 0.0);
}

#[test]
fn determinant_identity_is_1() {
    assert_eq!(Mat3::IDENTITY.determinant(), 1.0);
}

#[test]
fn determinant_duplicate_rows_is_zero() {
    assert_eq!(
        Mat3::from_cols(
            Vec3::new(1.0, 1.0, 0.0),
            Vec3::new(2.0, 2.0, 0.0),
            Vec3::new(3.0, 3.0, 1.0),
        )
        .determinant(),
        0.0
    );
}

#[test]
fn determinant_is_product_of_diagonal() {
    assert_eq!(
        Mat3::from_diagonal(Vec3::new(2.0, 3.0, 4.0)).determinant(),
        24.0
    );
}

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

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

#[test]
fn inverse_times_original_is_identity() {
    let cases: [Mat3; 3] = [
        Mat3::IDENTITY,
        Mat3::from_diagonal(Vec3::new(2.0, 0.5, -3.0)),
        Mat3::from_rotation_y(1.0),
    ];

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

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

#[test]
fn inverse_rotation() {
    let m: Mat3 = Mat3::from_rotation_y(1.2);
    assert!(m.inverse().approx_eq(m.transpose()));
}

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

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

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

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

    assert_eq!(Mat3::IDENTITY.transform(v), v);
}

#[test]
fn transform_applies_scale() {
    let m: Mat3 = Mat3::from_diagonal(Vec3::new(2.0, 3.0, 4.0));
    let r: Vec3 = m.transform(Vec3::ONE);

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

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

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

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

    assert!(Mat3::IDENTITY.approx_eq(m));
}

#[test]
fn abs() {
    assert_eq!((-Mat3::IDENTITY).abs(), Mat3::IDENTITY);
}

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

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

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

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

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

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

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

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

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

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

#[test]
fn add() {
    assert_eq!(
        Mat3::IDENTITY + Mat3::IDENTITY,
        Mat3::from_diagonal(Vec3::splat(2.0))
    )
}

#[test]
fn add_assign() {
    let mut m: Mat3 = Mat3::IDENTITY;
    m += Mat3::IDENTITY;
    assert_eq!(m, Mat3::from_diagonal(Vec3::splat(2.0)))
}

#[test]
fn sub() {
    assert_eq!(Mat3::IDENTITY - Mat3::IDENTITY, Mat3::ZERO)
}

#[test]
fn sub_assign() {
    let mut m: Mat3 = Mat3::IDENTITY;
    m -= Mat3::IDENTITY;
    assert_eq!(m, Mat3::ZERO)
}

#[test]
fn mul_identity_is_noop() {
    let m: Mat3 = Mat3::from_cols(
        Vec3::new(1.0, 4.0, 7.0),
        Vec3::new(2.0, 5.0, 8.0),
        Vec3::new(3.0, 6.0, 9.0),
    );

    assert_eq!(m * Mat3::IDENTITY, m);
    assert_eq!(Mat3::IDENTITY * m, m)
}

#[test]
fn mul_zero_returns_zero() {
    let m: Mat3 = Mat3::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),
    );

    assert_eq!(m * Mat3::ZERO, Mat3::ZERO);
}

#[test]
fn mul_scalar_diagonal() {
    let a: Mat3 = Mat3::from_diagonal(Vec3::splat(2.0));
    let b: Mat3 = Mat3::from_diagonal(Vec3::splat(3.0));
    let expected: Mat3 = Mat3::from_diagonal(Vec3::splat(6.0));

    assert_eq!(a * b, expected);
}

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

    assert_eq!(Mat3::IDENTITY * v, v);
}

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

    assert_eq!(Mat3::ZERO * v, Vec3::ZERO);
}

#[test]
fn mul_f32() {
    assert_eq!(
        Mat3::IDENTITY * 2.0,
        Mat3::from_cols(Vec3::X * 2.0, Vec3::Y * 2.0, Vec3::Z * 2.0)
    );
}

#[test]
fn mul_assign_f32() {
    let mut m: Mat3 = Mat3::IDENTITY;
    m *= 2.0;
    assert_eq!(m, Mat3::IDENTITY * 2.0);
}

#[test]
fn div() {
    assert_eq!(Mat3::IDENTITY * 2.0 / 2.0, Mat3::IDENTITY);
}

#[test]
fn div_assign() {
    let mut m: Mat3 = Mat3::IDENTITY;
    m /= 2.0;
    assert_eq!(m, Mat3::IDENTITY / 2.0);
}

#[test]
fn partial_eq() {
    assert!(Mat3::IDENTITY == Mat3::IDENTITY);
}

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

#[test]
fn neg() {
    assert_eq!(-Mat3::IDENTITY, Mat3::from_diagonal(Vec3::NEG_ONE));
}

#[test]
fn as_ref_array() {
    let r: &[f32; 9] = Mat3::ZERO.as_ref();
    assert_eq!(r, &[0.0; 9]);
}

#[test]
fn as_mut_array() {
    let mut m: Mat3 = Mat3::IDENTITY;
    let r: &mut [f32; 9] = m.as_mut();

    r[0] = 10.0;

    assert_eq!(
        m,
        Mat3::from_cols(Vec3::new(10.0, 0.0, 0.0), Vec3::Y, Vec3::Z)
    );
}

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

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

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

#[test]
fn from_array_trait() {
    assert_eq!(
        Mat3::from([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]),
        Mat3::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)
        )
    );
}

#[test]
fn array_from_mat4() {
    assert_eq!(
        <[f32; 9]>::from(Mat3::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)
        )),
        [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
    );
}

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

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

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