// BSD 3-Clause License
//
// Copyright (c) 2024, Woven by Toyota.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this
//   list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
//   this list of conditions and the following disclaimer in the documentation
//   and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
//   contributors may be used to endorse or promote products derived from
//   this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#[cfg(test)]
mod math_test {
    use maliput_sys::math::ffi::Vector3_cross;
    use maliput_sys::math::ffi::Vector3_dot;
    use maliput_sys::math::ffi::Vector3_equals;
    use maliput_sys::math::ffi::Vector3_new;

    use maliput_sys::math::ffi::Vector4_dot;
    use maliput_sys::math::ffi::Vector4_equals;
    use maliput_sys::math::ffi::Vector4_new;

    use maliput_sys::math::ffi::Matrix3_adjoint;
    use maliput_sys::math::ffi::Matrix3_cofactor_matrix;
    use maliput_sys::math::ffi::Matrix3_col;
    use maliput_sys::math::ffi::Matrix3_equals;
    use maliput_sys::math::ffi::Matrix3_inverse;
    use maliput_sys::math::ffi::Matrix3_new;
    use maliput_sys::math::ffi::Matrix3_operator_divide_by_scalar;
    use maliput_sys::math::ffi::Matrix3_operator_mul;
    use maliput_sys::math::ffi::Matrix3_operator_sub;
    use maliput_sys::math::ffi::Matrix3_operator_sum;
    use maliput_sys::math::ffi::Matrix3_row;
    use maliput_sys::math::ffi::Matrix3_to_str;
    use maliput_sys::math::ffi::Matrix3_transpose;

    use maliput_sys::math::ffi::RollPitchYaw_CalcRotationMatrixDt;
    use maliput_sys::math::ffi::RollPitchYaw_SetFromQuaternion;
    use maliput_sys::math::ffi::RollPitchYaw_ToMatrix;
    use maliput_sys::math::ffi::RollPitchYaw_ToQuaternion;
    use maliput_sys::math::ffi::RollPitchYaw_new;
    use maliput_sys::math::ffi::RollPitchYaw_set;

    use maliput_sys::math::ffi::Quaternion_Inverse;
    use maliput_sys::math::ffi::Quaternion_IsApprox;
    use maliput_sys::math::ffi::Quaternion_ToRotationMatrix;
    use maliput_sys::math::ffi::Quaternion_TransformVector;
    use maliput_sys::math::ffi::Quaternion_coeffs;
    use maliput_sys::math::ffi::Quaternion_conjugate;
    use maliput_sys::math::ffi::Quaternion_equals;
    use maliput_sys::math::ffi::Quaternion_new;
    use maliput_sys::math::ffi::Quaternion_to_str;
    use maliput_sys::math::ffi::Quaternion_vec;

    #[test]
    fn vector3_new() {
        let v = Vector3_new(1.0, 2.0, 3.0);
        assert_eq!(v.x(), 1.0);
        assert_eq!(v.y(), 2.0);
        assert_eq!(v.z(), 3.0);
    }

    #[test]
    fn vector3_norm() {
        let v = Vector3_new(1.0, 2.0, 3.0);
        assert_eq!(v.norm(), (v.x() * v.x() + v.y() * v.y() + v.z() * v.z()).sqrt());
    }

    #[test]
    fn vector3_normalize() {
        let mut v = Vector3_new(1.0, 2.0, 3.0);
        let norm = v.norm();
        v.as_mut().expect("").normalize();
        assert_eq!(v.x(), 1.0 / norm);
        assert_eq!(v.y(), 2.0 / norm);
        assert_eq!(v.z(), 3.0 / norm);
    }

    #[test]
    fn vector3_dot() {
        let v = Vector3_new(1.0, 2.0, 3.0);
        let w = Vector3_new(4.0, 5.0, 6.0);
        assert_eq!(Vector3_dot(&v, &w), v.x() * w.x() + v.y() * w.y() + v.z() * w.z());
    }

