use crate::{
    DMat2, DMat3, DMat4, DQuat, DVec2, DVec3, DVec4, IVec2, IVec3, IVec4, Mat2, Mat3, Mat4, Quat,
    UVec2, UVec3, UVec4, Vec2, Vec3, Vec3A, Vec4,
};

macro_rules! impl_vec_types {
    ($t:ty, $vec2:ty, $vec3:ty, $vec4:ty) => {
        impl From<mint::Point2<$t>> for $vec2 {
            fn from(v: mint::Point2<$t>) -> Self {
                Self::new(v.x, v.y)
            }
        }

        impl From<$vec2> for mint::Point2<$t> {
            fn from(v: $vec2) -> Self {
                Self { x: v.x, y: v.y }
            }
        }

        impl From<mint::Vector2<$t>> for $vec2 {
            fn from(v: mint::Vector2<$t>) -> Self {
                Self::new(v.x, v.y)
            }
        }

        impl From<$vec2> for mint::Vector2<$t> {
            fn from(v: $vec2) -> Self {
                Self { x: v.x, y: v.y }
            }
        }

        impl From<mint::Point3<$t>> for $vec3 {
            fn from(v: mint::Point3<$t>) -> Self {
                Self::new(v.x, v.y, v.z)
            }
        }

        impl From<$vec3> for mint::Point3<$t> {
            fn from(v: $vec3) -> Self {
                Self {
                    x: v.x,
                    y: v.y,
                    z: v.z,
                }
            }
        }

        impl From<mint::Vector3<$t>> for $vec3 {
            fn from(v: mint::Vector3<$t>) -> Self {
                Self::new(v.x, v.y, v.z)
            }
        }

        impl From<$vec3> for mint::Vector3<$t> {
            fn from(v: $vec3) -> Self {
                Self {
                    x: v.x,
                    y: v.y,
                    z: v.z,
                }
            }
        }

        impl From<mint::Vector4<$t>> for $vec4 {
            fn from(v: mint::Vector4<$t>) -> Self {
                Self::new(v.x, v.y, v.z, v.w)
            }
        }

        impl From<$vec4> for mint::Vector4<$t> {
            fn from(v: $vec4) -> Self {
                Self {
                    x: v.x,
                    y: v.y,
                    z: v.z,
                    w: v.w,
                }
            }
        }
    };
}

macro_rules! impl_float_types {
    ($t:ty, $mat2:ty, $mat3:ty, $mat4:ty, $quat:ty, $vec2:ty, $vec3:ty, $vec4:ty) => {
        impl_vec_types!($t, $vec2, $vec3, $vec4);

        impl From<mint::Quaternion<$t>> for $quat {
            fn from(q: mint::Quaternion<$t>) -> Self {
                Self::from_xyzw(q.v.x, q.v.y, q.v.z, q.s)
            }
        }

        impl From<$quat> for mint::Quaternion<$t> {
            fn from(q: $quat) -> Self {
                Self {
                    s: q.w,
                    v: mint::Vector3 {
                        x: q.x,
                        y: q.y,
                        z: q.z,
                    },
                }
            }
        }

        impl From<mint::RowMatrix2<$t>> for $mat2 {
            fn from(m: mint::RowMatrix2<$t>) -> Self {
                Self::from_cols(m.x.into(), m.y.into()).transpose()
            }
        }

        impl From<$mat2> for mint::RowMatrix2<$t> {
            fn from(m: $mat2) -> Self {
                let mt = m.transpose();
                Self {
                    x: mt.x_axis.into(),
                    y: mt.y_axis.into(),
                }
            }
        }

        impl From<mint::ColumnMatrix2<$t>> for $mat2 {
            fn from(m: mint::ColumnMatrix2<$t>) -> Self {
                Self::from_cols(m.x.into(), m.y.into())
            }
        }

        impl From<$mat2> for mint::ColumnMatrix2<$t> {
            fn from(m: $mat2) -> Self {
                Self {
                    x: m.x_axis.into(),
                    y: m.y_axis.into(),
                }
            }
        }

        impl From<mint::RowMatrix3<$t>> for $mat3 {
            fn from(m: mint::RowMatrix3<$t>) -> Self {
                Self::from_cols(m.x.into(), m.y.into(), m.z.into()).transpose()
            }
        }

        impl From<$mat3> for mint::RowMatrix3<$t> {
            fn from(m: $mat3) -> Self {
                let mt = m.transpose();
                Self {
                    x: mt.x_axis.into(),
                    y: mt.y_axis.into(),
                    z: mt.z_axis.into(),
                }
            }
        }

        impl From<mint::ColumnMatrix3<$t>> for $mat3 {
            fn from(m: mint::ColumnMatrix3<$t>) -> Self {
                Self::from_cols(m.x.into(), m.y.into(), m.z.into())
            }
        }

        impl From<$mat3> for mint::ColumnMatrix3<$t> {
            fn from(m: $mat3) -> Self {
                Self {
                    x: m.x_axis.into(),
                    y: m.y_axis.into(),
                    z: m.z_axis.into(),
                }
            }
        }

        impl From<mint::RowMatrix4<$t>> for $mat4 {
            fn from(m: mint::RowMatrix4<$t>) -> Self {
                Self::from_cols(m.x.into(), m.y.into(), m.z.into(), m.w.into()).transpose()
            }
        }

        impl From<$mat4> for mint::RowMatrix4<$t> {
            fn from(m: $mat4) -> Self {
                let mt = m.transpose();
                Self {
                    x: mt.x_axis.into(),
                    y: mt.y_axis.into(),
                    z: mt.z_axis.into(),
                    w: mt.w_axis.into(),
                }
            }
        }

        impl From<mint::ColumnMatrix4<$t>> for $mat4 {
            fn from(m: mint::ColumnMatrix4<$t>) -> Self {
                Self::from_cols(m.x.into(), m.y.into(), m.z.into(), m.w.into())
            }
        }

        impl From<$mat4> for mint::ColumnMatrix4<$t> {
            fn from(m: $mat4) -> Self {
                Self {
                    x: m.x_axis.into(),
                    y: m.y_axis.into(),
                    z: m.z_axis.into(),
                    w: m.w_axis.into(),
                }
            }
        }
    };
}

