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

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

#[test]
fn const_zero() {
    assert_eq!(
        Mat4::ZERO,
        Mat4::from_cols(Vec4::ZERO, Vec4::ZERO, Vec4::ZERO, Vec4::ZERO)
    );
}
#[test]
fn const_nan() {
    let a: [f32; 16] = Mat4::NAN.into();
    assert!(a.iter().all(|x| x.is_nan()))
}

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

#[test]
fn new() {
    assert_eq!(
        Mat4::from_cols(
            Vec4::new(1.0, 2.0, 3.0, 4.0),
            Vec4::new(5.0, 6.0, 7.0, 8.0),
            Vec4::new(9.0, 10.0, 11.0, 12.0),
            Vec4::new(13.0, 14.0, 15.0, 16.0)
        ),
        Mat4 {
            m00: 1.0,
            m01: 2.0,
            m02: 3.0,
            m03: 4.0,
            m10: 5.0,
            m11: 6.0,
            m12: 7.0,
            m13: 8.0,
            m20: 9.0,
            m21: 10.0,
            m22: 11.0,
            m23: 12.0,
            m30: 13.0,
            m31: 14.0,
            m32: 15.0,
            m33: 16.0,
        }
    );
}

#[test]
fn col0() {
    assert_eq!(Mat4::IDENTITY.col0(), Vec4::X);
}

#[test]
fn col1() {
    assert_eq!(Mat4::IDENTITY.col1(), Vec4::Y);
}

#[test]
fn col2() {
    assert_eq!(Mat4::IDENTITY.col2(), Vec4::Z);
}

#[test]
fn col3() {
    assert_eq!(Mat4::IDENTITY.col3(), Vec4::W);
}

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

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

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

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

#[test]
fn row3() {
    assert_eq!(Mat4::IDENTITY.row3(), Vec4::W);
}

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

#[test]
fn from_translation_zero_is_identity() {
    assert_eq!(Mat4::from_translation(Vec3::ZERO), Mat4::IDENTITY);
}

#[test]
fn from_translation() {
    let m: Mat4 = Mat4::from_translation(Vec3::new(1.0, 2.0, 3.0));

    assert_eq!(m.col3(), Vec4::new(1.0, 2.0, 3.0, 1.0));
}

#[test]
fn translation_identity_is_zero() {
    assert_eq!(Mat4::IDENTITY.translation(), Vec3::ZERO);
}

#[test]
fn translation_roundtrip() {
    let t: Vec3 = Vec3::new(3.0, 5.0, 7.0);
    assert_eq!(Mat4::from_translation(t).translation(), t);
}

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

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

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

