use crate::math::{Isometry, Point, Real, Vector};
use crate::query::{
    self, details::NonlinearTOIMode, ClosestPoints, Contact, NonlinearRigidMotion, QueryDispatcher,
    Unsupported, TOI,
};
#[cfg(feature = "std")]
use crate::query::{
    contact_manifolds::ContactManifoldsWorkspace, query_dispatcher::PersistentQueryDispatcher,
    ContactManifold,
};
use crate::shape::{HalfSpace, Segment, Shape, ShapeType};
#[derive(Debug, Clone)]
pub struct DefaultQueryDispatcher;
impl QueryDispatcher for DefaultQueryDispatcher {
    fn intersection_test(
        &self,
        pos12: &Isometry<Real>,
        shape1: &dyn Shape,
        shape2: &dyn Shape,
    ) -> Result<bool, Unsupported> {
        if let (Some(b1), Some(b2)) = (shape1.as_ball(), shape2.as_ball()) {
            let p12 = Point::from(pos12.translation.vector);
            Ok(query::details::intersection_test_ball_ball(&p12, b1, b2))
        } else if let (Some(c1), Some(c2)) = (shape1.as_cuboid(), shape2.as_cuboid()) {
            Ok(query::details::intersection_test_cuboid_cuboid(
                &pos12, c1, c2,
            ))
        } else if let (Some(t1), Some(c2)) = (shape1.as_triangle(), shape2.as_cuboid()) {
            Ok(query::details::intersection_test_triangle_cuboid(
                &pos12, t1, c2,
            ))
        } else if let (Some(c1), Some(t2)) = (shape1.as_cuboid(), shape2.as_triangle()) {
            Ok(query::details::intersection_test_cuboid_triangle(
                &pos12, c1, t2,
            ))
        } else if let Some(b1) = shape1.as_ball() {
            Ok(query::details::intersection_test_ball_point_query(
                &pos12, b1, shape2,
            ))
        } else if let Some(b2) = shape2.as_ball() {
            Ok(query::details::intersection_test_point_query_ball(
                &pos12, shape1, b2,
            ))
        } else if let (Some(p1), Some(s2)) =
            (shape1.as_shape::<HalfSpace>(), shape2.as_support_map())
        {
            Ok(query::details::intersection_test_halfspace_support_map(
                pos12, p1, s2,
            ))
        } else if let (Some(s1), Some(p2)) =
            (shape1.as_support_map(), shape2.as_shape::<HalfSpace>())
        {
            Ok(query::details::intersection_test_support_map_halfspace(
                pos12, s1, p2,
            ))
        } else if let (Some(s1), Some(s2)) = (shape1.as_support_map(), shape2.as_support_map()) {
            Ok(query::details::intersection_test_support_map_support_map(
                pos12, s1, s2,
            ))
        } else {
            #[cfg(feature = "std")]
            if let Some(c1) = shape1.as_composite_shape() {
                return Ok(query::details::intersection_test_composite_shape_shape(
                    self, pos12, c1, shape2,
                ));
            } else if let Some(c2) = shape2.as_composite_shape() {
                return Ok(query::details::intersection_test_shape_composite_shape(
                    self, pos12, shape1, c2,
                ));
            }
            Err(Unsupported)
        }
    }
    fn distance(
        &self,
        pos12: &Isometry<Real>,
        shape1: &dyn Shape,
        shape2: &dyn Shape,
    ) -> Result<Real, Unsupported> {
        let ball1 = shape1.as_ball();
        let ball2 = shape2.as_ball();
        if let (Some(b1), Some(b2)) = (ball1, ball2) {
            let p2 = Point::from(pos12.translation.vector);
            Ok(query::details::distance_ball_ball(b1, &p2, b2))
        } else if let (Some(b1), true) = (ball1, shape2.is_convex()) {
            Ok(query::details::distance_ball_convex_polyhedron(
                pos12, b1, shape2,
            ))
        } else if let (true, Some(b2)) = (shape1.is_convex(), ball2) {
            Ok(query::details::distance_convex_polyhedron_ball(
                pos12, shape1, b2,
            ))
        } else if let (Some(c1), Some(c2)) = (shape1.as_cuboid(), shape2.as_cuboid()) {
            Ok(query::details::distance_cuboid_cuboid(pos12, c1, c2))
        } else if let (Some(s1), Some(s2)) = (shape1.as_segment(), shape2.as_segment()) {
            Ok(query::details::distance_segment_segment(pos12, s1, s2))
        } else if let (Some(p1), Some(s2)) =
            (shape1.as_shape::<HalfSpace>(), shape2.as_support_map())
        {
            Ok(query::details::distance_halfspace_support_map(
                pos12, p1, s2,
            ))
        } else if let (Some(s1), Some(p2)) =
            (shape1.as_support_map(), shape2.as_shape::<HalfSpace>())
        {
            Ok(query::details::distance_support_map_halfspace(
                pos12, s1, p2,
            ))
        } else if let (Some(s1), Some(s2)) = (shape1.as_support_map(), shape2.as_support_map()) {
            Ok(query::details::distance_support_map_support_map(
                pos12, s1, s2,
            ))
        } else {
            #[cfg(feature = "std")]
            if let Some(c1) = shape1.as_composite_shape() {
                return Ok(query::details::distance_composite_shape_shape(
                    self, pos12, c1, shape2,
                ));
            } else if let Some(c2) = shape2.as_composite_shape() {
                return Ok(query::details::distance_shape_composite_shape(
                    self, pos12, shape1, c2,
                ));
            }
            Err(Unsupported)
        }
    }
    fn contact(
        &self,
        pos12: &Isometry<Real>,
        shape1: &dyn Shape,
        shape2: &dyn Shape,
        prediction: Real,
    ) -> Result<Option<Contact>, Unsupported> {
        let ball1 = shape1.as_ball();
        let ball2 = shape2.as_ball();
        if let (Some(b1), Some(b2)) = (ball1, ball2) {
            Ok(query::details::contact_ball_ball(pos12, b1, b2, prediction))
        } else if let (Some(p1), Some(s2)) =
            (shape1.as_shape::<HalfSpace>(), shape2.as_support_map())
        {
            Ok(query::details::contact_halfspace_support_map(
                pos12, p1, s2, prediction,
            ))
        } else if let (Some(s1), Some(p2)) =
            (shape1.as_support_map(), shape2.as_shape::<HalfSpace>())
        {
            Ok(query::details::contact_support_map_halfspace(
                pos12, s1, p2, prediction,
            ))
        } else if let (Some(b1), true) = (ball1, shape2.is_convex()) {
            Ok(query::details::contact_ball_convex_polyhedron(
                pos12, b1, shape2, prediction,
            ))
        } else if let (true, Some(b2)) = (shape1.is_convex(), ball2) {
            Ok(query::details::contact_convex_polyhedron_ball(
                pos12, shape1, b2, prediction,
            ))
        } else {
            #[cfg(feature = "std")]
            if let (Some(s1), Some(s2)) = (shape1.as_support_map(), shape2.as_support_map()) {
                return Ok(query::details::contact_support_map_support_map(
                    pos12, s1, s2, prediction,
                ));
            } else if let Some(c1) = shape1.as_composite_shape() {
                return Ok(query::details::contact_composite_shape_shape(
                    self, pos12, c1, shape2, prediction,
                ));
            } else if let Some(c2) = shape2.as_composite_shape() {
                return Ok(query::details::contact_shape_composite_shape(
                    self, pos12, shape1, c2, prediction,
                ));
            }
            Err(Unsupported)
        }
    }
    fn closest_points(
        &self,
        pos12: &Isometry<Real>,
        shape1: &dyn Shape,
        shape2: &dyn Shape,
        max_dist: Real,
    ) -> Result<ClosestPoints, Unsupported> {
        let ball1 = shape1.as_ball();
        let ball2 = shape2.as_ball();
        if let (Some(b1), Some(b2)) = (ball1, ball2) {
            Ok(query::details::closest_points_ball_ball(
                &pos12, b1, b2, max_dist,
            ))
        } else if let (Some(b1), true) = (ball1, shape2.is_convex()) {
            Ok(query::details::closest_points_ball_convex_polyhedron(
                pos12, b1, shape2, max_dist,
            ))
        } else if let (true, Some(b2)) = (shape1.is_convex(), ball2) {
            Ok(query::details::closest_points_convex_polyhedron_ball(
                pos12, shape1, b2, max_dist,
            ))
        } else if let (Some(s1), Some(s2)) =
            (shape1.as_shape::<Segment>(), shape2.as_shape::<Segment>())
        {
            Ok(query::details::closest_points_segment_segment(
                &pos12, s1, s2, max_dist,
            ))
        } else if let (Some(s1), Some(s2)) = (shape1.as_segment(), shape2.as_segment()) {
            Ok(query::details::closest_points_segment_segment(
                pos12, s1, s2, max_dist,
            ))
        } else if let (Some(t1), Some(c2)) = (shape1.as_triangle(), shape2.as_cuboid()) {
            Ok(query::details::closest_points_triangle_cuboid(
                pos12, t1, c2, max_dist,
            ))
        } else if let (Some(p1), Some(s2)) =
            (shape1.as_shape::<HalfSpace>(), shape2.as_support_map())
        {
            Ok(query::details::closest_points_halfspace_support_map(
                &pos12, p1, s2, max_dist,
            ))
        } else if let (Some(s1), Some(p2)) =
            (shape1.as_support_map(), shape2.as_shape::<HalfSpace>())
        {
            Ok(query::details::closest_points_support_map_halfspace(
                &pos12, s1, p2, max_dist,
            ))
        } else if let (Some(s1), Some(s2)) = (shape1.as_support_map(), shape2.as_support_map()) {
            Ok(query::details::closest_points_support_map_support_map(
                &pos12, s1, s2, max_dist,
            ))
        } else {
            #[cfg(feature = "std")]
            if let Some(c1) = shape1.as_composite_shape() {
                return Ok(query::details::closest_points_composite_shape_shape(
                    self, &pos12, c1, shape2, max_dist,
                ));
            } else if let Some(c2) = shape2.as_composite_shape() {
                return Ok(query::details::closest_points_shape_composite_shape(
                    self, &pos12, shape1, c2, max_dist,
                ));
            }
            Err(Unsupported)
        }
    }
    fn time_of_impact(
        &self,
        pos12: &Isometry<Real>,
        local_vel12: &Vector<Real>,
        shape1: &dyn Shape,
        shape2: &dyn Shape,
        max_toi: Real,
        stop_at_penetration: bool,
    ) -> Result<Option<TOI>, Unsupported> {
        if let (Some(b1), Some(b2)) = (shape1.as_ball(), shape2.as_ball()) {
            Ok(query::details::time_of_impact_ball_ball(
                pos12,
                local_vel12,
                b1,
                b2,
                max_toi,
            ))
        } else if let (Some(p1), Some(s2)) =
            (shape1.as_shape::<HalfSpace>(), shape2.as_support_map())
        {
            Ok(query::details::time_of_impact_halfspace_support_map(
                pos12,
                local_vel12,
                p1,
                s2,
                max_toi,
                stop_at_penetration,
            ))
        } else if let (Some(s1), Some(p2)) =
            (shape1.as_support_map(), shape2.as_shape::<HalfSpace>())
        {
            Ok(query::details::time_of_impact_support_map_halfspace(
                pos12,
                local_vel12,
                s1,
                p2,
                max_toi,
                stop_at_penetration,
            ))
        } else {
            #[cfg(feature = "std")]
            if let Some(heightfield1) = shape1.as_heightfield() {
                return query::details::time_of_impact_heightfield_shape(
                    self,
                    pos12,
                    local_vel12,
                    heightfield1,
                    shape2,
                    max_toi,
                    stop_at_penetration,
                );
            } else if let Some(heightfield2) = shape1.as_heightfield() {
                return query::details::time_of_impact_shape_heightfield(
                    self,
                    pos12,
                    local_vel12,
                    shape1,
                    heightfield2,
                    max_toi,
                    stop_at_penetration,
                );
            } else if let (Some(s1), Some(s2)) = (shape1.as_support_map(), shape2.as_support_map())
            {
                return Ok(query::details::time_of_impact_support_map_support_map(
                    pos12,
                    local_vel12,
                    s1,
                    s2,
                    max_toi,
                    stop_at_penetration,
                ));
            } else if let Some(c1) = shape1.as_composite_shape() {
                return Ok(query::details::time_of_impact_composite_shape_shape(
                    self,
                    pos12,
                    local_vel12,
                    c1,
                    shape2,
                    max_toi,
                    stop_at_penetration,
                ));
            } else if let Some(c2) = shape2.as_composite_shape() {
                return Ok(query::details::time_of_impact_shape_composite_shape(
                    self,
                    pos12,
                    local_vel12,
                    shape1,
                    c2,
                    max_toi,
                    stop_at_penetration,
                ));
            }
            Err(Unsupported)
        }
    }
    fn nonlinear_time_of_impact(
        &self,
        motion1: &NonlinearRigidMotion,
        shape1: &dyn Shape,
        motion2: &NonlinearRigidMotion,
        shape2: &dyn Shape,
        start_time: Real,
        end_time: Real,
        stop_at_penetration: bool,
    ) -> Result<Option<TOI>, Unsupported> {
        if let (Some(sm1), Some(sm2)) = (shape1.as_support_map(), shape2.as_support_map()) {
            let mode = if stop_at_penetration {
                NonlinearTOIMode::StopAtPenetration
            } else {
                NonlinearTOIMode::directional_toi(shape1, shape2)
            };
            Ok(
                query::details::nonlinear_time_of_impact_support_map_support_map(
                    self, motion1, sm1, shape1, motion2, sm2, shape2, start_time, end_time, mode,
                ),
            )
        } else {
            #[cfg(feature = "std")]
            if let Some(c1) = shape1.as_composite_shape() {
                return Ok(
                    query::details::nonlinear_time_of_impact_composite_shape_shape(
                        self,
                        motion1,
                        c1,
                        motion2,
                        shape2,
                        start_time,
                        end_time,
                        stop_at_penetration,
                    ),
                );
            } else if let Some(c2) = shape2.as_composite_shape() {
                return Ok(
                    query::details::nonlinear_time_of_impact_shape_composite_shape(
                        self,
                        motion1,
                        shape1,
                        motion2,
                        c2,
                        start_time,
                        end_time,
                        stop_at_penetration,
                    ),
                );
            }
            Err(Unsupported)
        }
    }
}
#[cfg(feature = "std")]
impl<ManifoldData, ContactData> PersistentQueryDispatcher<ManifoldData, ContactData>
    for DefaultQueryDispatcher
