use crate::core::shape::{CreateShape, ALL_TRIANGLE_MESHES};
use crate::core::texture::FloatTexture;
use crate::shapes::mesh::TriangleMesh;
use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{bail, Result};
use log::warn;
use shared::core::shape::Shape;
use shared::shapes::TriangleShape;
use shared::{Ptr, Transform};
use std::collections::HashMap;
use std::sync::Arc;

impl CreateShape for TriangleShape {
    fn create(
        render_from_object: Transform,
        _object_from_render: Transform,
        reverse_orientation: bool,
        parameters: ParameterDictionary,
        _float_texture: &HashMap<String, Arc<FloatTexture>>,
        _loc: FileLoc,
        arena: &Arena,
    ) -> Result<Vec<Ptr<Shape>>> {
        let mut vertex_indices = parameters.get_int_array("indices")?;
        let p = parameters.get_point3f_array("P")?;
        let mut uvs = parameters.get_point2f_array("uv")?;
        let mut s = parameters.get_vector3f_array("S")?;
        let mut n = parameters.get_normal3f_array("N")?;

        if vertex_indices.is_empty() {
            if p.len() == 3 {
            } else {
                bail!("Vertex indices \"indices\" must be provided with triangle mesh.");
            }
        } else if vertex_indices.len() % 3 != 0 {
            let excess = vertex_indices.len() % 3;
            warn!(
                "Number of vertex indices {} not a multiple of 3. Discarding {} excess.",
                vertex_indices.len(),
                excess
            );
            let new_len = vertex_indices.len() - excess;
            vertex_indices.truncate(new_len);
        }

        if p.is_empty() {
            bail!("Vertex positions \"P\" must be provided with triangle mesh.");
        }

        if !uvs.is_empty() && uvs.len() != p.len() {
            warn!("Number of \"uv\"s for triangle mesh must match \"P\"s. Discarding uvs.");
            uvs.clear();
        }

        if !s.is_empty() && s.len() != p.len() {
            warn!("Number of \"S\"s for triangle mesh must match \"P\"s. Discarding \"S\"s.");
            s.clear();
        }

        if !n.is_empty() && n.len() != p.len() {
            warn!("Number of \"N\"s for triangle mesh must match \"P\"s. Discarding \"N\"s.");
            n.clear();
        }

        for (_, &index) in vertex_indices.iter().enumerate() {
            // Check for negative indices (if keeping i32) or out of bounds
            if index < 0 || index as usize >= p.len() {
                bail!(
                    "TriangleMesh has out-of-bounds vertex index {} ({} \"P\" values were given). Discarding this mesh.",
                    index,
                    p.len()
                );
            }
        }

        let mut face_indices = parameters.get_int_array("faceIndices")?;
        let n_triangles = vertex_indices.len() / 3;

        if !face_indices.is_empty() && face_indices.len() != n_triangles {
            warn!(
                "Number of face indices {} does not match number of triangles {}. Discarding face indices.",
                face_indices.len(),
                n_triangles
            );
            face_indices.clear();
        }

        let host = TriangleMesh::new(
            &render_from_object,
            reverse_orientation,
            vertex_indices,
            p,
            n,
            s,
            uvs,
            face_indices,
        );

        let host_arc = Arc::new(host);
        let mut global_store = ALL_TRIANGLE_MESHES.lock();
        // let mesh_index = global_store.len() as u32;
        global_store.push(host_arc.clone());
        drop(global_store);
        let n_patches = host_arc.device.n_triangles;
        let mesh_ptr = Ptr::from(&host_arc.device);
        let mut shapes = Vec::with_capacity(n_patches as usize);
        for i in 0..n_patches {
            shapes.push(arena.alloc(Shape::Triangle(TriangleShape {
                mesh: mesh_ptr,
                tri_index: i as i32,
            })));
        }

        Ok(shapes)
    }
}