impl From<mint::Point3<f32>> for Vec3A {
    fn from(v: mint::Point3<f32>) -> Self {
        Self::new(v.x, v.y, v.z)
    }
}

impl From<Vec3A> for mint::Point3<f32> {
    fn from(v: Vec3A) -> Self {
        Self {
            x: v.x,
            y: v.y,
            z: v.z,
        }
    }
}

impl From<mint::Vector3<f32>> for Vec3A {
    fn from(v: mint::Vector3<f32>) -> Self {
        Self::new(v.x, v.y, v.z)
    }
}

impl From<Vec3A> for mint::Vector3<f32> {
    fn from(v: Vec3A) -> Self {
        Self {
            x: v.x,
            y: v.y,
            z: v.z,
        }
    }
}

impl_float_types!(f32, Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec4);
impl_float_types!(f64, DMat2, DMat3, DMat4, DQuat, DVec2, DVec3, DVec4);
impl_vec_types!(i32, IVec2, IVec3, IVec4);
impl_vec_types!(u32, UVec2, UVec3, UVec4);

#[cfg(test)]
mod test {
    macro_rules! impl_vec_tests {
        ($t:ty, $vec2:ident, $vec3:ident, $vec4:ident) => {
            use crate::{$vec2, $vec3, $vec4};
            use mint;

            #[test]
            fn test_point2() {
                let m = mint::Point2 {
                    x: 1 as $t,
                    y: 2 as $t,
                };
                let g = $vec2::from(m);
                assert_eq!(g, $vec2::new(1 as $t, 2 as $t));
                assert_eq!(m, g.into());
            }

            #[test]
            fn test_point3() {
                let m = mint::Point3 {
                    x: 1 as $t,
                    y: 2 as $t,
                    z: 3 as $t,
                };
                let g = $vec3::from(m);
                assert_eq!(g, $vec3::new(1 as $t, 2 as $t, 3 as $t));
                assert_eq!(m, g.into());
            }

            #[test]
            fn test_vector2() {
                let m = mint::Vector2 {
                    x: 1 as $t,
                    y: 2 as $t,
                };
                let g = $vec2::from(m);
                assert_eq!(g, $vec2::new(1 as $t, 2 as $t));
                assert_eq!(m, g.into());
            }

            #[test]
            fn test_vector3() {
                let m = mint::Vector3 {
                    x: 1 as $t,
                    y: 2 as $t,
                    z: 3 as $t,
                };
                let g = $vec3::from(m);
                assert_eq!(g, $vec3::new(1 as $t, 2 as $t, 3 as $t));
                assert_eq!(m, g.into());
            }

            #[test]
            fn test_vector4() {
                let m = mint::Vector4 {
                    x: 1 as $t,
                    y: 2 as $t,
                    z: 3 as $t,
                    w: 4 as $t,
                };
                let g = $vec4::from(m);
                assert_eq!(g, $vec4::new(1 as $t, 2 as $t, 3 as $t, 4 as $t));
                assert_eq!(m, g.into());
            }
        };
    }

