use bytemuck::{Pod, Zeroable};
use nalgebra::Vector4;
use rapier3d::parry::math::Point;
use tobj::{LoadError, Model, Material};


#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct Vertex {
    pos: [f32; 4],
    normal: [f32; 4],
    uv: [f32; 2],
}

impl Vertex {
    pub fn position(&self) -> Point<f32> {
        Point::<f32>::new(self.pos[0], self.pos[1], self.pos[2])
    }
    pub fn from(pos: [f32; 3], nor: [f32; 3], uv: [f32; 2]) -> Vertex {
        Vertex {
            pos: [pos[0], pos[1], pos[2], 1.0],
            normal: [nor[0], nor[1], nor[2], 0.0],
            uv: [uv[0], uv[1]],
        }
    }
}

unsafe impl Pod for Vertex {}
unsafe impl Zeroable for Vertex {}

#[derive(Clone, Debug)]
pub struct RawMesh {
    pub vertices: Vec<Vertex>,
    pub indices: Vec<[u32; 3]>,
}


/// We use meshes in a few different places. To keep things simple, we return
/// the most basic, direct-to-memory version. If something fancy needs to be done
/// with it, the fancy stays there
pub fn load_obj(obj_path: &str) -> Result<RawMesh, String> {

    log::info!("Loading object {}", obj_path);

    // Is there no better way to translate error messages?
    let (models, materials) = match tobj::load_obj(obj_path, true) {
        Ok((a, b)) => {Ok((a, b))}
        Err(load_error) => {Err(load_error.to_string())}
    }?;

    // println!("# of models: {}", models.len());
    // println!("# of materials: {}", materials.len());
    // println!("{:?}", materials);

    let mut index_data: Vec<[u32; 3]> = Vec::new();
    let mut vertex_data = Vec::new();

    for model in models {
        let mesh = &model.mesh;

        // Cycle through the faces and chunk out the indices
        let mut next_face = 0;
        for f in 0..mesh.num_face_indices.len() {
            // calculate the next chunk
            let end = next_face + mesh.num_face_indices[f] as usize;
            let face_indices: Vec<_> = mesh.indices[next_face..end].iter().collect();

            if face_indices.len() != 3 {
                return Err("we only handle triangulated faces".to_string());
            }
            index_data.push([*face_indices[0], *face_indices[1], *face_indices[2]]);
            next_face = end;
        }

        if mesh.positions.len() % 3 != 0 {
            return Err(format!("position array not % 3 : {}", mesh.positions.len()))
        }
        if mesh.texcoords.len() % 2 != 0 {
            return Err(format!("position array not % 3 : {}", mesh.positions.len()))
        }
        if mesh.normals.is_empty() {
            return Err(format!("It would be best if this had normals"))
        }
        if mesh.texcoords.is_empty() {
            log::info!("\tMesh texture coordinates empty")
        }

        for v in 0..mesh.positions.len() / 3 {
            let texcoords = if mesh.texcoords.len() == 0 {
                [0.0, 0.0]
            } else {
                [mesh.texcoords[2 * v], mesh.texcoords[2 * v + 1]]
            };

            vertex_data.push(Vertex::from(
                [
                    mesh.positions[3 * v],
                    mesh.positions[3 * v + 1],
                    mesh.positions[3 * v + 2],
                ],
                [
                    mesh.normals[3 * v],
                    mesh.normals[3 * v + 1],
                    mesh.normals[3 * v + 2],
                ],
                texcoords,
            ));
        }
    }
    Ok(RawMesh {
        vertices: vertex_data.to_vec(),
        indices: index_data.to_vec(),
    })
}
