use crate::bounding_volume::{Aabb, BoundingSphere, BoundingVolume};
use crate::math::{Isometry, Real};
use crate::partitioning::Qbvh;
#[cfg(feature = "dim2")]
use crate::shape::{ConvexPolygon, TriMesh, Triangle};
use crate::shape::{Shape, SharedShape, SimdCompositeShape, TypedSimdCompositeShape};
#[cfg(feature = "dim2")]
use crate::transformation::hertel_mehlhorn;
use crate::utils::DefaultStorage;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone)]
pub struct Compound {
    shapes: Vec<(Isometry<Real>, SharedShape)>,
    qbvh: Qbvh<u32>,
    aabbs: Vec<Aabb>,
    aabb: Aabb,
}
impl Compound {
    pub fn new(shapes: Vec<(Isometry<Real>, SharedShape)>) -> Compound {
        assert!(
            !shapes.is_empty(),
            "A compound shape must contain at least one shape."
        );
        let mut aabbs = Vec::new();
        let mut leaves = Vec::new();
        let mut aabb = Aabb::new_invalid();
        for (i, &(ref delta, ref shape)) in shapes.iter().enumerate() {
            let bv = shape.compute_aabb(delta);
            aabb.merge(&bv);
            aabbs.push(bv.clone());
            leaves.push((i as u32, bv));
            if shape.as_composite_shape().is_some() {
                panic!("Nested composite shapes are not allowed.");
            }
        }
        let mut qbvh = Qbvh::new();
        qbvh.clear_and_rebuild(leaves.into_iter(), 0.0);
        Compound {
            shapes,
            qbvh,
            aabbs,
            aabb,
        }
    }
    #[cfg(feature = "dim2")]
    pub fn decompose_trimesh(trimesh: &TriMesh) -> Option<Self> {
        let polygons = hertel_mehlhorn(trimesh.vertices(), trimesh.indices());
        let shapes: Option<Vec<_>> = polygons
            .into_iter()
            .map(|points| {
                match points.len() {
                    3 => {
                        let triangle = Triangle::new(points[0], points[1], points[2]);
                        Some(SharedShape::new(triangle))
                    }
                    _ => ConvexPolygon::from_convex_polyline(points).map(SharedShape::new),
                }
                .map(|shape| (Isometry::identity(), shape))
            })
            .collect();
        Some(Self::new(shapes?))
    }
}
impl Compound {
    #[inline]
    pub fn shapes(&self) -> &[(Isometry<Real>, SharedShape)] {
        &self.shapes[..]
    }
    #[inline]
    pub fn local_aabb(&self) -> &Aabb {
        &self.aabb
    }
    #[inline]
    pub fn local_bounding_sphere(&self) -> BoundingSphere {
        self.aabb.bounding_sphere()
    }
    #[inline]
    pub fn aabbs(&self) -> &[Aabb] {
        &self.aabbs[..]
    }
    #[inline]
    pub fn qbvh(&self) -> &Qbvh<u32> {
        &self.qbvh
    }
}
impl SimdCompositeShape for Compound {
    #[inline]
    fn map_part_at(&self, shape_id: u32, f: &mut dyn FnMut(Option<&Isometry<Real>>, &dyn Shape)) {
        if let Some(shape) = self.shapes.get(shape_id as usize) {
            f(Some(&shape.0), &*shape.1)
        }
    }
    #[inline]
    fn qbvh(&self) -> &Qbvh<u32> {
        &self.qbvh
    }
}
impl TypedSimdCompositeShape for Compound {
    type PartShape = dyn Shape;
    type PartId = u32;
    type QbvhStorage = DefaultStorage;
    #[inline(always)]
    fn map_typed_part_at(
        &self,
        i: u32,
        mut f: impl FnMut(Option<&Isometry<Real>>, &Self::PartShape),
    ) {
        if let Some((part_pos, part)) = self.shapes.get(i as usize) {
            f(Some(part_pos), &**part)
        }
    }
    #[inline(always)]
    fn map_untyped_part_at(
        &self,
        i: u32,
        mut f: impl FnMut(Option<&Isometry<Real>>, &Self::PartShape),
    ) {
        if let Some((part_pos, part)) = self.shapes.get(i as usize) {
            f(Some(part_pos), &**part)
        }
    }
    #[inline]
    fn typed_qbvh(&self) -> &Qbvh<u32> {
        &self.qbvh
    }
}