Documentation
use macaw::BoundingBox;
use macaw::ColorRgba8;
use macaw::Vec3;

/// Bounding box & sphere of mesh.
///
/// For usability, this is translated to `ffi::MeshBounds`.
/// The Vec3-as-four-floats thing makes it ugly to try to map it directly.
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with_speedy", derive(speedy::Writable, speedy::Readable))]
pub struct MeshBounds {
    /// Bounding box minimum
    pub bounding_box_min: Vec3,
    /// Bounding box maximum
    pub bounding_box_max: Vec3,
    /// Bounding sphere radius from origin
    pub bounding_sphere_radius: f32,
}

impl MeshBounds {
    /// The bounding box.
    #[inline]
    pub fn bounding_box(&self) -> BoundingBox {
        BoundingBox::from_min_max(self.bounding_box_min, self.bounding_box_max)
    }
}

/// User-friendly mesh buffer contents struct
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with_speedy", derive(speedy::Writable, speedy::Readable))]
pub struct MeshData {
    /// Debug name,  used to make warnings and errors more readable.
    pub name: String,

    /// List of indices, this can be empty to have a non-indexed mesh
    pub indices: Vec<u32>,

    /// Vertex positions
    pub positions: Vec<[f32; 3]>,
    /// Vertex normals (optional)
    pub normals: Option<Vec<[f32; 3]>>,
    /// Vertex colors (optional)
    pub colors: Option<Vec<ColorRgba8>>,
}

impl MeshData {
    /// Validate mesh buffer contents
    ///
    /// This will for example check that the indices are valid and that we have
    /// the same amount of vertices in the position/normal/color streams.
    pub fn validate(&self) -> Result<(), String> {
        if self.indices.len() % 3 != 0 {
            return Err(
                "Index count not divisible by 3, which is required for triangle lists".to_owned(),
            );
        }

        if !self.indices.is_empty() {
            let max = *self.indices.iter().max().unwrap();
            if max >= self.positions.len() as u32 {
                return Err(format!(
                    "Index out of bounds: {}, vertex count: {}",
                    max,
                    self.positions.len()
                ));
            }
        }
        if let Some(normals) = &self.normals {
            if normals.len() != self.positions.len() {
                return Err(format!(
                    "Mismatch in amount of normals vs positions: {} vs {}",
                    normals.len(),
                    self.positions.len()
                ));
            }
        }
        if let Some(colors) = &self.colors {
            if colors.len() != self.positions.len() {
                return Err(format!(
                    "Mismatch in amount of colors vs positions: {} vs {}",
                    colors.len(),
                    self.positions.len()
                ));
            }
        }
        Ok(())
    }
}

/// Simple mesh representation, layer on to of `MeshData`
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "with_serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "with_speedy", derive(speedy::Writable, speedy::Readable))]
pub struct Mesh {
    /// Bounding box minimum
    pub bounding_box_min: Vec3,
    /// Bounding box maximum
    pub bounding_box_max: Vec3,
    /// bounding sphere radius from origin
    pub bounding_sphere_radius: f32,
    /// Amount of primitives (triangles) in mesh
    pub primitive_count: u64,
    /// Optional mesh data
    pub data: Option<MeshData>,
}

impl Mesh {
    /// Create an empty mesh with no data, centered around 0,0,0
    pub fn empty() -> Self {
        Self {
            bounding_box_min: Vec3::ZERO,
            bounding_box_max: Vec3::ZERO,
            bounding_sphere_radius: 0.0,
            primitive_count: 0,
            data: None,
        }
    }

    /// Validate mesh contents
    pub fn validate(&self) -> Result<(), String> {
        if let Some(data) = &self.data {
            data.validate()
        } else {
            Ok(())
        }
    }

    /// Get bounds of the mesh
    pub fn get_bounds(&self) -> MeshBounds {
        MeshBounds {
            bounding_box_min: self.bounding_box_min,
            bounding_box_max: self.bounding_box_max,
            bounding_sphere_radius: self.bounding_sphere_radius,
        }
    }
}

impl Default for Mesh {
    fn default() -> Self {
        Self::empty()
    }
}