#[test]
fn from_rotation_x() {
    let m: Mat4 = Mat4::from_rotation_degrees_x(90.0);
    let v: Vec4 = m * Vec4::Y;

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

#[test]
fn from_rotation_x_does_not_affect_x_axis() {
    let m: Mat4 = Mat4::from_rotation_degrees_x(90.0);

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

#[test]
fn from_rotation_y() {
    let m: Mat4 = Mat4::from_rotation_degrees_y(90.0);
    let v: Vec4 = m * Vec4::Z;

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

#[test]
fn from_rotation_y_does_not_affect_y_axis() {
    let m: Mat4 = Mat4::from_rotation_degrees_y(90.0);

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

#[test]
fn from_rotation_z() {
    let m: Mat4 = Mat4::from_rotation_degrees_z(90.0);
    let v: Vec4 = m * Vec4::X;

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

#[test]
fn from_rotation_z_does_not_affect_z_axis() {
    let m: Mat4 = Mat4::from_rotation_degrees_z(90.0);

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

#[test]
fn from_trs_euler_identity_components_is_identity() {
    assert_eq!(
        Mat4::from_trs_euler(Vec3::ZERO, Vec3::ZERO, Vec3::ONE),
        Mat4::IDENTITY
    );
}

#[test]
fn from_trs_euler_translation() {
    assert_eq!(
        Mat4::from_trs_euler(Vec3::new(1.0, 2.0, 3.0), Vec3::ZERO, Vec3::ONE),
        Mat4::from_translation(Vec3::new(1.0, 2.0, 3.0))
    );
}

#[test]
fn from_trs_euler_scale() {
    assert_eq!(
        Mat4::from_trs_euler(Vec3::ZERO, Vec3::ZERO, Vec3::new(1.0, 2.0, 3.0)),
        Mat4::from_scale(Vec3::new(1.0, 2.0, 3.0))
    );
}

#[test]
fn from_trs_euler_scale_then_translate() {
    let m: Mat4 = Mat4::from_trs_euler(Vec3::new(10.0, 0.0, 0.0), Vec3::ZERO, Vec3::splat(2.0));
    let v: Vec4 = Vec4::new(1.0, 0.0, 0.0, 1.0);

    assert_eq!(m * v, Vec4::new(12.0, 0.0, 0.0, 1.0));
}

#[test]
fn from_trs_euler_degrees_matches_from_trs_euler() {
    assert_eq!(
        Mat4::from_trs_euler_degrees(Vec3::ZERO, Vec3::splat(90.0), Vec3::ZERO),
        Mat4::from_trs_euler(Vec3::ZERO, Vec3::splat(FRAC_PI_2), Vec3::ZERO)
    );
}

#[test]
fn from_trs_identity_components_is_identity() {
    assert_eq!(
        Mat4::from_trs(Vec3::ZERO, Quat::IDENTITY, Vec3::ONE),
        Mat4::IDENTITY
    );
}

#[test]
fn from_trs_translation() {
    assert_eq!(
        Mat4::from_trs(Vec3::new(1.0, 2.0, 3.0), Quat::IDENTITY, Vec3::ONE),
        Mat4::from_translation(Vec3::new(1.0, 2.0, 3.0))
    );
}

#[test]
fn from_trs_scale() {
    assert_eq!(
        Mat4::from_trs(Vec3::ZERO, Quat::IDENTITY, Vec3::new(1.0, 2.0, 3.0)),
        Mat4::from_scale(Vec3::new(1.0, 2.0, 3.0))
    );
}

#[test]
fn from_trs_scale_then_translate() {
    let m: Mat4 = Mat4::from_trs(Vec3::new(10.0, 0.0, 0.0), Quat::IDENTITY, Vec3::splat(2.0));
    let v: Vec4 = Vec4::new(1.0, 0.0, 0.0, 1.0);

    assert_eq!(m * v, Vec4::new(12.0, 0.0, 0.0, 1.0));
}

#[test]
fn from_trs_rotation_matches_from_quat() {
    let q: Quat = Quat::from_axis_angle_degrees(Vec3::Y, 90.0);
    let via_trs: Vec3 = Mat4::from_trs(Vec3::ZERO, q, Vec3::ONE).transform_direction(Vec3::X);
    let via_quat: Vec3 = q * Vec3::X;

    assert_eq!(via_trs, via_quat);
}

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

#[test]
fn from_quat_matches_rotation_x() {
    assert!(
        Mat4::from_quat(Quat::from_axis_angle_degrees(Vec3::X, 90.0))
            .approx_eq(Mat4::from_rotation_degrees_x(90.0))
    );
}

#[test]
fn from_quat_matches_rotation_y() {
    assert!(
        Mat4::from_quat(Quat::from_axis_angle_degrees(Vec3::Y, 90.0))
            .approx_eq(Mat4::from_rotation_degrees_y(90.0))
    );
}

#[test]
fn from_quat_matches_rotation_z() {
    assert!(
        Mat4::from_quat(Quat::from_axis_angle_degrees(Vec3::Z, 90.0))
            .approx_eq(Mat4::from_rotation_degrees_z(90.0))
    );
}

#[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 = Mat4::from_quat(q).transform_direction(v);

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

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

#[test]
fn from_axis_angle_matches_rotation_x() {
    assert!(
        Mat4::from_axis_angle_degrees(Vec3::X, 90.0).approx_eq(Mat4::from_rotation_degrees_x(90.0))
    );
}

#[test]
fn from_axis_angle_matches_rotation_y() {
    assert!(
        Mat4::from_axis_angle_degrees(Vec3::Y, 90.0).approx_eq(Mat4::from_rotation_degrees_y(90.0))
    );
}

#[test]
fn from_axis_angle_matches_rotation_z() {
    assert!(
        Mat4::from_axis_angle_degrees(Vec3::Z, 90.0).approx_eq(Mat4::from_rotation_degrees_z(90.0))
    );
}

#[test]
fn look_at_rh_looking_down_neg_z() {
    let m: Mat4 = Mat4::look_at_rh(Vec3::Z, Vec3::ZERO, Vec3::Y);
    let v: Vec4 = m * Vec4::W;

    assert!(v.z < 0.0);
}

#[test]
fn look_at_lh_looking_down_z() {
    let m: Mat4 = Mat4::look_at_lh(Vec3::NEG_Z, Vec3::ZERO, Vec3::Y);
    let v: Vec4 = m * Vec4::W;

    assert!(v.z > 0.0);
}

#[test]
fn perspective_rh_near_maps_to_zero_depth() {
    let z_near: f32 = 0.1;
    let z_far: f32 = 100.0;
    let m: Mat4 = Mat4::perspective_rh(FRAC_PI_2, 1.0, z_near, z_far);
    let v: Vec4 = m * Vec4::new(0.0, 0.0, -z_near, 1.0);
    assert!((v.z / v.w).abs() < 1e-5);
}

#[test]
fn perspective_rh_far_maps_to_one_depth() {
    let z_near: f32 = 0.1;
    let z_far: f32 = 100.0;
    let m: Mat4 = Mat4::perspective_rh(FRAC_PI_2, 1.0, z_near, z_far);
    let v: Vec4 = m * Vec4::new(0.0, 0.0, -z_far, 1.0);
    assert!((v.z / v.w - 1.0).abs() < 1e-5);
}

#[test]
fn perspective_lh_near_maps_to_zero_depth() {
    let z_near: f32 = 0.1;
    let z_far: f32 = 100.0;
    let m: Mat4 = Mat4::perspective_lh(FRAC_PI_2, 1.0, z_near, z_far);
    let v: Vec4 = m * Vec4::new(0.0, 0.0, z_near, 1.0);
    assert!((v.z / v.w).abs() < 1e-5);
}

#[test]
fn perspective_lh_far_maps_to_one_depth() {
    let z_near: f32 = 0.1;
    let z_far: f32 = 100.0;
    let m: Mat4 = Mat4::perspective_lh(FRAC_PI_2, 1.0, z_near, z_far);
    let v: Vec4 = m * Vec4::new(0.0, 0.0, z_far, 1.0);
    assert!((v.z / v.w - 1.0).abs() < 1e-5);
}

#[test]
fn perspective_rh_gl_near_maps_to_neg_one_depth() {
    let z_near: f32 = 0.1;
    let z_far: f32 = 100.0;
    let m: Mat4 = Mat4::perspective_rh_gl(FRAC_PI_2, 1.0, z_near, z_far);
    let v: Vec4 = m * Vec4::new(0.0, 0.0, -z_near, 1.0);
    assert!((v.z / v.w + 1.0).abs() < 1e-5);
}

#[test]
fn perspective_rh_gl_far_maps_to_one_depth() {
    let z_near: f32 = 0.1;
    let z_far: f32 = 100.0;
    let m: Mat4 = Mat4::perspective_rh_gl(FRAC_PI_2, 1.0, z_near, z_far);
    let v: Vec4 = m * Vec4::new(0.0, 0.0, -z_far, 1.0);
    assert!((v.z / v.w - 1.0).abs() < 1e-5);
}

#[test]
fn perspective_lh_gl_near_maps_to_neg_one_depth() {
    let z_near: f32 = 0.1;
    let z_far: f32 = 100.0;
    let m: Mat4 = Mat4::perspective_lh_gl(FRAC_PI_2, 1.0, z_near, z_far);
    let v: Vec4 = m * Vec4::new(0.0, 0.0, z_near, 1.0);
    assert!((v.z / v.w + 1.0).abs() < 1e-5);
}

#[test]
fn perspective_lh_gl_far_maps_to_one_depth() {
    let z_near: f32 = 0.1;
    let z_far: f32 = 100.0;
    let m: Mat4 = Mat4::perspective_lh_gl(FRAC_PI_2, 1.0, z_near, z_far);
    let v: Vec4 = m * Vec4::new(0.0, 0.0, z_far, 1.0);
    assert!((v.z / v.w - 1.0).abs() < 1e-5);
}

#[test]
fn orthographic_rh_maps_near_to_zero() {
    let m: Mat4 = Mat4::orthographic_rh(-1.0, 1.0, -1.0, 1.0, 0.1, 100.0);
    let p: Vec4 = m * Vec4::new(0.0, 0.0, -0.1, 1.0);

    assert_eq!(p.z, 0.0);
}

#[test]
fn orthographic_rh_maps_far_to_one() {
    let m: Mat4 = Mat4::orthographic_rh(-1.0, 1.0, -1.0, 1.0, 0.1, 100.0);
    let p: Vec4 = m * Vec4::new(0.0, 0.0, -100.0, 1.0);

    assert_eq!(p.z, 1.0);
}

#[test]
fn orthographic_lh_maps_near_to_zero() {
    let m: Mat4 = Mat4::orthographic_lh(-1.0, 1.0, -1.0, 1.0, 0.1, 100.0);
    let p: Vec4 = m * Vec4::new(0.0, 0.0, 0.1, 1.0);

    assert_eq!(p.z, 0.0);
}

#[test]
fn orthographic_lh_maps_far_to_one() {
    let m: Mat4 = Mat4::orthographic_lh(-1.0, 1.0, -1.0, 1.0, 0.1, 100.0);
    let p: Vec4 = m * Vec4::new(0.0, 0.0, 100.0, 1.0);

    assert_eq!(p.z, 1.0);
}

#[test]
fn orthographic_rh_gl_maps_near_to_neg_one() {
    let m: Mat4 = Mat4::orthographic_rh_gl(-1.0, 1.0, -1.0, 1.0, 0.1, 100.0);
    let p: Vec4 = m * Vec4::new(0.0, 0.0, -0.1, 1.0);

    assert_eq!(p.z, -1.0);
}

#[test]
fn orthographic_rh_gl_maps_far_to_one() {
    let m: Mat4 = Mat4::orthographic_rh_gl(-1.0, 1.0, -1.0, 1.0, 0.1, 100.0);
    let p: Vec4 = m * Vec4::new(0.0, 0.0, -100.0, 1.0);

    assert_eq!(p.z, 1.0);
}

#[test]
fn orthographic_lh_gl_maps_near_to_neg_one() {
    let m: Mat4 = Mat4::orthographic_lh_gl(-1.0, 1.0, -1.0, 1.0, 0.1, 100.0);
    let p: Vec4 = m * Vec4::new(0.0, 0.0, 0.1, 1.0);

    assert_eq!(p.z, -1.0);
}

#[test]
fn orthographic_lh_gl_maps_far_to_one() {
    let m: Mat4 = Mat4::orthographic_lh_gl(-1.0, 1.0, -1.0, 1.0, 0.1, 100.0);
    let p: Vec4 = m * Vec4::new(0.0, 0.0, 100.0, 1.0);

    assert_eq!(p.z, 1.0);
}

#[test]
fn orthographic_rh_maps_center_xy_to_zero() {
    let m: Mat4 = Mat4::orthographic_rh(-4.0, 4.0, -3.0, 3.0, 0.1, 100.0);
    let p: Vec4 = m * Vec4::new(0.0, 0.0, -1.0, 1.0);

    assert_eq!(p.x, 0.0);
    assert_eq!(p.y, 0.0);
}

#[test]
fn orthographic_rh_maps_right_edge_to_one() {
    let m: Mat4 = Mat4::orthographic_rh(-4.0, 4.0, -3.0, 3.0, 0.1, 100.0);
    let p: Vec4 = m * Vec4::new(4.0, 0.0, -1.0, 1.0);

    assert_eq!(p.x, 1.0);
}

#[test]
fn transpose_identity() {
    let mut m: Mat4 = Mat4::IDENTITY;
    m.transpose();
    assert_eq!(m, Mat4::IDENTITY);
}

#[test]
fn transpose_swaps_rows_and_columns() {
    let mut m: Mat4 = Mat4::from_cols(
        Vec4::new(1.0, 2.0, 3.0, 4.0),
        Vec4::new(5.0, 6.0, 7.0, 8.0),
        Vec4::new(9.0, 10.0, 11.0, 12.0),
        Vec4::new(13.0, 14.0, 15.0, 16.0),
    );
    m.transpose();

    assert_eq!(
        m,
        Mat4::from_cols(
            Vec4::new(1.0, 5.0, 9.0, 13.0),
            Vec4::new(2.0, 6.0, 10.0, 14.0),
            Vec4::new(3.0, 7.0, 11.0, 15.0),
            Vec4::new(4.0, 8.0, 12.0, 16.0)
        )
    )
}

#[test]
fn transpose_twice_stays_same() {
    let m: Mat4 = Mat4::from_cols(
        Vec4::new(1.0, 5.0, 9.0, 13.0),
        Vec4::new(2.0, 6.0, 10.0, 14.0),
        Vec4::new(3.0, 7.0, 11.0, 15.0),
        Vec4::new(4.0, 8.0, 12.0, 16.0),
    );
    let mut t: Mat4 = m;
    t.transpose();
    t.transpose();

    assert_eq!(t, m);
}

#[test]
fn transposed_identity() {
    assert_eq!(Mat4::IDENTITY.transposed(), Mat4::IDENTITY);
}

#[test]
fn transposed_swaps_rows_and_columns() {
    let m: Mat4 = Mat4::from_cols(
        Vec4::new(1.0, 2.0, 3.0, 4.0),
        Vec4::new(5.0, 6.0, 7.0, 8.0),
        Vec4::new(9.0, 10.0, 11.0, 12.0),
        Vec4::new(13.0, 14.0, 15.0, 16.0),
    );

    assert_eq!(
        m.transposed(),
        Mat4::from_cols(
            Vec4::new(1.0, 5.0, 9.0, 13.0),
            Vec4::new(2.0, 6.0, 10.0, 14.0),
            Vec4::new(3.0, 7.0, 11.0, 15.0),
            Vec4::new(4.0, 8.0, 12.0, 16.0)
        )
    )
}

#[test]
fn transposed_twice_stays_same() {
    let m: Mat4 = Mat4::from_cols(
        Vec4::new(1.0, 5.0, 9.0, 13.0),
        Vec4::new(2.0, 6.0, 10.0, 14.0),
        Vec4::new(3.0, 7.0, 11.0, 15.0),
        Vec4::new(4.0, 8.0, 12.0, 16.0),
    );

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

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

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

#[test]
fn determinant_scale_is_product_of_diagonal() {
    assert_eq!(
        Mat4::from_scale(Vec3::new(2.0, 3.0, 4.0)).determinant(),
        24.0
    );
}

#[test]
fn inverse_identity_is_identity() {
    let mut m: Mat4 = Mat4::IDENTITY;
    m.inverse();

    assert_eq!(m, Mat4::IDENTITY);
}

#[test]
fn inverse_singular_stays_same() {
    let mut m: Mat4 = Mat4::ZERO;
    m.inverse();

    assert_eq!(m, Mat4::ZERO);
}

#[test]
fn inverse_times_original_is_identity() {
    let m: Mat4 = Mat4::from_trs_euler_degrees(
        Vec3::new(1.0, 2.0, 3.0),
        Vec3::new(15.0, 30.0, 45.0),
        Vec3::ONE,
    );
    let mut inv: Mat4 = m;
    inv.inverse();

    assert!(Mat4::IDENTITY.approx_eq(m * inv));
    assert!(Mat4::IDENTITY.approx_eq(inv * m));
}

#[test]
fn inverse_translation_is_negated_translation() {
    let mut m: Mat4 = Mat4::from_translation(Vec3::new(3.0, 5.0, 7.0));
    m.inverse();

    assert_eq!(m, Mat4::from_translation(Vec3::new(-3.0, -5.0, -7.0)));
}

#[test]
fn inversed_identity_is_identity() {
    assert_eq!(Mat4::IDENTITY.inversed().unwrap(), Mat4::IDENTITY);
}

#[test]
fn inversed_singular_returns_none() {
    assert!(Mat4::ZERO.inversed().is_none());
}

#[test]
fn inversed_times_original_is_identity() {
    let m: Mat4 = Mat4::from_trs_euler_degrees(
        Vec3::new(1.0, 2.0, 3.0),
        Vec3::new(15.0, 30.0, 45.0),
        Vec3::ONE,
    );
    let inv: Mat4 = m.inversed().unwrap();

    assert!(Mat4::IDENTITY.approx_eq(m * inv));
    assert!(Mat4::IDENTITY.approx_eq(inv * m));
}

#[test]
fn inversed_translation_is_negated_translation() {
    assert_eq!(
        Mat4::from_translation(Vec3::new(3.0, 5.0, 7.0))
            .inversed()
            .unwrap(),
        Mat4::from_translation(Vec3::new(-3.0, -5.0, -7.0))
    );
}

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

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

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

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

#[test]
fn transform_point_applies_translation() {
    let m: Mat4 = Mat4::from_translation(Vec3::new(10.0, 20.0, 30.0));
    let r: Vec3 = m.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 m: Mat4 = Mat4::from_scale(Vec3::new(2.0, 3.0, 4.0));
    let r: Vec3 = m.transform_point(Vec3::ONE);

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

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

    assert_eq!(Mat4::IDENTITY.transform_direction(v), v);
}

#[test]
fn transform_direction_ignores_translation() {
    let m: Mat4 = Mat4::from_translation(Vec3::new(10.0, 20.0, 30.0));
    let v: Vec3 = Vec3::new(1.0, 2.0, 3.0);

    assert_eq!(m.transform_direction(v), v);
}

#[test]
fn transform_direction_applies_scale() {
    let m: Mat4 = Mat4::from_scale(Vec3::new(2.0, 3.0, 4.0));
    let r: Vec3 = m.transform_direction(Vec3::ONE);

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

#[test]
fn transform_direction_rotation_preserves_length() {
    let m: Mat4 = Mat4::from_rotation_degrees_y(90.0);
    let r: Vec3 = m.transform_direction(Vec3::X);

    assert_eq!(r.length_squared(), 1.0);
}

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

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

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

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

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

#[test]
fn is_finite_nan() {
    let mut m: Mat4 = Mat4::IDENTITY;
    m.m00 = f32::NAN;

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

#[test]
fn is_finite_infinity() {
    let mut m: Mat4 = Mat4::IDENTITY;
    m.m00 = f32::INFINITY;

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

#[test]
fn is_finite_neg_infinity() {
    let mut m: Mat4 = Mat4::IDENTITY;
    m.m00 = f32::NEG_INFINITY;

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

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

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

#[test]
fn mul_identity_stays_same() {
    let m: Mat4 = Mat4::from_cols(
        Vec4::new(1.0, 5.0, 9.0, 13.0),
        Vec4::new(2.0, 6.0, 10.0, 14.0),
        Vec4::new(3.0, 7.0, 11.0, 15.0),
        Vec4::new(4.0, 8.0, 12.0, 16.0),
    );

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

#[test]
fn mul_zero_returns_zero() {
    let m: Mat4 = Mat4::from_cols(
        Vec4::new(1.0, 5.0, 9.0, 13.0),
        Vec4::new(2.0, 6.0, 10.0, 14.0),
        Vec4::new(3.0, 7.0, 11.0, 15.0),
        Vec4::new(4.0, 8.0, 12.0, 16.0),
    );

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

#[test]
fn mul_vec4_identity_stays_same() {
    let v: Vec4 = Vec4::new(1.0, 2.0, 3.0, 4.0);

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

#[test]
fn mul_vec4_zero_returns_zero() {
    let v: Vec4 = Vec4::new(1.0, 2.0, 3.0, 4.0);

    assert_eq!(Mat4::ZERO * v, Vec4::ZERO);
}

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

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

#[test]
fn from_array() {
    assert_eq!(
        Mat4::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, 13.0, 14.0, 15.0, 16.0,
        ]),
        Mat4::from_cols(
            Vec4::new(1.0, 2.0, 3.0, 4.0),
            Vec4::new(5.0, 6.0, 7.0, 8.0),
            Vec4::new(9.0, 10.0, 11.0, 12.0),
            Vec4::new(13.0, 14.0, 15.0, 16.0)
        )
    );
}

#[test]
fn array_from_mat4() {
    assert_eq!(
        <[f32; 16]>::from(Mat4::from_cols(
            Vec4::new(1.0, 2.0, 3.0, 4.0),
            Vec4::new(5.0, 6.0, 7.0, 8.0),
            Vec4::new(9.0, 10.0, 11.0, 12.0),
            Vec4::new(13.0, 14.0, 15.0, 16.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, 13.0, 14.0, 15.0, 16.0,
        ]
    );
}

#[test]
fn from_slice() {
    let data: [f32; 16] = [
        1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
    ];
    let m: Mat4 = Mat4::from(&data[..]);
    assert_eq!(
        m,
        Mat4::from_cols(
            Vec4::new(1.0, 2.0, 3.0, 4.0),
            Vec4::new(5.0, 6.0, 7.0, 8.0),
            Vec4::new(9.0, 10.0, 11.0, 12.0),
            Vec4::new(13.0, 14.0, 15.0, 16.0)
        )
    );
}

#[test]
#[should_panic(expected = "index out of bounds")]
fn from_slice_panics_if_too_short() {
    let data: [f32; 1] = [1.0];
    let _v: Vec3 = Vec3::from(&data[..]);
}

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

#[test]
fn as_ref_slice() {
    let r: &[f32] = Mat4::ZERO.as_ref();
    assert_eq!(r, &[0.0; 16]);
}

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

    r[0] = 10.0;

    assert_eq!(
        m,
        Mat4::from_cols(Vec4::new(10.0, 0.0, 0.0, 0.0), Vec4::Y, Vec4::Z, Vec4::W)
    );
}

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

    r[0] = 10.0;

    assert_eq!(
        m,
        Mat4::from_cols(Vec4::new(10.0, 0.0, 0.0, 0.0), Vec4::Y, Vec4::Z, Vec4::W)
    );
}

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