use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,IndexedVertexList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId};
use strafesnet_common::integer::Planar64Vec3;

#[derive(Debug)]
pub enum Primitives{
	Sphere,
	Cube,
	Cylinder,
	Wedge,
	CornerWedge,
}
#[derive(Hash,PartialEq,Eq)]
pub enum CubeFace{
	Right,
	Top,
	Back,
	Left,
	Bottom,
	Front,
}
const CUBE_DEFAULT_TEXTURE_COORDS:[TextureCoordinate;4]=[
	TextureCoordinate::new(0.0,0.0),
	TextureCoordinate::new(1.0,0.0),
	TextureCoordinate::new(1.0,1.0),
	TextureCoordinate::new(0.0,1.0),
];
const CUBE_DEFAULT_VERTICES:[Planar64Vec3;8]=[
	Planar64Vec3::int(-1,-1, 1),//0 left bottom back
	Planar64Vec3::int( 1,-1, 1),//1 right bottom back
	Planar64Vec3::int( 1, 1, 1),//2 right top back
	Planar64Vec3::int(-1, 1, 1),//3 left top back
	Planar64Vec3::int(-1, 1,-1),//4 left top front
	Planar64Vec3::int( 1, 1,-1),//5 right top front
	Planar64Vec3::int( 1,-1,-1),//6 right bottom front
	Planar64Vec3::int(-1,-1,-1),//7 left bottom front
];
const CUBE_DEFAULT_NORMALS:[Planar64Vec3;6]=[
	Planar64Vec3::int( 1, 0, 0),//CubeFace::Right
	Planar64Vec3::int( 0, 1, 0),//CubeFace::Top
	Planar64Vec3::int( 0, 0, 1),//CubeFace::Back
	Planar64Vec3::int(-1, 0, 0),//CubeFace::Left
	Planar64Vec3::int( 0,-1, 0),//CubeFace::Bottom
	Planar64Vec3::int( 0, 0,-1),//CubeFace::Front
];
const CUBE_DEFAULT_POLYS:[[[u32;3];4];6]=[
	// right (1, 0, 0)
	[
		[6,2,0],//[vertex,tex,norm]
		[5,1,0],
		[2,0,0],
		[1,3,0],
	],
	// top (0, 1, 0)
	[
		[5,3,1],
		[4,2,1],
		[3,1,1],
		[2,0,1],
	],
	// back (0, 0, 1)
	[
		[0,3,2],
		[1,2,2],
		[2,1,2],
		[3,0,2],
	],
	// left (-1, 0, 0)
	[
		[0,2,3],
		[3,1,3],
		[4,0,3],
		[7,3,3],
	],
	// bottom (0,-1, 0)
	[
		[1,1,4],
		[0,0,4],
		[7,3,4],
		[6,2,4],
	],
	// front (0, 0,-1)
	[
		[4,1,5],
		[5,0,5],
		[6,3,5],
		[7,2,5],
	],
];

