use crate::core::image::{Image, ImageIO};
use crate::core::shape::{CreateShape, ALL_BILINEAR_MESHES};
use crate::core::texture::FloatTexture;
use crate::shapes::mesh::BilinearPatchMesh;
use crate::utils::sampling::PiecewiseConstant2D;
use crate::{Arena, FileLoc, ParameterDictionary};
use anyhow::{anyhow, Result};
use log::warn;
use shared::core::shape::Shape;
use shared::shapes::BilinearPatchShape;
use shared::{Ptr, Transform};
use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;

impl CreateShape for BilinearPatchShape {
    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>>> {
        let mut vertex_indices = parameters.get_int_array("indices")?;
        let p = parameters.get_point3f_array("P")?;
        let mut uv = parameters.get_point2f_array("uv")?;
        let mut n = parameters.get_normal3f_array("N")?;
        let mut face_indices = parameters.get_int_array("faceIndices")?;

        if vertex_indices.is_empty() {
            if p.len() == 4 {
                vertex_indices = vec![0, 1, 2, 3];
            } else {
                return Err(anyhow!(
                    "Vertex indices \"indices\" must be provided with bilinear patch mesh shape."
                ));
            }
        } else if vertex_indices.len() % 4 != 0 {
            let excess = vertex_indices.len() % 4;
            warn!(
                "Number of vertex indices {} not a multiple of 4. Discarding {} excess.",
                vertex_indices.len(),
                excess
            );
            let new_len = vertex_indices.len() - excess;
            vertex_indices.truncate(new_len);
        }

        if p.is_empty() {
            return Err(anyhow!(
                "Vertex positions \"P\" must be provided with bilinear patch mesh shape."
            ));
        }

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

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

        for (_, &idx) in vertex_indices.iter().enumerate() {
            if idx < 0 || idx as usize >= p.len() {
                return Err(anyhow!(
                    "Bilinear patch mesh has out-of-bounds vertex index {} ({} \"P\" values were given). Discarding this mesh.",
                    idx,
                    p.len()
                ));
            }
        }

        let n_patches = vertex_indices.len() / 4;
        if !face_indices.is_empty() && face_indices.len() != n_patches {
            warn!(
                "Number of face indices {} does not match number of bilinear patches {}. Discarding face indices.",
                face_indices.len(),
                n_patches
            );
            face_indices.clear();
        }

        let filename = parameters.get_one_string("emissionfilename", "")?;

        let image_dist = if !filename.is_empty() {
            if !uv.is_empty() {
                warn!(
                    "\"emissionfilename\" is currently ignored for bilinear patches if \"uv\" coordinates have been provided--sorry!"
                );
                None
            } else {
                let im = Image::read(Path::new(&filename), None)?;
                let mut img = im.image;
                img.flip_y();
                Some(PiecewiseConstant2D::from_image(&img))
            }
        } else {
            None
        };

        let host = BilinearPatchMesh::new(
            &render_from_object,
            reverse_orientation,
            vertex_indices,
            p,
            n,
            uv,
            image_dist,
        );

        let host_arc = Arc::new(host);
        let mut global_store = ALL_BILINEAR_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_patches;
        let mesh_ptr = Ptr::from(&host_arc.device);
        let mut shapes = Vec::with_capacity(n_patches as usize);
        for i in 0..n_patches as i32 {
            shapes.push(arena.alloc(Shape::BilinearPatch(BilinearPatchShape {
                mesh: mesh_ptr,
                blp_index: i,
                area: 0.0,
                rectangle: false,
            })));
        }
        Ok(shapes)
    }
}