    macro_rules! impl_float_tests {
        ($t:ty, $mat2:ident, $mat3:ident, $mat4:ident, $quat:ident, $vec2:ident, $vec3:ident, $vec4:ident) => {
            impl_vec_tests!($t, $vec2, $vec3, $vec4);

            use crate::{$mat2, $mat3, $mat4, $quat};

            #[test]
            fn test_quaternion() {
                let m = mint::Quaternion {
                    v: mint::Vector3 {
                        x: 1.0,
                        y: 2.0,
                        z: 3.0,
                    },
                    s: 4.0,
                };
                let g = $quat::from(m);
                assert_eq!(g, $quat::from_xyzw(1.0, 2.0, 3.0, 4.0));
                assert_eq!(m, g.into());
            }

            #[test]
            fn test_matrix2() {
                let g = $mat2::from_cols_array_2d(&[[1.0, 2.0], [3.0, 4.0]]);
                let m = mint::ColumnMatrix2::from(g);
                assert_eq!(g, $mat2::from(m));
                let mt = mint::RowMatrix2::from(g);
                assert_eq!(mt, mint::RowMatrix2::from([[1.0, 3.0], [2.0, 4.0]]));
                assert_eq!(g, $mat2::from(mt));
            }

            #[test]
            fn test_matrix3() {
                let g =
                    $mat3::from_cols_array_2d(&[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]);
                let m = mint::ColumnMatrix3::from(g);
                assert_eq!(g, $mat3::from(m));
                let mt = mint::RowMatrix3::from(g);
                assert_eq!(
                    mt,
                    mint::RowMatrix3::from([[1.0, 4.0, 7.0], [2.0, 5.0, 8.0], [3.0, 6.0, 9.0]])
                );
                assert_eq!(g, $mat3::from(mt));
            }

            #[test]
            fn test_matrix4() {
                let g = $mat4::from_cols_array_2d(&[
                    [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 = mint::ColumnMatrix4::from(g);
                assert_eq!(g, $mat4::from(m));
                let mt = mint::RowMatrix4::from(g);
                assert_eq!(
                    mt,
                    mint::RowMatrix4::from([
                        [1.0, 5.0, 9.0, 13.0],
                        [2.0, 6.0, 10.0, 14.0],
                        [3.0, 7.0, 11.0, 15.0],
                        [4.0, 8.0, 12.0, 16.0]
                    ])
                );
                assert_eq!(g, $mat4::from(mt));
            }
        };
    }

    mod f32 {
        impl_float_tests!(f32, Mat2, Mat3, Mat4, Quat, Vec2, Vec3, Vec4);

        #[test]
        fn test_point3a() {
            use crate::Vec3A;
            let m = mint::Point3 {
                x: 1.0,
                y: 2.0,
                z: 3.0,
            };
            let g = Vec3A::from(m);
            assert_eq!(g, Vec3A::new(1.0, 2.0, 3.0));
            assert_eq!(m, g.into());
        }

        #[test]
        fn test_vector3a() {
            use crate::Vec3A;
            let m = mint::Vector3 {
                x: 1.0,
                y: 2.0,
                z: 3.0,
            };
            let g = Vec3A::from(m);
            assert_eq!(g, Vec3A::new(1.0, 2.0, 3.0));
            assert_eq!(m, g.into());
        }
    }

    mod f64 {
        impl_float_tests!(f64, DMat2, DMat3, DMat4, DQuat, DVec2, DVec3, DVec4);
    }

    mod i32 {
        impl_vec_tests!(i32, IVec2, IVec3, IVec4);
    }

    mod u32 {
        impl_vec_tests!(u32, UVec2, UVec3, UVec4);
    }
}