where
    ManifoldData: Default + Clone,
    ContactData: Default + Copy,
{
    fn contact_manifolds(
        &self,
        pos12: &Isometry<Real>,
        shape1: &dyn Shape,
        shape2: &dyn Shape,
        prediction: Real,
        manifolds: &mut Vec<ContactManifold<ManifoldData, ContactData>>,
        workspace: &mut Option<ContactManifoldsWorkspace>,
    ) -> Result<(), Unsupported> {
        use crate::query::contact_manifolds::*;
        let composite1 = shape1.as_composite_shape();
        let composite2 = shape2.as_composite_shape();
        if let (Some(composite1), Some(composite2)) = (composite1, composite2) {
            contact_manifolds_composite_shape_composite_shape(
                self, pos12, composite1, composite2, prediction, manifolds, workspace,
            );
            return Ok(());
        }
        match (shape1.shape_type(), shape2.shape_type()) {
            (ShapeType::TriMesh, _) | (_, ShapeType::TriMesh) => {
                contact_manifolds_trimesh_shape_shapes(
                    self, pos12, shape1, shape2, prediction, manifolds, workspace,
                );
            }
            (ShapeType::HeightField, _) => {
                if let Some(composite2) = composite2 {
                    contact_manifolds_heightfield_composite_shape(
                        self,
                        pos12,
                        &pos12.inverse(),
                        shape1.as_heightfield().unwrap(),
                        composite2,
                        prediction,
                        manifolds,
                        workspace,
                        false,
                    )
                } else {
                    contact_manifolds_heightfield_shape_shapes(
                        self, pos12, shape1, shape2, prediction, manifolds, workspace,
                    );
                }
            }
            (_, ShapeType::HeightField) => {
                if let Some(composite1) = composite1 {
                    contact_manifolds_heightfield_composite_shape(
                        self,
                        &pos12.inverse(),
                        pos12,
                        shape2.as_heightfield().unwrap(),
                        composite1,
                        prediction,
                        manifolds,
                        workspace,
                        true,
                    )
                } else {
                    contact_manifolds_heightfield_shape_shapes(
                        self, pos12, shape1, shape2, prediction, manifolds, workspace,
                    );
                }
            }
            _ => {
                if let Some(composite1) = composite1 {
                    contact_manifolds_composite_shape_shape(
                        self, pos12, composite1, shape2, prediction, manifolds, workspace, false,
                    );
                } else if let Some(composite2) = composite2 {
                    contact_manifolds_composite_shape_shape(
                        self,
                        &pos12.inverse(),
                        composite2,
                        shape1,
                        prediction,
                        manifolds,
                        workspace,
                        true,
                    );
                } else {
                    if manifolds.is_empty() {
                        manifolds.push(ContactManifold::new());
                    }
                    return self.contact_manifold_convex_convex(
                        pos12,
                        shape1,
                        shape2,
                        prediction,
                        &mut manifolds[0],
                    );
                }
            }
        }
        Ok(())
    }
    fn contact_manifold_convex_convex(
        &self,
        pos12: &Isometry<Real>,
        shape1: &dyn Shape,
        shape2: &dyn Shape,
        prediction: Real,
        manifold: &mut ContactManifold<ManifoldData, ContactData>,
    ) -> Result<(), Unsupported> {
        use crate::query::contact_manifolds::*;
        match (shape1.shape_type(), shape2.shape_type()) {
            (ShapeType::Ball, ShapeType::Ball) => {
                contact_manifold_ball_ball_shapes(pos12, shape1, shape2, prediction, manifold)
            }
            (ShapeType::Cuboid, ShapeType::Cuboid) =>
                contact_manifold_cuboid_cuboid_shapes(pos12, shape1, shape2, prediction, manifold)
            ,
            (ShapeType::Capsule, ShapeType::Capsule) => {
                contact_manifold_capsule_capsule_shapes(pos12, shape1, shape2, prediction, manifold)
            }
            (_, ShapeType::Ball) | (ShapeType::Ball, _) => {
                contact_manifold_convex_ball_shapes(pos12, shape1, shape2, prediction, manifold)
            }
            (ShapeType::Triangle, ShapeType::Cuboid) | (ShapeType::Cuboid, ShapeType::Triangle) => {
                contact_manifold_cuboid_triangle_shapes(pos12, shape1, shape2, prediction, manifold)
            }
            (ShapeType::HalfSpace, _) => {
                if let Some((pfm2, border_radius2)) = shape2.as_polygonal_feature_map() {
                    contact_manifold_halfspace_pfm(
                        pos12,
                        shape1.as_halfspace().unwrap(),
                        pfm2,
                        border_radius2,
                        prediction,
                        manifold,
                        false
                    )
                } else {
                    return Err(Unsupported)
                }
            }
            (_, ShapeType::HalfSpace) => {
                if let Some((pfm1, border_radius1)) = shape1.as_polygonal_feature_map() {
                    contact_manifold_halfspace_pfm(
                        &pos12.inverse(),
                        shape2.as_halfspace().unwrap(),
                        pfm1,
                        border_radius1,
                        prediction,
                        manifold,
                        true
                    )
                } else {
                    return Err(Unsupported)
                }
            }
            _ => {
                if let (Some(pfm1), Some(pfm2)) = (
                    shape1.as_polygonal_feature_map(),
                    shape2.as_polygonal_feature_map(),
                ) {
                    contact_manifold_pfm_pfm(
                        pos12, pfm1.0, pfm1.1, pfm2.0, pfm2.1, prediction, manifold,
                    )
                } else {
                    return Err(Unsupported);
                }
            }
        }
        Ok(())
    }
}