    #[test]
    fn vector3_cross() {
        let v = Vector3_new(1.0, 2.0, 3.0);
        let w = Vector3_new(4.0, 5.0, 6.0);
        let cross = Vector3_cross(&v, &w);
        assert_eq!(cross.x(), v.y() * w.z() - v.z() * w.y());
        assert_eq!(cross.y(), v.z() * w.x() - v.x() * w.z());
        assert_eq!(cross.z(), v.x() * w.y() - v.y() * w.x());
    }

    #[test]
    fn vector3_equals() {
        let v = Vector3_new(1.0, 2.0, 3.0);
        let w = Vector3_new(1.0, 2.0, 3.0);
        assert!(Vector3_equals(&v, &w));
    }

    #[test]
    fn vector4_new() {
        let v = Vector4_new(1.0, 2.0, 3.0, 4.0);
        assert_eq!(v.x(), 1.0);
        assert_eq!(v.y(), 2.0);
        assert_eq!(v.z(), 3.0);
        assert_eq!(v.w(), 4.0);
    }

    #[test]
    fn vector4_norm() {
        let v = Vector4_new(1.0, 2.0, 3.0, 4.0);
        assert_eq!(
            v.norm(),
            (v.x() * v.x() + v.y() * v.y() + v.z() * v.z() + v.w() * v.w()).sqrt()
        );
    }

    #[test]
    fn vector4_normalize() {
        let mut v = Vector4_new(1.0, 2.0, 3.0, 4.0);
        let norm = v.norm();
        v.as_mut().expect("").normalize();
        assert_eq!(v.x(), 1.0 / norm);
        assert_eq!(v.y(), 2.0 / norm);
        assert_eq!(v.z(), 3.0 / norm);
        assert_eq!(v.w(), 4.0 / norm);
    }

    #[test]
    fn vector4_dot() {
        let v = Vector4_new(1.0, 2.0, 3.0, 4.0);
        let w = Vector4_new(5.0, 6.0, 7.0, 8.0);
        assert_eq!(
            Vector4_dot(&v, &w),
            v.x() * w.x() + v.y() * w.y() + v.z() * w.z() + v.w() * w.w()
        );
    }

    #[test]
    fn vector4_equals() {
        let v = Vector4_new(1.0, 2.0, 3.0, 4.0);
        let w = Vector4_new(1.0, 2.0, 3.0, 4.0);
        assert!(Vector4_equals(&v, &w));
    }

    #[test]
    fn matrix3_new() {
        let m = Matrix3_new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
        let row1 = Matrix3_row(&m, 0);
        let row2 = Matrix3_row(&m, 1);
        let row3 = Matrix3_row(&m, 2);
        assert_eq!(row1.x(), 1.0);
        assert_eq!(row1.y(), 2.0);
        assert_eq!(row1.z(), 3.0);
        assert_eq!(row2.x(), 4.0);
        assert_eq!(row2.y(), 5.0);
        assert_eq!(row2.z(), 6.0);
        assert_eq!(row3.x(), 7.0);
        assert_eq!(row3.y(), 8.0);
        assert_eq!(row3.z(), 9.0);
    }

    #[test]
    fn matrix3_col() {
        let m = Matrix3_new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
        let col1 = Matrix3_col(&m, 0);
        let col2 = Matrix3_col(&m, 1);
        let col3 = Matrix3_col(&m, 2);
        assert_eq!(col1.x(), 1.0);
        assert_eq!(col1.y(), 4.0);
        assert_eq!(col1.z(), 7.0);
        assert_eq!(col2.x(), 2.0);
        assert_eq!(col2.y(), 5.0);
        assert_eq!(col2.z(), 8.0);
        assert_eq!(col3.x(), 3.0);
        assert_eq!(col3.y(), 6.0);
        assert_eq!(col3.z(), 9.0);
    }

    #[test]
    fn matrix3_cofactor() {
        let m = Matrix3_new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
        assert_eq!(m.cofactor(0, 0), -3.0);
        assert_eq!(m.cofactor(0, 1), 6.0);
        assert_eq!(m.cofactor(0, 2), -3.0);
        assert_eq!(m.cofactor(1, 0), 6.0);
        assert_eq!(m.cofactor(1, 1), -12.0);
        assert_eq!(m.cofactor(1, 2), 6.0);
        assert_eq!(m.cofactor(2, 0), -3.0);
        assert_eq!(m.cofactor(2, 1), 6.0);
        assert_eq!(m.cofactor(2, 2), -3.0);
    }