#[derive(Hash,PartialEq,Eq)]
pub enum WedgeFace{
	Right,
	TopFront,
	Back,
	Left,
	Bottom,
}
const WEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
	Planar64Vec3::int( 1, 0, 0),//Wedge::Right
	Planar64Vec3::int( 0, 1,-1),//Wedge::TopFront
	Planar64Vec3::int( 0, 0, 1),//Wedge::Back
	Planar64Vec3::int(-1, 0, 0),//Wedge::Left
	Planar64Vec3::int( 0,-1, 0),//Wedge::Bottom
];
/*
local cornerWedgeVerticies = {
	Vector3.new(-1/2,-1/2,-1/2),7
	Vector3.new(-1/2,-1/2, 1/2),0
	Vector3.new( 1/2,-1/2,-1/2),6
	Vector3.new( 1/2,-1/2, 1/2),1
	Vector3.new( 1/2, 1/2,-1/2),5
}
*/
#[derive(Hash,PartialEq,Eq)]
pub enum CornerWedgeFace{
	Right,
	TopBack,
	TopLeft,
	Bottom,
	Front,
}
const CORNERWEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
	Planar64Vec3::int( 1, 0, 0),//CornerWedge::Right
	Planar64Vec3::int( 0, 1, 1),//CornerWedge::BackTop
	Planar64Vec3::int(-1, 1, 0),//CornerWedge::LeftTop
	Planar64Vec3::int( 0,-1, 0),//CornerWedge::Bottom
	Planar64Vec3::int( 0, 0,-1),//CornerWedge::Front
];
pub fn unit_sphere(render:RenderConfigId)->Mesh{
	unit_cube(render)
}
#[derive(Default)]
pub struct CubeFaceDescription([Option<FaceDescription>;6]);
impl CubeFaceDescription{
	pub fn insert(&mut self,index:CubeFace,value:FaceDescription){
		self.0[index as usize]=Some(value);
	}
	pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,6>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{
		self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
	}
}
pub fn unit_cube(render:RenderConfigId)->Mesh{
	let mut t=CubeFaceDescription::default();
	t.insert(CubeFace::Right,FaceDescription::new_with_render_id(render));
	t.insert(CubeFace::Top,FaceDescription::new_with_render_id(render));
	t.insert(CubeFace::Back,FaceDescription::new_with_render_id(render));
	t.insert(CubeFace::Left,FaceDescription::new_with_render_id(render));
	t.insert(CubeFace::Bottom,FaceDescription::new_with_render_id(render));
	t.insert(CubeFace::Front,FaceDescription::new_with_render_id(render));
	generate_partial_unit_cube(t)
}
pub fn unit_cylinder(render:RenderConfigId)->Mesh{
	//lmao
	unit_cube(render)
}
#[derive(Default)]
pub struct WedgeFaceDescription([Option<FaceDescription>;5]);
impl WedgeFaceDescription{
	pub fn insert(&mut self,index:WedgeFace,value:FaceDescription){
		self.0[index as usize]=Some(value);
	}
	pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,5>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{
		self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
	}
}
pub fn unit_wedge(render:RenderConfigId)->Mesh{
	let mut t=WedgeFaceDescription::default();
	t.insert(WedgeFace::Right,FaceDescription::new_with_render_id(render));
	t.insert(WedgeFace::TopFront,FaceDescription::new_with_render_id(render));
	t.insert(WedgeFace::Back,FaceDescription::new_with_render_id(render));
	t.insert(WedgeFace::Left,FaceDescription::new_with_render_id(render));
	t.insert(WedgeFace::Bottom,FaceDescription::new_with_render_id(render));
	generate_partial_unit_wedge(t)
}
#[derive(Default)]
pub struct CornerWedgeFaceDescription([Option<FaceDescription>;5]);
impl CornerWedgeFaceDescription{
	pub fn insert(&mut self,index:CornerWedgeFace,value:FaceDescription){
		self.0[index as usize]=Some(value);
	}
	pub fn pairs(self)->std::iter::FilterMap<std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>,5>>,impl FnMut((usize,Option<FaceDescription>))->Option<(usize,FaceDescription)>>{
		self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
	}
}
pub fn unit_cornerwedge(render:RenderConfigId)->Mesh{
	let mut t=CornerWedgeFaceDescription::default();
	t.insert(CornerWedgeFace::Right,FaceDescription::new_with_render_id(render));
	t.insert(CornerWedgeFace::TopBack,FaceDescription::new_with_render_id(render));
	t.insert(CornerWedgeFace::TopLeft,FaceDescription::new_with_render_id(render));
	t.insert(CornerWedgeFace::Bottom,FaceDescription::new_with_render_id(render));
	t.insert(CornerWedgeFace::Front,FaceDescription::new_with_render_id(render));
	generate_partial_unit_cornerwedge(t)
}

