use std::collections::HashMap;

use rbx_mesh::mesh::{Vertex2, Vertex2Truncated};
use strafesnet_common::{integer::Planar64Vec3,model::{self, ColorId, IndexedVertex, NormalId, PolygonGroup, PolygonList, PositionId, TextureCoordinateId, VertexId}};

#[derive(Debug)]
pub enum Error{
	Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
	RbxMesh(rbx_mesh::mesh::Error)
}
impl std::fmt::Display for Error{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
		write!(f,"{self:?}")
	}
}
impl std::error::Error for Error{}

fn ingest_vertices2<
	AcquirePosId,
	AcquireTexId,
	AcquireNormalId,
	AcquireColorId,
	AcquireVertexId,
>(
	vertices:Vec<Vertex2>,
	acquire_pos_id:&mut AcquirePosId,
	acquire_tex_id:&mut AcquireTexId,
	acquire_normal_id:&mut AcquireNormalId,
	acquire_color_id:&mut AcquireColorId,
	acquire_vertex_id:&mut AcquireVertexId,
)->Result<HashMap<rbx_mesh::mesh::VertexId2,VertexId>,Error>
where
	AcquirePosId:FnMut([f32;3])->Result<PositionId,Error>,
	AcquireTexId:FnMut([f32;2])->TextureCoordinateId,
	AcquireNormalId:FnMut([f32;3])->Result<NormalId,Error>,
	AcquireColorId:FnMut([f32;4])->ColorId,
	AcquireVertexId:FnMut(IndexedVertex)->VertexId,
{
	//this monster is collecting a map of old_vertices_index -> unique_vertices_index
	//while also doing the inserting unique entries into lists simultaneously
	Ok(vertices.into_iter().enumerate().map(|(vertex_id,vertex)|Ok((
		rbx_mesh::mesh::VertexId2(vertex_id as u32),
		acquire_vertex_id(IndexedVertex{
			pos:acquire_pos_id(vertex.pos)?,
			tex:acquire_tex_id(vertex.tex),
			normal:acquire_normal_id(vertex.norm)?,
			color:acquire_color_id(vertex.color.map(|f|f as f32/255.0f32))
		}),
	))).collect::<Result<_,_>>()?)
}
fn ingest_vertices_truncated2<
	AcquirePosId,
	AcquireTexId,
	AcquireNormalId,
	AcquireVertexId,
>(
	vertices:Vec<Vertex2Truncated>,
	acquire_pos_id:&mut AcquirePosId,
	acquire_tex_id:&mut AcquireTexId,
	acquire_normal_id:&mut AcquireNormalId,
	static_color_id:ColorId,//pick one color and fill everything with it
	acquire_vertex_id:&mut AcquireVertexId,
)->Result<HashMap<rbx_mesh::mesh::VertexId2,VertexId>,Error>
where
	AcquirePosId:FnMut([f32;3])->Result<PositionId,Error>,
	AcquireTexId:FnMut([f32;2])->TextureCoordinateId,
	AcquireNormalId:FnMut([f32;3])->Result<NormalId,Error>,
	AcquireVertexId:FnMut(IndexedVertex)->VertexId,
{
	//this monster is collecting a map of old_vertices_index -> unique_vertices_index
	//while also doing the inserting unique entries into lists simultaneously
	Ok(vertices.into_iter().enumerate().map(|(vertex_id,vertex)|Ok((
		rbx_mesh::mesh::VertexId2(vertex_id as u32),
		acquire_vertex_id(IndexedVertex{
			pos:acquire_pos_id(vertex.pos)?,
			tex:acquire_tex_id(vertex.tex),
			normal:acquire_normal_id(vertex.norm)?,
			color:static_color_id
		}),
	))).collect::<Result<_,_>>()?)
}

fn ingest_faces2_lods3(
	polygon_groups:&mut Vec<PolygonGroup>,
	vertex_id_map:&HashMap<rbx_mesh::mesh::VertexId2,VertexId>,
	faces:&Vec<rbx_mesh::mesh::Face2>,
	lods:&Vec<rbx_mesh::mesh::Lod3>
){
	//faces have to be split into polygon groups based on lod
	polygon_groups.extend(lods.windows(2).map(|lod_pair|
		PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().map(|face|
			vec![vertex_id_map[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]]
		).collect()))
	))
}

pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<model::Mesh,Error>{
	//generate that mesh boi
	let mut unique_pos=Vec::new();
	let mut pos_id_from=HashMap::new();
	let mut unique_tex=Vec::new();
	let mut tex_id_from=HashMap::new();
	let mut unique_normal=Vec::new();
	let mut normal_id_from=HashMap::new();
	let mut unique_color=Vec::new();
	let mut color_id_from=HashMap::new();
	let mut unique_vertices=Vec::new();
	let mut vertex_id_from=HashMap::new();
	let mut polygon_groups=Vec::new();
	let mut acquire_pos_id=|pos|{
		let p=Planar64Vec3::try_from(pos).map_err(Error::Planar64Vec3)?;
		Ok(PositionId::new(*pos_id_from.entry(p).or_insert_with(||{
			let pos_id=unique_pos.len();
			unique_pos.push(p);
			pos_id
		}) as u32))
	};
	let mut acquire_tex_id=|tex|{
		let h=bytemuck::cast::<[f32;2],[u32;2]>(tex);
		TextureCoordinateId::new(*tex_id_from.entry(h).or_insert_with(||{
			let tex_id=unique_tex.len();
			unique_tex.push(glam::Vec2::from_array(tex));
			tex_id
		}) as u32)
	};
	let mut acquire_normal_id=|normal|{
		let n=Planar64Vec3::try_from(normal).map_err(Error::Planar64Vec3)?;
		Ok(NormalId::new(*normal_id_from.entry(n).or_insert_with(||{
			let normal_id=unique_normal.len();
			unique_normal.push(n);
			normal_id
		}) as u32))
	};
	let mut acquire_color_id=|color|{
		let h=bytemuck::cast::<[f32;4],[u32;4]>(color);
		ColorId::new(*color_id_from.entry(h).or_insert_with(||{
			let color_id=unique_color.len();
			unique_color.push(glam::Vec4::from_array(color));
			color_id
		}) as u32)
	};
	let mut acquire_vertex_id=|vertex:IndexedVertex|{
		VertexId::new(*vertex_id_from.entry(vertex.clone()).or_insert_with(||{
			let vertex_id=unique_vertices.len();
			unique_vertices.push(vertex);
			vertex_id
		}) as u32)
	};
	match rbx_mesh::read_versioned(roblox_mesh_bytes.cursor()).map_err(Error::RbxMesh)?{
		rbx_mesh::mesh::VersionedMesh::Version1(mesh)=>{
			let color_id=acquire_color_id([1.0f32;4]);
			polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.vertices.chunks_exact(3).map(|trip|{
				let mut ingest_vertex1=|vertex:&rbx_mesh::mesh::Vertex1|Ok(acquire_vertex_id(IndexedVertex{
					pos:acquire_pos_id(vertex.pos)?,
					tex:acquire_tex_id([vertex.tex[0],vertex.tex[1]]),
					normal:acquire_normal_id(vertex.norm)?,
					color:color_id,
				}));
				Ok(vec![ingest_vertex1(&trip[0])?,ingest_vertex1(&trip[1])?,ingest_vertex1(&trip[2])?])
			}).collect::<Result<_,_>>()?)));
		},
		rbx_mesh::mesh::VersionedMesh::Version2(mesh)=>{
			let vertex_id_map=match mesh.header.sizeof_vertex{
				rbx_mesh::mesh::SizeOfVertex2::Truncated=>{
					//pick white and make all the vertices white
					let color_id=acquire_color_id([1.0f32;4]);
					ingest_vertices_truncated2(mesh.vertices_truncated,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,color_id,&mut acquire_vertex_id)
				},
				rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id),
			}?;
			//one big happy group for all the faces
			polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|face|
				vec![vertex_id_map[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]]
			).collect())));
		},
		rbx_mesh::mesh::VersionedMesh::Version3(mesh)=>{
			let vertex_id_map=match mesh.header.sizeof_vertex{
				rbx_mesh::mesh::SizeOfVertex2::Truncated=>{
					let color_id=acquire_color_id([1.0f32;4]);
					ingest_vertices_truncated2(mesh.vertices_truncated,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,color_id,&mut acquire_vertex_id)
				},
				rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id),
			}?;
			ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
		},
		rbx_mesh::mesh::VersionedMesh::Version4(mesh)=>{
			let vertex_id_map=ingest_vertices2(
				mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id
			)?;
			ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
		},
		rbx_mesh::mesh::VersionedMesh::Version5(mesh)=>{
			let vertex_id_map=ingest_vertices2(
				mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id
			)?;
			ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
		},
	}
	Ok(model::Mesh{
		unique_pos,
		unique_normal,
		unique_tex,
		unique_color,
		unique_vertices,
		polygon_groups,
		//these should probably be moved to the model...
		graphics_groups:Vec::new(),
		physics_groups:Vec::new(),
	})
}