use crate::core::texture::FloatTexture;
use crate::shapes::*;
use crate::utils::resolve_filename;
use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{anyhow, bail, Result};
use parking_lot::Mutex;
use shared::core::shape::*;
use shared::shapes::*;
use shared::{Transform, Ptr};
use std::collections::HashMap;
use std::sync::Arc;

pub static ALL_TRIANGLE_MESHES: Mutex<Vec<Arc<TriangleMesh>>> = Mutex::new(Vec::new());
pub static ALL_BILINEAR_MESHES: Mutex<Vec<Arc<BilinearPatchMesh>>> = Mutex::new(Vec::new());

#[derive(Debug, Clone)]
pub struct ShapeWithContext {
    pub shape: Ptr<Shape>,
    pub entity_index: usize,
    pub shape_index: usize,
}

pub trait CreateShape {
    fn create(
        render_from_object: Transform,
        object_from_render: Transform,
        reverse_orientation: bool,
        parameters: ParameterDictionary,
        float_textures: &HashMap<String, Arc<FloatTexture>>,
        loc: FileLoc,
        arena: &Arena,
    ) -> Result<Vec<Ptr<Shape>>>;
}

pub trait ShapeFactory {
    fn create(
        name: &str,
        render_from_object: Transform,
        object_from_render: Transform,
        reverse_orientation: bool,
        parameters: ParameterDictionary,
        float_textures: &HashMap<String, Arc<FloatTexture>>,
        loc: FileLoc,
        arena: &Arena,
    ) -> Result<Vec<Ptr<Shape>>>;
}

impl ShapeFactory for Shape {
    fn create(
        name: &str,
        render_from_object: Transform,
        object_from_render: Transform,
        reverse_orientation: bool,
        parameters: ParameterDictionary,
        float_textures: &HashMap<String, Arc<FloatTexture>>,
        loc: FileLoc,
        arena: &Arena,
    ) -> Result<Vec<Ptr<Shape>>> {
        match name {
            "sphere" => SphereShape::create(
                render_from_object,
                object_from_render,
                reverse_orientation,
                parameters,
                float_textures,
                loc,
                arena,
            ),
            "cylinder" => CylinderShape::create(
                render_from_object,
                object_from_render,
                reverse_orientation,
                parameters,
                float_textures,
                loc,
                arena,
            ),
            "disk" => DiskShape::create(
                render_from_object,
                object_from_render,
                reverse_orientation,
                parameters,
                float_textures,
                loc,
                arena,
            ),
            "bilinearmesh" => BilinearPatchShape::create(
                render_from_object,
                object_from_render,
                reverse_orientation,
                parameters,
                float_textures,
                loc,
                arena,
            ),
            "trianglemesh" => TriangleShape::create(
                render_from_object,
                object_from_render,
                reverse_orientation,
                parameters,
                float_textures,
                loc,
                arena,
            ),
            "plymesh" => {
                let filename = resolve_filename(&parameters.get_one_string("filename", "")?);
                if filename.is_empty() {
                    bail!("{}: plymesh requires \"filename\" parameter", loc);
                }

                let tri_mesh =
                    TriangleMesh::from_ply(&filename, &render_from_object, reverse_orientation)?;

                let host_arc = Arc::new(tri_mesh);
                let mut global_store = ALL_TRIANGLE_MESHES.lock();
                global_store.push(host_arc.clone());
                drop(global_store);

                let n_tris = host_arc..n_triangles;
                let mesh_ptr = Ptr::from(&host_arc);
                let shapes: Vec<Ptr<Shape>> = (0..n_tris)
                    .map(|i| {
                        let tri_shape = Shape::Triangle(TriangleShape {
                            mesh: mesh_ptr,
                            tri_index: i as i32,
                        });
                        arena.alloc(tri_shape)
                    })
                    .collect();

                Ok(shapes)
            }
            _ => Err(anyhow!("Unknown shape name")),
        }
    }
}