#[derive(Clone)]
pub struct FaceDescription{
	pub render:RenderConfigId,
	pub transform:glam::Affine2,
	pub color:Color4,
}
impl FaceDescription{
	pub fn new_with_render_id(render:RenderConfigId)->Self {
		Self{
			render,
			transform:glam::Affine2::IDENTITY,
			color:Color4::new(1.0,1.0,1.0,0.0),//zero alpha to hide the default texture
		}
	}
}
pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
	let mut generated_pos=Vec::new();
	let mut generated_tex=Vec::new();
	let mut generated_normal=Vec::new();
	let mut generated_color=Vec::new();
	let mut generated_vertices=Vec::new();
	let mut polygon_groups=Vec::new();
	let mut graphics_groups=Vec::new();
	let mut physics_group=IndexedPhysicsGroup::default();
	let mut transforms=Vec::new();
	//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
	for (face_id,face_description) in face_descriptions.pairs(){
		//assume that scanning short lists is faster than hashing.
		let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
			transform_index
		}else{
			//create new transform_index
			let transform_index=transforms.len();
			transforms.push(face_description.transform);
			generated_tex.extend(CUBE_DEFAULT_TEXTURE_COORDS.map(|tex|
				face_description.transform.transform_point2(tex)
			));
			transform_index
		} as u32;
		let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){
			color_index
		}else{
			//create new color_index
			let color_index=generated_color.len();
			generated_color.push(face_description.color);
			color_index
		} as u32;
		//always push normal
		let normal_index=generated_normal.len() as u32;
		generated_normal.push(CUBE_DEFAULT_NORMALS[face_id]);
		//push vertices as they are needed
		let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
		polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
			CUBE_DEFAULT_POLYS[face_id].map(|tup|{
				let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
				let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
					pos_index
				}else{
					//create new pos_index
					let pos_index=generated_pos.len();
					generated_pos.push(pos);
					pos_index
				} as u32;
				//always push vertex
				let vertex=IndexedVertex{
					pos:PositionId::new(pos_index),
					tex:TextureCoordinateId::new(tup[1]+4*transform_index),
					normal:NormalId::new(normal_index),
					color:ColorId::new(color_index),
				};
				let vert_index=generated_vertices.len();
				generated_vertices.push(vertex);
				VertexId::new(vert_index as u32)
			}).to_vec(),
		])));
		graphics_groups.push(IndexedGraphicsGroup{
			render:face_description.render,
			groups:vec![group_id],
		});
		physics_group.groups.push(group_id);
	}
	Mesh{
		unique_pos:generated_pos,
		unique_tex:generated_tex,
		unique_normal:generated_normal,
		unique_color:generated_color,
		unique_vertices:generated_vertices,
		polygon_groups,
		graphics_groups,
		physics_groups:vec![physics_group],
	}
}
//don't think too hard about the copy paste because this is all going into the map tool eventually...
pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh{
	let wedge_default_polys=[
		// right (1, 0, 0)
		vec![
			[6,2,0],//[vertex,tex,norm]
			[2,0,0],
			[1,3,0],
		],
		// FrontTop (0, 1, -1)
		vec![
			[3,1,1],
			[2,0,1],
			[6,3,1],
			[7,2,1],
		],
		// back (0, 0, 1)
		vec![
			[0,3,2],
			[1,2,2],
			[2,1,2],
			[3,0,2],
		],
		// left (-1, 0, 0)
		vec![
			[0,2,3],
			[3,1,3],
			[7,3,3],
		],
		// bottom (0,-1, 0)
		vec![
			[1,1,4],
			[0,0,4],
			[7,3,4],
			[6,2,4],
		],
	];
	let mut generated_pos=Vec::new();
	let mut generated_tex=Vec::new();
	let mut generated_normal=Vec::new();
	let mut generated_color=Vec::new();
	let mut generated_vertices=Vec::new();
	let mut polygon_groups=Vec::new();
	let mut graphics_groups=Vec::new();
	let mut physics_group=IndexedPhysicsGroup::default();
	let mut transforms=Vec::new();
	//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
	for (face_id,face_description) in face_descriptions.pairs(){
		//assume that scanning short lists is faster than hashing.
		let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
			transform_index
		}else{
			//create new transform_index
			let transform_index=transforms.len();
			transforms.push(face_description.transform);
			generated_tex.extend(CUBE_DEFAULT_TEXTURE_COORDS.map(|tex|
				face_description.transform.transform_point2(tex)
			));
			transform_index
		} as u32;
		let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){
			color_index
		}else{
			//create new color_index
			let color_index=generated_color.len();
			generated_color.push(face_description.color);
			color_index
		} as u32;
		//always push normal
		let normal_index=generated_normal.len() as u32;
		generated_normal.push(WEDGE_DEFAULT_NORMALS[face_id]);
		//push vertices as they are needed
		let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
		polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
			wedge_default_polys[face_id].iter().map(|tup|{
				let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
				let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
					pos_index
				}else{
					//create new pos_index
					let pos_index=generated_pos.len();
					generated_pos.push(pos);
					pos_index
				} as u32;
				//always push vertex
				let vertex=IndexedVertex{
					pos:PositionId::new(pos_index),
					tex:TextureCoordinateId::new(tup[1]+4*transform_index),
					normal:NormalId::new(normal_index),
					color:ColorId::new(color_index),
				};
				let vert_index=generated_vertices.len();
				generated_vertices.push(vertex);
				VertexId::new(vert_index as u32)
			}).collect()
		])));
		graphics_groups.push(IndexedGraphicsGroup{
			render:face_description.render,
			groups:vec![group_id],
		});
		physics_group.groups.push(group_id);
	}
	Mesh{
		unique_pos:generated_pos,
		unique_tex:generated_tex,
		unique_normal:generated_normal,
		unique_color:generated_color,
		unique_vertices:generated_vertices,
		polygon_groups,
		graphics_groups,
		physics_groups:vec![physics_group],
	}
}

pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->Mesh{
	let cornerwedge_default_polys=[
		// right (1, 0, 0)
		vec![
			[6,2,0],//[vertex,tex,norm]
			[5,1,0],
			[1,3,0],
		],
		// BackTop (0, 1, 1)
		vec![
			[5,3,1],
			[0,1,1],
			[1,0,1],
		],
		// LeftTop (-1, 1, 0)
		vec![
			[5,3,2],
			[7,2,2],
			[0,1,2],
		],
		// bottom (0,-1, 0)
		vec![
			[1,1,3],
			[0,0,3],
			[7,3,3],
			[6,2,3],
		],
		// front (0, 0,-1)
		vec![
			[5,0,4],
			[6,3,4],
			[7,2,4],
		],
	];
	let mut generated_pos=Vec::new();
	let mut generated_tex=Vec::new();
	let mut generated_normal=Vec::new();
	let mut generated_color=Vec::new();
	let mut generated_vertices=Vec::new();
	let mut polygon_groups=Vec::new();
	let mut graphics_groups=Vec::new();
	let mut physics_group=IndexedPhysicsGroup::default();
	let mut transforms=Vec::new();
	//note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices.
	for (face_id,face_description) in face_descriptions.pairs(){
		//assume that scanning short lists is faster than hashing.
		let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){
			transform_index
		}else{
			//create new transform_index
			let transform_index=transforms.len();
			transforms.push(face_description.transform);
			generated_tex.extend(CUBE_DEFAULT_TEXTURE_COORDS.map(|tex|
				face_description.transform.transform_point2(tex)
			));
			transform_index
		} as u32;
		let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){
			color_index
		}else{
			//create new color_index
			let color_index=generated_color.len();
			generated_color.push(face_description.color);
			color_index
		} as u32;
		//always push normal
		let normal_index=generated_normal.len() as u32;
		generated_normal.push(CORNERWEDGE_DEFAULT_NORMALS[face_id]);
		//push vertices as they are needed
		let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
		polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
			cornerwedge_default_polys[face_id].iter().map(|tup|{
				let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize];
				let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){
					pos_index
				}else{
					//create new pos_index
					let pos_index=generated_pos.len();
					generated_pos.push(pos);
					pos_index
				} as u32;
				//always push vertex
				let vertex=IndexedVertex{
					pos:PositionId::new(pos_index),
					tex:TextureCoordinateId::new(tup[1]+4*transform_index),
					normal:NormalId::new(normal_index),
					color:ColorId::new(color_index),
				};
				let vert_index=generated_vertices.len();
				generated_vertices.push(vertex);
				VertexId::new(vert_index as u32)
			}).collect(),
		])));
		graphics_groups.push(IndexedGraphicsGroup{
			render:face_description.render,
			groups:vec![group_id],
		});
		physics_group.groups.push(group_id);
	}
	Mesh{
		unique_pos:generated_pos,
		unique_tex:generated_tex,
		unique_normal:generated_normal,
		unique_color:generated_color,
		unique_vertices:generated_vertices,
		polygon_groups,
		graphics_groups,
		physics_groups:vec![physics_group],
	}
}