    #[test]
    fn matrix3_cofactor_matrix() {
        let m = Matrix3_new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
        let cofactor_matrix = Matrix3_cofactor_matrix(&m);
        assert_eq!(Matrix3_row(&cofactor_matrix, 0).x(), m.cofactor(0, 0));
    }

    #[test]
    fn matrix3_determinant() {
        let m = Matrix3_new(6.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
        assert_eq!(m.determinant(), -15.);
    }

    #[test]
    fn matrix3_is_singular() {
        let m = Matrix3_new(6.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
        assert_eq!(m.is_singular(), m.determinant() == 0.0);
    }

    #[test]
    fn matrix3_transpose() {
        let m = Matrix3_new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
        let t = Matrix3_transpose(&m);
        let n = Matrix3_new(1.0, 4.0, 7.0, 2.0, 5.0, 8.0, 3.0, 6.0, 9.0);
        assert!(Matrix3_equals(&t, &n));
    }

    #[test]
    fn matrix3_equals() {
        let m = Matrix3_new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
        let n = Matrix3_new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
        assert!(Matrix3_equals(&m, &n));
    }

    #[test]
    fn matrix3_adjoint() {
        let m = Matrix3_new(6.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
        let adj = Matrix3_adjoint(&m);
        assert!(Matrix3_equals(&Matrix3_transpose(&Matrix3_cofactor_matrix(&m)), &adj));
    }

    #[test]
    fn matrix3_operator_mul() {
        let m = Matrix3_new(1., 2., 3., 4., 5., 6., 7., 8., 9.);
        let n = Matrix3_new(9., 8., 7., 6., 5., 4., 3., 2., 1.);
        let p = Matrix3_new(30., 24., 18., 84., 69., 54., 138., 114., 90.);
        assert!(Matrix3_equals(&p, &Matrix3_operator_mul(&m, &n)));
    }

    #[test]
    fn matrix3_inverse() {
        let k_tolerance = 1e-10;
        let m = Matrix3_new(1., 12., 3., 8., 5., 3., 6., 14., 9.);
        let inv = Matrix3_inverse(&m);
        let identity = Matrix3_new(1., 0., 0., 0., 1., 0., 0., 0., 1.);
        let product = Matrix3_operator_mul(&m, &inv);
        assert!((Matrix3_row(&product, 0).x() - Matrix3_row(&identity, 0).x()).abs() < k_tolerance);
        assert!((Matrix3_row(&product, 0).y() - Matrix3_row(&identity, 0).y()).abs() < k_tolerance);
        assert!((Matrix3_row(&product, 0).z() - Matrix3_row(&identity, 0).z()).abs() < k_tolerance);
        assert!((Matrix3_row(&product, 1).x() - Matrix3_row(&identity, 1).x()).abs() < k_tolerance);
        assert!((Matrix3_row(&product, 1).y() - Matrix3_row(&identity, 1).y()).abs() < k_tolerance);
        assert!((Matrix3_row(&product, 1).z() - Matrix3_row(&identity, 1).z()).abs() < k_tolerance);
        assert!((Matrix3_row(&product, 2).x() - Matrix3_row(&identity, 2).x()).abs() < k_tolerance);
        assert!((Matrix3_row(&product, 2).y() - Matrix3_row(&identity, 2).y()).abs() < k_tolerance);
        assert!((Matrix3_row(&product, 2).z() - Matrix3_row(&identity, 2).z()).abs() < k_tolerance);
    }

    #[test]
    fn matrix3_operator_sum() {
        let m = Matrix3_new(1., 2., 3., 4., 5., 6., 7., 8., 9.);
        let n = Matrix3_new(9., 8., 7., 6., 5., 4., 3., 2., 1.);
        let p = Matrix3_new(10., 10., 10., 10., 10., 10., 10., 10., 10.);
        assert!(Matrix3_equals(&p, &Matrix3_operator_sum(&m, &n)));
    }

    #[test]
    fn matrix3_operator_sub() {
        let m = Matrix3_new(1., 2., 3., 4., 5., 6., 7., 8., 9.);
        let n = Matrix3_new(9., 8., 7., 6., 5., 4., 3., 2., 1.);
        let p = Matrix3_new(-8., -6., -4., -2., 0., 2., 4., 6., 8.);
        assert!(Matrix3_equals(&p, &Matrix3_operator_sub(&m, &n)));
    }

    #[test]
    fn matrix3_operator_divide_by_scalar() {
        let m = Matrix3_new(1., 2., 3., 4., 5., 6., 7., 8., 9.);
        let n = 2.0;
        let p = Matrix3_new(0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5);
        assert!(Matrix3_equals(&p, &Matrix3_operator_divide_by_scalar(&m, n)));
    }

    #[test]
    fn matrix3_to_str() {
        let m = Matrix3_new(1., 2., 3., 4., 5., 6., 7., 8., 9.);
        assert_eq!(Matrix3_to_str(&m), "{{1, 2, 3},\n{4, 5, 6},\n{7, 8, 9}}");
    }

    #[test]
    fn quaternion_new() {
        let q = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        assert_eq!(q.w(), 1.0);
        assert_eq!(q.x(), 2.0);
        assert_eq!(q.y(), 3.0);
        assert_eq!(q.z(), 4.0);
    }

    #[test]
    fn quaternion_dot() {
        let q = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        let r = Quaternion_new(5.0, 6.0, 7.0, 8.0);
        assert_eq!(q.dot(&r), q.w() * r.w() + q.x() * r.x() + q.y() * r.y() + q.z() * r.z());
    }

    #[test]
    fn quaternion_angular_distance() {
        let q = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        let r = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        assert_eq!(q.AngularDistance(&r), 0.);
    }

    #[test]
    fn quaternion_norm() {
        let q = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        assert_eq!(
            q.norm(),
            (q.w() * q.w() + q.x() * q.x() + q.y() * q.y() + q.z() * q.z()).sqrt()
        );
    }

    #[test]
    fn quaternion_normalize() {
        let mut q = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        let norm = q.norm();
        q.as_mut().expect("").normalize();
        assert_eq!(q.w(), 1.0 / norm);
        assert_eq!(q.x(), 2.0 / norm);
        assert_eq!(q.y(), 3.0 / norm);
        assert_eq!(q.z(), 4.0 / norm);
    }

    #[test]
    fn quaternion_squared_norm() {
        let q = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        assert_eq!(
            q.squared_norm(),
            q.w() * q.w() + q.x() * q.x() + q.y() * q.y() + q.z() * q.z()
        );
    }

    #[test]
    fn quaternion_vec() {
        let q = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        let v = Quaternion_vec(&q);
        assert_eq!(v.x(), q.x());
        assert_eq!(v.y(), q.y());
        assert_eq!(v.z(), q.z());
    }

    #[test]
    fn quaternion_coeffs() {
        let q = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        let v = Quaternion_coeffs(&q);
        assert_eq!(v.x(), q.w());
        assert_eq!(v.y(), q.x());
        assert_eq!(v.z(), q.y());
        assert_eq!(v.w(), q.z());
    }

    #[test]
    fn quaternion_inverse() {
        let q = Quaternion_new(1.0, 0.0, 0.0, 0.0);
        let inv = Quaternion_Inverse(&q);
        assert!(Quaternion_equals(&inv, &Quaternion_Inverse(&q)));
    }

    #[test]
    fn quaternion_equals() {
        let q = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        let r = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        assert!(Quaternion_equals(&q, &r));
    }

    #[test]
    fn quaternion_conjugate() {
        let q = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        let conj = Quaternion_conjugate(&q);
        assert_eq!(conj.w(), q.w());
        assert_eq!(conj.x(), -q.x());
        assert_eq!(conj.y(), -q.y());
        assert_eq!(conj.z(), -q.z());
    }

    #[test]
    fn quaternion_to_rotation_matrix() {
        let q = Quaternion_new(1.0, 0.0, 0.0, 0.0);
        let r = Quaternion_ToRotationMatrix(&q);
        assert_eq!(Matrix3_row(&r, 0).x(), 1.0);
        assert_eq!(Matrix3_row(&r, 1).y(), 1.0);
        assert_eq!(Matrix3_row(&r, 2).z(), 1.0);
    }

    #[test]
    fn quaternion_transform_vector() {
        let q = Quaternion_new(1.0, 0.0, 0.0, 0.0);
        let v = Vector3_new(1.0, 2.0, 3.0);
        let w = Quaternion_TransformVector(&q, &v);
        assert!(Vector3_equals(&w, &v));
    }

    #[test]
    fn quaternion_is_approx() {
        let q = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        let r = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        assert!(Quaternion_IsApprox(&q, &r, 1e-10));
    }

    #[test]
    fn quaternion_to_str() {
        let q = Quaternion_new(1.0, 2.0, 3.0, 4.0);
        assert_eq!(Quaternion_to_str(&q), "(w: 1, x: 2, y: 3, z: 4)");
    }

    #[test]
    fn roll_pitch_yaw_new() {
        let rpy = RollPitchYaw_new(1.0, 2.0, 3.0);
        assert_eq!(rpy.roll_angle(), 1.0);
        assert_eq!(rpy.pitch_angle(), 2.0);
        assert_eq!(rpy.yaw_angle(), 3.0);
    }

    #[test]
    fn roll_pitch_yaw_vector() {
        let rpy = RollPitchYaw_new(1.0, 2.0, 3.0);
        let v = rpy.vector();
        assert_eq!(v.x(), 1.0);
        assert_eq!(v.y(), 2.0);
        assert_eq!(v.z(), 3.0);
    }

    #[test]
    fn roll_pitch_yaw_set() {
        let mut rpy = RollPitchYaw_new(1.0, 2.0, 3.0);
        RollPitchYaw_set(rpy.as_mut().expect(""), 4.0, 5.0, 6.0);
        assert_eq!(rpy.roll_angle(), 4.0);
        assert_eq!(rpy.pitch_angle(), 5.0);
        assert_eq!(rpy.yaw_angle(), 6.0);
    }

    #[test]
    fn roll_pitch_yaw_set_from_quaternion() {
        let q = Quaternion_new(1.0, 0.0, 0.0, 0.0);
        let mut rpy = RollPitchYaw_new(1.0, 2.0, 3.0);
        RollPitchYaw_SetFromQuaternion(rpy.as_mut().expect(""), &q);
        assert_eq!(rpy.roll_angle(), 0.0);
        assert_eq!(rpy.pitch_angle(), 0.0);
        assert_eq!(rpy.yaw_angle(), 0.0);
    }

    #[test]
    fn roll_pitch_yaw_to_quaternion() {
        let rpy = RollPitchYaw_new(0.0, 0.0, 0.0);
        let q = RollPitchYaw_ToQuaternion(&rpy);
        assert_eq!(q.w(), 1.);
        assert_eq!(q.x(), 0.);
        assert_eq!(q.y(), 0.);
        assert_eq!(q.z(), 0.);
    }

    #[test]
    fn roll_pitch_yaw_to_matrix() {
        let rpy = RollPitchYaw_new(0.0, 0.0, 0.0);
        let m = RollPitchYaw_ToMatrix(&rpy);
        assert_eq!(Matrix3_row(&m, 0).x(), 1.0);
        assert_eq!(Matrix3_row(&m, 1).y(), 1.0);
        assert_eq!(Matrix3_row(&m, 2).z(), 1.0);
    }

    #[test]
    fn roll_pitch_yaw_calc_rotation_matrix_dt() {
        let rpy = RollPitchYaw_new(0.0, 0.0, 0.0);
        let v = Vector3_new(1.0, 1.0, 1.0);
        let m = RollPitchYaw_CalcRotationMatrixDt(&rpy, &v);
        assert_eq!(Matrix3_row(&m, 0).x(), 0.0);
        assert_eq!(Matrix3_row(&m, 1).y(), 0.0);
        assert_eq!(Matrix3_row(&m, 2).z(), 0.0);
    }
}
