#![cfg(feature = "proptest-support")]
#![allow(non_snake_case)]

use na::Similarity3;

use crate::proptest::*;
use proptest::{prop_assert, prop_assert_eq, proptest};

proptest!(
    #[test]
    fn inverse_is_identity(i in similarity3(), p in point3(), v in vector3()) {
        let ii = i.inverse();

        prop_assert!(relative_eq!(i * ii, Similarity3::identity(), epsilon = 1.0e-7)
            && relative_eq!(ii * i, Similarity3::identity(), epsilon = 1.0e-7)
            && relative_eq!((i * ii) * p, p, epsilon = 1.0e-7)
            && relative_eq!((ii * i) * p, p, epsilon = 1.0e-7)
            && relative_eq!((i * ii) * v, v, epsilon = 1.0e-7)
            && relative_eq!((ii * i) * v, v, epsilon = 1.0e-7))
    }

    #[test]
    #[cfg_attr(rustfmt, rustfmt_skip)]
    fn inverse_is_parts_inversion(
        t in translation3(),
        r in unit_quaternion(),
        scaling in PROPTEST_F64
    ) {
        if !relative_eq!(scaling, 0.0) {
            let s = Similarity3::from_isometry(t * r, scaling);
            prop_assert_eq!(s.inverse(), Similarity3::from_scaling(1.0 / scaling) * r.inverse() * t.inverse())
        }
    }

    #[test]
    #[cfg_attr(rustfmt, rustfmt_skip)]
    fn multiply_equals_alga_transform(
        s in similarity3(),
        v in vector3(),
        p in point3()
    ) {
        prop_assert!(s * v == s.transform_vector(&v)
            && s * p == s.transform_point(&p)
            && relative_eq!(
                s.inverse() * v,
                s.inverse_transform_vector(&v),
                epsilon = 1.0e-7
            )
            && relative_eq!(
                s.inverse() * p,
                s.inverse_transform_point(&p),
                epsilon = 1.0e-7
            ))
    }

    #[test]
    #[cfg_attr(rustfmt, rustfmt_skip)]
    fn composition(
        i in isometry3(),
        uq in unit_quaternion(),
        t in translation3(),
        v in vector3(),
        p in point3(),
        scaling in PROPTEST_F64
    ) {
        if !relative_eq!(scaling, 0.0) {
            let s = Similarity3::from_scaling(scaling);

            // (rotation × translation × scaling) × point = rotation × (translation × (scaling × point))
            prop_assert!(relative_eq!((uq * t * s) * v, uq * (scaling * v), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((uq * t * s) * p, uq * (t * (scaling * p)), epsilon = 1.0e-7));

            // (translation × rotation × scaling) × point = translation × (rotation × (scaling × point))
            prop_assert!(relative_eq!((t * uq * s) * v, uq * (scaling * v), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((t * uq * s) * p, t * (uq * (scaling * p)), epsilon = 1.0e-7));

            // (rotation × isometry × scaling) × point = rotation × (isometry × (scaling × point))
            prop_assert!(relative_eq!((uq * i * s) * v, uq * (i * (scaling * v)), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((uq * i * s) * p, uq * (i * (scaling * p)), epsilon = 1.0e-7));

            // (isometry × rotation × scaling) × point = isometry × (rotation × (scaling × point))
            prop_assert!(relative_eq!((i * uq * s) * v, i * (uq * (scaling * v)), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((i * uq * s) * p, i * (uq * (scaling * p)), epsilon = 1.0e-7));

            // (translation × isometry × scaling) × point = translation × (isometry × (scaling × point))
            prop_assert!(relative_eq!((t * i * s) * v,     (i * (scaling * v)), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((t * i * s) * p, t * (i * (scaling * p)), epsilon = 1.0e-7));

            // (isometry × translation × scaling) × point = isometry × (translation × (scaling × point))
            prop_assert!(relative_eq!((i * t * s) * v, i * (scaling * v),       epsilon = 1.0e-7));
            prop_assert!(relative_eq!((i * t * s) * p, i * (t * (scaling * p)), epsilon = 1.0e-7));


            /*
             * Same as before but with scaling on the middle.
             */
            // (rotation × scaling × translation) × point = rotation × (scaling × (translation × point))
            prop_assert!(relative_eq!((uq * s * t) * v, uq * (scaling * v), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((uq * s * t) * p, uq * (scaling * (t * p)), epsilon = 1.0e-7));

            // (translation × scaling × rotation) × point = translation × (scaling × (rotation × point))
            prop_assert!(relative_eq!((t * s * uq) * v, scaling * (uq * v), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((t * s * uq) * p, t * (scaling * (uq * p)), epsilon = 1.0e-7));

            // (rotation × scaling × isometry) × point = rotation × (scaling × (isometry × point))
            prop_assert!(relative_eq!((uq * s * i) * v, uq * (scaling * (i * v)), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((uq * s * i) * p, uq * (scaling * (i * p)), epsilon = 1.0e-7));

            // (isometry × scaling × rotation) × point = isometry × (scaling × (rotation × point))
            prop_assert!(relative_eq!((i * s * uq) * v, i * (scaling * (uq * v)), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((i * s * uq) * p, i * (scaling * (uq * p)), epsilon = 1.0e-7));

            // (translation × scaling × isometry) × point = translation × (scaling × (isometry × point))
            prop_assert!(relative_eq!((t * s * i) * v,     (scaling * (i * v)), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((t * s * i) * p, t * (scaling * (i * p)), epsilon = 1.0e-7));

            // (isometry × scaling × translation) × point = isometry × (scaling × (translation × point))
            prop_assert!(relative_eq!((i * s * t) * v, i * (scaling * v),       epsilon = 1.0e-7));
            prop_assert!(relative_eq!((i * s * t) * p, i * (scaling * (t * p)), epsilon = 1.0e-7));


            /*
             * Same as before but with scaling on the left.
             */
            // (scaling × rotation × translation) × point = scaling × (rotation × (translation × point))
            prop_assert!(relative_eq!((s * uq * t) * v, scaling * (uq * v), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((s * uq * t) * p, scaling * (uq * (t * p)), epsilon = 1.0e-7));

            // (scaling × translation × rotation) × point = scaling × (translation × (rotation × point))
            prop_assert!(relative_eq!((s * t * uq) * v, scaling * (uq * v), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((s * t * uq) * p, scaling * (t * (uq * p)), epsilon = 1.0e-7));

            // (scaling × rotation × isometry) × point = scaling × (rotation × (isometry × point))
            prop_assert!(relative_eq!((s * uq * i) * v, scaling * (uq * (i * v)), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((s * uq * i) * p, scaling * (uq * (i * p)), epsilon = 1.0e-7));

            // (scaling × isometry × rotation) × point = scaling × (isometry × (rotation × point))
            prop_assert!(relative_eq!((s * i * uq) * v, scaling * (i * (uq * v)), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((s * i * uq) * p, scaling * (i * (uq * p)), epsilon = 1.0e-7));

            // (scaling × translation × isometry) × point = scaling × (translation × (isometry × point))
            prop_assert!(relative_eq!((s * t * i) * v,     (scaling * (i * v)), epsilon = 1.0e-7));
            prop_assert!(relative_eq!((s * t * i) * p, scaling * (t * (i * p)), epsilon = 1.0e-7));

            // (scaling × isometry × translation) × point = scaling × (isometry × (translation × point))
            prop_assert!(relative_eq!((s * i * t) * v, scaling * (i * v),       epsilon = 1.0e-7));
            prop_assert!(relative_eq!((s * i * t) * p, scaling * (i * (t * p)), epsilon = 1.0e-7));
        }
    }

    #[test]
    #[cfg_attr(rustfmt, rustfmt_skip)]
    fn all_op_exist(
        s in similarity3(),
        i in isometry3(),
        uq in unit_quaternion(),
        t in translation3(),
        v in vector3(),
        p in point3()
    ) {
        let sMs = s * s;
        let sMuq = s * uq;
        let sDs = s / s;
        let sDuq = s / uq;

        let sMp = s * p;
        let sMv = s * v;

        let sMt = s * t;
        let tMs = t * s;

        let uqMs = uq * s;
        let uqDs = uq / s;

        let sMi = s * i;
        let sDi = s / i;

        let iMs = i * s;
        let iDs = i / s;

        let mut sMt1 = s;
        let mut sMt2 = s;

        let mut sMs1 = s;
        let mut sMs2 = s;

        let mut sMuq1 = s;
        let mut sMuq2 = s;

        let mut sMi1 = s;
        let mut sMi2 = s;

        let mut sDs1 = s;
        let mut sDs2 = s;

        let mut sDuq1 = s;
        let mut sDuq2 = s;

        let mut sDi1 = s;
        let mut sDi2 = s;

        sMt1 *= t;
        sMt2 *= &t;

        sMs1 *= s;
        sMs2 *= &s;

        sMuq1 *= uq;
        sMuq2 *= &uq;

        sMi1 *= i;
        sMi2 *= &i;

        sDs1 /= s;
        sDs2 /= &s;

        sDuq1 /= uq;
        sDuq2 /= &uq;

        sDi1 /= i;
        sDi2 /= &i;

        prop_assert!(sMt == sMt1
            && sMt == sMt2
            && sMs == sMs1
            && sMs == sMs2
            && sMuq == sMuq1
            && sMuq == sMuq2
            && sMi == sMi1
            && sMi == sMi2
            && sDs == sDs1
            && sDs == sDs2
            && sDuq == sDuq1
            && sDuq == sDuq2
            && sDi == sDi1
            && sDi == sDi2
            && sMs == &s * &s
            && sMs == s * &s
            && sMs == &s * s
            && sMuq == &s * &uq
            && sMuq == s * &uq
            && sMuq == &s * uq
            && sDs == &s / &s
            && sDs == s / &s
            && sDs == &s / s
            && sDuq == &s / &uq
            && sDuq == s / &uq
            && sDuq == &s / uq
            && sMp == &s * &p
            && sMp == s * &p
            && sMp == &s * p
            && sMv == &s * &v
            && sMv == s * &v
            && sMv == &s * v
            && sMt == &s * &t
            && sMt == s * &t
            && sMt == &s * t
            && tMs == &t * &s
            && tMs == t * &s
            && tMs == &t * s
            && uqMs == &uq * &s
            && uqMs == uq * &s
            && uqMs == &uq * s
            && uqDs == &uq / &s
            && uqDs == uq / &s
            && uqDs == &uq / s
            && sMi == &s * &i
            && sMi == s * &i
            && sMi == &s * i
            && sDi == &s / &i
            && sDi == s / &i
            && sDi == &s / i
            && iMs == &i * &s
            && iMs == i * &s
            && iMs == &i * s
            && iDs == &i / &s
            && iDs == i / &s
            && iDs == &i / s)
    }
);
