use std::collections::HashMap;
use crate::loader::MeshIndex;
use crate::primitives;
use strafesnet_common::aabb::Aabb;
use strafesnet_common::map;
use strafesnet_common::model;
use strafesnet_common::gameplay_modes::{NormalizedModes,Mode,ModeId,ModeUpdate,ModesBuilder,Stage,StageElement,StageElementBehaviour,StageId,Zone};
use strafesnet_common::gameplay_style;
use strafesnet_common::gameplay_attributes as attr;
use strafesnet_common::integer::{self,vec3,Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3};
use strafesnet_common::model::RenderConfigId;
use strafesnet_deferred_loader::deferred_loader::{RenderConfigDeferredLoader,MeshDeferredLoader};
use strafesnet_deferred_loader::mesh::Meshes;
use strafesnet_deferred_loader::texture::{RenderConfigs,Texture};

fn class_is_a(class: &str, superclass: &str) -> bool {
	if class==superclass {
		return true
	}
	let class_descriptor=rbx_reflection_database::get().classes.get(class);
	if let Some(descriptor) = &class_descriptor {
		if let Some(class_super) = &descriptor.superclass {
			return class_is_a(&class_super, superclass)
		}
	}
	false
}
fn recursive_collect_superclass(objects: &mut std::vec::Vec<rbx_dom_weak::types::Ref>,dom: &rbx_dom_weak::WeakDom, instance: &rbx_dom_weak::Instance, superclass: &str){
	let mut stack=vec![instance];
	while let Some(item)=stack.pop(){
		for &referent in item.children(){
			if let Some(c)=dom.get_by_ref(referent){
				if class_is_a(c.class.as_str(),superclass){
					objects.push(c.referent());//copy ref
				}
				stack.push(c);
			}
		}
	}
}
fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_weak::types::Vector3)->Planar64Affine3{
	Planar64Affine3::new(
		Planar64Mat3::from_cols([
			vec3::try_from_f32_array([cf.orientation.x.x,cf.orientation.y.x,cf.orientation.z.x]).unwrap()
			*integer::try_from_f32(size.x/2.0).unwrap(),
			vec3::try_from_f32_array([cf.orientation.x.y,cf.orientation.y.y,cf.orientation.z.y]).unwrap()
			*integer::try_from_f32(size.y/2.0).unwrap(),
			vec3::try_from_f32_array([cf.orientation.x.z,cf.orientation.y.z,cf.orientation.z.z]).unwrap()
			*integer::try_from_f32(size.z/2.0).unwrap(),
			].map(|t|t.fix_1())),
		vec3::try_from_f32_array([cf.position.x,cf.position.y,cf.position.z]).unwrap()
	)
}

fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap<model::ModelId,u32>,wormhole_id_to_out_model:&mut HashMap<u32,model::ModelId>)->attr::CollisionAttributes{
	let mut general=attr::GeneralAttributes::default();
	let mut intersecting=attr::IntersectingAttributes::default();
	let mut contacting=attr::ContactingAttributes::default();
	let mut force_can_collide=can_collide;
	let mut force_intersecting=false;
	match name{
		"Water"=>{
			force_can_collide=false;
			//TODO: read stupid CustomPhysicalProperties
			intersecting.water=Some(attr::IntersectingWater{density:Planar64::ONE,viscosity:Planar64::ONE/10,velocity});
		},
		"Accelerator"=>{
			//although the new game supports collidable accelerators, this is a roblox compatability map loader
			force_can_collide=false;
			general.accelerator=Some(attr::Accelerator{acceleration:velocity});
		},
		// "UnorderedCheckpoint"=>general.teleport_behaviour=Some(model::TeleportBehaviour::StageElement(attr::StageElement{
		// 	mode_id:0,
		// 	stage_id:0,
		// 	force:false,
		// 	behaviour:model::StageElementBehaviour::Unordered
		// })),
		"SetVelocity"=>general.trajectory=Some(attr::SetTrajectory::Velocity(velocity)),
		"MapStart"=>{
			force_can_collide=false;
			force_intersecting=true;
			modes_builder.insert_mode(
				ModeId::MAIN,
				Mode::empty(
					gameplay_style::StyleModifiers::roblox_bhop(),
					model_id
				)
			);
		},
		"MapFinish"=>{
			force_can_collide=false;
			force_intersecting=true;
			modes_builder.push_mode_update(
				ModeId::MAIN,
				ModeUpdate::zone(
					model_id,
					Zone::Finish,
				),
			);
		},
		"MapAnticheat"=>{
			force_can_collide=false;
			force_intersecting=true;
			modes_builder.push_mode_update(
				ModeId::MAIN,
				ModeUpdate::zone(
					model_id,
					Zone::Anticheat,
				),
			);
		},
		"Platform"=>{
			modes_builder.push_mode_update(
				ModeId::MAIN,
				ModeUpdate::element(
					model_id,
					StageElement::new(StageId::FIRST,false,StageElementBehaviour::Platform,None),//roblox does not know which stage the platform belongs to
				),
			);
		},
		other=>{
			let regman=lazy_regex::regex!(r"^(BonusStart|WormholeOut)(\d+)$");
			if let Some(captures)=regman.captures(other){
				match &captures[1]{
					"BonusStart"=>{
						force_can_collide=false;
						force_intersecting=true;
						modes_builder.insert_mode(
							ModeId::new(captures[2].parse::<u32>().unwrap()),
							Mode::empty(
								gameplay_style::StyleModifiers::roblox_bhop(),
								model_id
							)
						);
					},
					"WormholeOut"=>{
						//the PhysicsModelId has to exist for it to be teleported to!
						force_intersecting=true;
						//this object is not special in strafe client, but the roblox mapping needs to be converted to model id
						assert!(wormhole_id_to_out_model.insert(captures[2].parse::<u32>().unwrap(),model_id).is_none(),"Cannot have multiple WormholeOut with same id");
					},
					_=>(),
				}
			}else if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$")
			.captures(other){
				force_intersecting=true;
				let stage_id=StageId::new(captures[3].parse::<u32>().unwrap());
				let stage_element=StageElement::new(
					//stage_id:
					stage_id,
					//force:
					match captures.get(1){
						Some(m)=>m.as_str()=="Force",
						None=>false,
					},
					//behaviour:
					match &captures[2]{
						"Spawn"=>{
							modes_builder.insert_stage(
								ModeId::MAIN,
								stage_id,
								Stage::empty(model_id),
							);
							//TODO: let denormalize handle this
							StageElementBehaviour::SpawnAt
						},
						"SpawnAt"=>StageElementBehaviour::SpawnAt,
						//cancollide false so you don't hit the side
						//NOT a decoration
						"Trigger"=>{force_can_collide=false;StageElementBehaviour::Trigger},
						"Teleport"=>{force_can_collide=false;StageElementBehaviour::Teleport},
						"Platform"=>StageElementBehaviour::Platform,
						_=>panic!("regex1[2] messed up bad"),
					},
					None
				);
				modes_builder.push_mode_update(
					ModeId::MAIN,
					ModeUpdate::element(
						model_id,
						stage_element,
					),
				);
			}else if let Some(captures)=lazy_regex::regex!(r"^(Jump|WormholeIn)(\d+)$")
			.captures(other){
				match &captures[1]{
					"Jump"=>modes_builder.push_mode_update(
						ModeId::MAIN,
						ModeUpdate::element(
							model_id,
							//jump_limit:
							StageElement::new(
								StageId::FIRST,
								false,
								StageElementBehaviour::Check,
								Some(captures[2].parse::<u8>().unwrap())
							)
						),
					),
					"WormholeIn"=>{
						force_can_collide=false;
						force_intersecting=true;
						assert!(wormhole_in_model_to_id.insert(model_id,captures[2].parse::<u32>().unwrap()).is_none(),"Impossible");
					},
					_=>panic!("regex2[1] messed up bad"),
				}
			}else if let Some(captures)=lazy_regex::regex!(r"^Bonus(Finish|Anticheat)(\d+)$")
			.captures(other){
				force_can_collide=false;
				force_intersecting=true;
				modes_builder.push_mode_update(
					ModeId::new(captures[2].parse::<u32>().unwrap()),
					ModeUpdate::zone(
						model_id,
						//zone:
						match &captures[1]{
							"Finish"=>Zone::Finish,
							"Anticheat"=>Zone::Anticheat,
							_=>panic!("regex3[1] messed up bad"),
						},
					),
				);
			}
			// else if let Some(captures)=lazy_regex::regex!(r"^Stage(\d+)OrderedCheckpoint(\d+)$")
			// .captures(other){
			// 	match &captures[1]{
			// 		"OrderedCheckpoint"=>modes_builder.push_stage_update(
			// 			ModeId::MAIN,
			// 			StageId::new(0),
			// 			StageUpdate::ordered_checkpoint(captures[2].parse::<u32>().unwrap()),
			// 		),
			// 		_=>panic!("regex3[1] messed up bad"),
			// 	}
			// }
		}
	}
	//need some way to skip this
	if velocity!=vec3::ZERO{
		general.booster=Some(attr::Booster::Velocity(velocity));
	}
	match force_can_collide{
		true=>{
			match name{
				"Bounce"=>contacting.contact_behaviour=Some(attr::ContactingBehaviour::Elastic(u32::MAX)),
				"Surf"=>contacting.contact_behaviour=Some(attr::ContactingBehaviour::Surf),
				"Ladder"=>contacting.contact_behaviour=Some(attr::ContactingBehaviour::Ladder(attr::ContactingLadder{sticky:true})),
				_=>(),
			}
			attr::CollisionAttributes::Contact(attr::ContactAttributes{contacting,general})
		},
		false=>if force_intersecting
		||general.any()
		||intersecting.any()
		{
			attr::CollisionAttributes::Intersect(attr::IntersectAttributes{intersecting,general})
		}else{
			attr::CollisionAttributes::Decoration
		},
	}
}

#[derive(Clone,Copy)]
pub struct RobloxTextureTransform{
	offset_studs_u:f32,
	offset_studs_v:f32,
	studs_per_tile_u:f32,
	studs_per_tile_v:f32,
	size_u:f32,
	size_v:f32,
}
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
pub struct RobloxTextureTransformBits{
	offset_studs_u:u32,
	offset_studs_v:u32,
	studs_per_tile_u:u32,
	studs_per_tile_v:u32,
	size_u:u32,
	size_v:u32,
}
impl RobloxTextureTransform{
	fn identity()->Self{
		Self{
			offset_studs_u:0.0,
			offset_studs_v:0.0,
			studs_per_tile_u:1.0,
			studs_per_tile_v:1.0,
			size_u:1.0,
			size_v:1.0,
		}
	}
	pub fn to_bits(self)->RobloxTextureTransformBits{
		RobloxTextureTransformBits{
			offset_studs_u:self.offset_studs_u.to_bits(),
			offset_studs_v:self.offset_studs_v.to_bits(),
			studs_per_tile_u:self.studs_per_tile_u.to_bits(),
			studs_per_tile_v:self.studs_per_tile_v.to_bits(),
			size_u:self.size_u.to_bits(),
			size_v:self.size_v.to_bits(),
		}
	}
	pub fn affine(&self)->glam::Affine2{
		glam::Affine2::from_translation(
			glam::vec2(self.offset_studs_u/self.studs_per_tile_u,self.offset_studs_v/self.studs_per_tile_v)
		)
		*glam::Affine2::from_scale(
			glam::vec2(self.size_u/self.studs_per_tile_u,self.size_v/self.studs_per_tile_v)
		)
	}
	pub fn set_size(&mut self,size_u:f32,size_v:f32){
		self.size_u=size_u;
		self.size_v=size_v;
	}
}
impl core::hash::Hash for RobloxTextureTransform{
	fn hash<H:core::hash::Hasher>(&self,state:&mut H){
		self.to_bits().hash(state);
	}
}
#[derive(Clone,Copy,Hash,Eq,PartialEq)]
pub struct RobloxFaceTextureDescriptionBits{
	render:RenderConfigId,
	color:[u32;4],
	transform:RobloxTextureTransformBits,
}
#[derive(Clone,Copy)]
pub struct RobloxFaceTextureDescription{
	pub render:RenderConfigId,
	pub color:glam::Vec4,
	pub transform:RobloxTextureTransform,
}
impl core::cmp::PartialEq for RobloxFaceTextureDescription{
	fn eq(&self,other:&Self)->bool{
		self.to_bits().eq(&other.to_bits())
	}
}
impl core::cmp::Eq for RobloxFaceTextureDescription{}
impl core::hash::Hash for RobloxFaceTextureDescription{
	fn hash<H:core::hash::Hasher>(&self,state:&mut H){
		self.to_bits().hash(state);
	}
}
impl RobloxFaceTextureDescription{
	pub fn to_bits(self)->RobloxFaceTextureDescriptionBits{
		RobloxFaceTextureDescriptionBits{
			render:self.render,
			color:self.color.to_array().map(f32::to_bits),
			transform:self.transform.to_bits(),
		}
	}
	pub fn to_face_description(&self)->primitives::FaceDescription{
		primitives::FaceDescription{
			render:self.render,
			transform:self.transform.affine(),
			color:self.color,
		}
	}
}
pub type RobloxPartDescription=[Option<RobloxFaceTextureDescription>;6];
type RobloxWedgeDescription=[Option<RobloxFaceTextureDescription>;5];
type RobloxCornerWedgeDescription=[Option<RobloxFaceTextureDescription>;5];
#[derive(Clone,Eq,Hash,PartialEq)]
enum RobloxBasePartDescription{
	Sphere(RobloxPartDescription),
	Part(RobloxPartDescription),
	Cylinder(RobloxPartDescription),
	Wedge(RobloxWedgeDescription),
	CornerWedge(RobloxCornerWedgeDescription),
}
fn get_texture_description<'a>(
	temp_objects:&mut Vec<rbx_dom_weak::types::Ref>,
	render_config_deferred_loader:&mut RenderConfigDeferredLoader<&'a str>,
	dom:&'a rbx_dom_weak::WeakDom,
	object:&rbx_dom_weak::Instance,
	size:&rbx_dom_weak::types::Vector3,
)->RobloxPartDescription{
	//use the biggest one and cut it down later...
	let mut part_texture_description:RobloxPartDescription=[None,None,None,None,None,None];
	temp_objects.clear();
	recursive_collect_superclass(temp_objects,&dom,object,"Decal");
	for &mut decal_ref in temp_objects{
	if let Some(decal)=dom.get_by_ref(decal_ref){
		if let (
			Some(rbx_dom_weak::types::Variant::Content(content)),
			Some(rbx_dom_weak::types::Variant::Enum(normalid)),
			Some(rbx_dom_weak::types::Variant::Color3(decal_color3)),
			Some(rbx_dom_weak::types::Variant::Float32(decal_transparency)),
		) = (
			decal.properties.get("Texture"),
			decal.properties.get("Face"),
			decal.properties.get("Color3"),
			decal.properties.get("Transparency"),
		) {
			let render_id=render_config_deferred_loader.acquire_render_config_id(Some(content.as_ref()));
				let normal_id=normalid.to_u32();
				if normal_id<6{
					let (roblox_texture_color,roblox_texture_transform)=if decal.class=="Texture"{
						//generate tranform
						if let (
								Some(&rbx_dom_weak::types::Variant::Float32(offset_studs_u)),
								Some(&rbx_dom_weak::types::Variant::Float32(offset_studs_v)),
								Some(&rbx_dom_weak::types::Variant::Float32(studs_per_tile_u)),
								Some(&rbx_dom_weak::types::Variant::Float32(studs_per_tile_v)),
							) = (
								decal.properties.get("OffsetStudsU"),
								decal.properties.get("OffsetStudsV"),
								decal.properties.get("StudsPerTileU"),
								decal.properties.get("StudsPerTileV"),
							)
						{
							let (size_u,size_v)=match normal_id{
								0=>(size.z,size.y),//right
								1=>(size.x,size.z),//top
								2=>(size.x,size.y),//back
								3=>(size.z,size.y),//left
								4=>(size.x,size.z),//bottom
								5=>(size.x,size.y),//front
								_=>unreachable!(),
							};
							(
								glam::vec4(decal_color3.r,decal_color3.g,decal_color3.b,1.0-*decal_transparency),
								RobloxTextureTransform{
									offset_studs_u,
									offset_studs_v,
									studs_per_tile_u,
									studs_per_tile_v,
									size_u,
									size_v,
								}
							)
						}else{
							(glam::Vec4::ONE,RobloxTextureTransform::identity())
						}
					}else{
						(glam::Vec4::ONE,RobloxTextureTransform::identity())
					};
					part_texture_description[normal_id as usize]=Some(RobloxFaceTextureDescription{
						render:render_id,
						color:roblox_texture_color,
						transform:roblox_texture_transform,
					});
				}else{
					println!("NormalId={} is invalid",normal_id);
				}
			}
		}
	}
	part_texture_description
}
enum Shape{
	Primitive(primitives::Primitives),
	MeshPart,
	PhysicsData,
}
enum MeshAvailability{
	Immediate,
	DeferredMesh(RenderConfigId),
	DeferredUnion(RobloxPartDescription),
}
struct DeferredModelDeferredAttributes<'a>{
	render:RenderConfigId,
	model:ModelDeferredAttributes<'a>,
}
struct ModelDeferredAttributes<'a>{
	mesh:model::MeshId,
	deferred_attributes:GetAttributesArgs<'a>,
	color:model::Color4,//transparency is in here
	transform:Planar64Affine3,
}
struct DeferredUnionDeferredAttributes<'a>{
	render:RobloxPartDescription,
	model:ModelDeferredAttributes<'a>,
}
struct ModelOwnedAttributes{
	mesh:model::MeshId,
	attributes:attr::CollisionAttributes,
	color:model::Color4,//transparency is in here
	transform:Planar64Affine3,
}
struct GetAttributesArgs<'a>{
	name:&'a str,
	can_collide:bool,
	velocity:Planar64Vec3,
}
pub fn convert<'a>(
	dom:&'a rbx_dom_weak::WeakDom,
	render_config_deferred_loader:&mut RenderConfigDeferredLoader<&'a str>,
	mesh_deferred_loader:&mut MeshDeferredLoader<MeshIndex<'a>>,
)->PartialMap1<'a>{
	let mut deferred_models_deferred_attributes=Vec::new();
	let mut deferred_unions_deferred_attributes=Vec::new();
	let mut primitive_models_deferred_attributes=Vec::new();
	let mut primitive_meshes=Vec::new();
	let mut mesh_id_from_description=HashMap::new();

	//just going to leave it like this for now instead of reworking the data structures for this whole thing
	let textureless_render_group=render_config_deferred_loader.acquire_render_config_id(None);

	let mut object_refs=Vec::new();
	let mut temp_objects=Vec::new();
	recursive_collect_superclass(&mut object_refs, &dom, dom.root(),"BasePart");
	for object_ref in object_refs {
		if let Some(object)=dom.get_by_ref(object_ref){
			if let (
					Some(rbx_dom_weak::types::Variant::CFrame(cf)),
					Some(rbx_dom_weak::types::Variant::Vector3(size)),
					Some(rbx_dom_weak::types::Variant::Vector3(velocity)),
					Some(rbx_dom_weak::types::Variant::Float32(transparency)),
					Some(rbx_dom_weak::types::Variant::Color3uint8(color3)),
					Some(rbx_dom_weak::types::Variant::Bool(can_collide)),
				) = (
					object.properties.get("CFrame"),
					object.properties.get("Size"),
					object.properties.get("Velocity"),
					object.properties.get("Transparency"),
					object.properties.get("Color"),
					object.properties.get("CanCollide"),
				)
			{
				let model_transform=planar64_affine3_from_roblox(cf,size);

				if model_transform.matrix3.det().is_zero(){
					let mut parent_ref=object.parent();
					let mut full_path=object.name.clone();
					while let Some(parent)=dom.get_by_ref(parent_ref){
						full_path=format!("{}.{}",parent.name,full_path);
						parent_ref=parent.parent();
					}
					println!("Zero determinant CFrame at location {}",full_path);
					println!("matrix3:{}",model_transform.matrix3);
					continue;
				}

				//TODO: also detect "CylinderMesh" etc here
				let shape=match object.class.as_str(){
					"Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get("Shape"){
						Shape::Primitive(match shape.to_u32(){
							0=>primitives::Primitives::Sphere,
							1=>primitives::Primitives::Cube,
							2=>primitives::Primitives::Cylinder,
							3=>primitives::Primitives::Wedge,
							4=>primitives::Primitives::CornerWedge,
							other=>panic!("Funky roblox PartType={};",other),
						})
					}else{
						panic!("Part has no Shape!");
					},
					"TrussPart"=>Shape::Primitive(primitives::Primitives::Cube),
					"WedgePart"=>Shape::Primitive(primitives::Primitives::Wedge),
					"CornerWedgePart"=>Shape::Primitive(primitives::Primitives::CornerWedge),
					"MeshPart"=>Shape::MeshPart,
					"UnionOperation"=>Shape::PhysicsData,
					_=>{
						println!("Unsupported BasePart ClassName={}; defaulting to cube",object.class);
						Shape::Primitive(primitives::Primitives::Cube)
					}
				};

				let (availability,mesh_id)=match shape{
					Shape::Primitive(primitive_shape)=>{
				//TODO: TAB TAB
				let part_texture_description=get_texture_description(&mut temp_objects,render_config_deferred_loader,dom,object,size);
				//obscure rust syntax "slice pattern"
				let [
					f0,//Cube::Right
					f1,//Cube::Top
					f2,//Cube::Back
					f3,//Cube::Left
					f4,//Cube::Bottom
					f5,//Cube::Front
				]=part_texture_description;
				let basepart_description=match primitive_shape{
					primitives::Primitives::Sphere=>RobloxBasePartDescription::Sphere([f0,f1,f2,f3,f4,f5]),
					primitives::Primitives::Cube=>RobloxBasePartDescription::Part([f0,f1,f2,f3,f4,f5]),
					primitives::Primitives::Cylinder=>RobloxBasePartDescription::Cylinder([f0,f1,f2,f3,f4,f5]),
					//use front face texture first and use top face texture as a fallback
					primitives::Primitives::Wedge=>RobloxBasePartDescription::Wedge([
						f0,//Cube::Right->Wedge::Right
						f5.or(f1),//Cube::Front|Cube::Top->Wedge::TopFront
						f2,//Cube::Back->Wedge::Back
						f3,//Cube::Left->Wedge::Left
						f4,//Cube::Bottom->Wedge::Bottom
					]),
					//TODO: fix Left+Back texture coordinates to match roblox when not overwridden by Top
					primitives::Primitives::CornerWedge=>RobloxBasePartDescription::CornerWedge([
						f0,//Cube::Right->CornerWedge::Right
						f2.or(f1.clone()),//Cube::Back|Cube::Top->CornerWedge::TopBack
						f3.or(f1),//Cube::Left|Cube::Top->CornerWedge::TopLeft
						f4,//Cube::Bottom->CornerWedge::Bottom
						f5,//Cube::Front->CornerWedge::Front
					]),
				};
				//make new model if unit cube has not been created before
				let mesh_id=if let Some(&mesh_id)=mesh_id_from_description.get(&basepart_description){
					//push to existing texture model
					mesh_id
				}else{
					let mesh_id=model::MeshId::new(primitive_meshes.len() as u32);
					mesh_id_from_description.insert(basepart_description.clone(),mesh_id);//borrow checker going crazy
					let mesh=match basepart_description{
						RobloxBasePartDescription::Sphere(part_texture_description)
						|RobloxBasePartDescription::Cylinder(part_texture_description)
						|RobloxBasePartDescription::Part(part_texture_description)=>{
							let mut cube_face_description=primitives::CubeFaceDescription::default();
							for (face_id,roblox_face_description) in part_texture_description.iter().enumerate(){
								cube_face_description.insert(
								match face_id{
									0=>primitives::CubeFace::Right,
									1=>primitives::CubeFace::Top,
									2=>primitives::CubeFace::Back,
									3=>primitives::CubeFace::Left,
									4=>primitives::CubeFace::Bottom,
									5=>primitives::CubeFace::Front,
									_=>unreachable!(),
								},
								match roblox_face_description{
									Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
									None=>primitives::FaceDescription::new_with_render_id(textureless_render_group),
								});
							}
							primitives::generate_partial_unit_cube(cube_face_description)
						},
						RobloxBasePartDescription::Wedge(wedge_texture_description)=>{
							let mut wedge_face_description=primitives::WedgeFaceDescription::default();
							for (face_id,roblox_face_description) in wedge_texture_description.iter().enumerate(){
								wedge_face_description.insert(
								match face_id{
									0=>primitives::WedgeFace::Right,
									1=>primitives::WedgeFace::TopFront,
									2=>primitives::WedgeFace::Back,
									3=>primitives::WedgeFace::Left,
									4=>primitives::WedgeFace::Bottom,
									_=>unreachable!(),
								},
								match roblox_face_description{
									Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
									None=>primitives::FaceDescription::new_with_render_id(textureless_render_group),
								});
							}
							primitives::generate_partial_unit_wedge(wedge_face_description)
						},
						RobloxBasePartDescription::CornerWedge(cornerwedge_texture_description)=>{
							let mut cornerwedge_face_description=primitives::CornerWedgeFaceDescription::default();
							for (face_id,roblox_face_description) in cornerwedge_texture_description.iter().enumerate(){
								cornerwedge_face_description.insert(
								match face_id{
									0=>primitives::CornerWedgeFace::Right,
									1=>primitives::CornerWedgeFace::TopBack,
									2=>primitives::CornerWedgeFace::TopLeft,
									3=>primitives::CornerWedgeFace::Bottom,
									4=>primitives::CornerWedgeFace::Front,
									_=>unreachable!(),
								},
								match roblox_face_description{
									Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
									None=>primitives::FaceDescription::new_with_render_id(textureless_render_group),
								});
							}
							primitives::generate_partial_unit_cornerwedge(cornerwedge_face_description)
						},
					};
					primitive_meshes.push(mesh);
					mesh_id
				};
				(MeshAvailability::Immediate,mesh_id)
					},
					Shape::MeshPart=>if let (
						Some(rbx_dom_weak::types::Variant::Content(mesh_asset_id)),
						Some(rbx_dom_weak::types::Variant::Content(texture_asset_id)),
					)=(
						object.properties.get("MeshId"),
						object.properties.get("TextureID"),
					){
						(
							MeshAvailability::DeferredMesh(render_config_deferred_loader.acquire_render_config_id(Some(texture_asset_id.as_ref()))),
							mesh_deferred_loader.acquire_mesh_id(MeshIndex::file_mesh(mesh_asset_id.as_ref())),
						)
					}else{
						panic!("Mesh has no Mesh or Texture");
					},
					Shape::PhysicsData=>{
						let mut content="";
						let mut mesh_data:&[u8]=&[];
						let mut physics_data:&[u8]=&[];
						if let Some(rbx_dom_weak::types::Variant::Content(asset_id))=object.properties.get("AssetId"){
							content=asset_id.as_ref();
						}
						if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get("MeshData"){
							mesh_data=data.as_ref();
						}
						if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get("PhysicsData"){
							physics_data=data.as_ref();
						}
						let part_texture_description=get_texture_description(&mut temp_objects,render_config_deferred_loader,dom,object,size);
						let mesh_index=MeshIndex::union(content,mesh_data,physics_data,size,part_texture_description.clone());
						let mesh_id=mesh_deferred_loader.acquire_mesh_id(mesh_index);
						(MeshAvailability::DeferredUnion(part_texture_description),mesh_id)
					},
				};
				let model_deferred_attributes=ModelDeferredAttributes{
					mesh:mesh_id,
					transform:model_transform,
					color:glam::vec4(color3.r as f32/255f32, color3.g as f32/255f32, color3.b as f32/255f32, 1.0-*transparency),
					deferred_attributes:GetAttributesArgs{
						name:object.name.as_str(),
						can_collide:*can_collide,
						velocity:vec3::try_from_f32_array([velocity.x,velocity.y,velocity.z]).unwrap(),
					},
				};
				match availability{
					MeshAvailability::Immediate=>primitive_models_deferred_attributes.push(model_deferred_attributes),
					MeshAvailability::DeferredMesh(render)=>deferred_models_deferred_attributes.push(DeferredModelDeferredAttributes{
						render,
						model:model_deferred_attributes
					}),
					MeshAvailability::DeferredUnion(part_texture_description)=>deferred_unions_deferred_attributes.push(DeferredUnionDeferredAttributes{
						render:part_texture_description,
						model:model_deferred_attributes,
					}),
				}
			}
		}
	}
	PartialMap1{
		primitive_meshes,
		primitive_models_deferred_attributes,
		deferred_models_deferred_attributes,
		deferred_unions_deferred_attributes,
	}
}
struct MeshWithAabb{
	mesh:model::Mesh,
	aabb:Aabb,
}
fn acquire_mesh_id_from_render_config_id<'a>(
	primitive_meshes:&mut Vec<model::Mesh>,
	mesh_id_from_render_config_id:&mut HashMap<model::MeshId,HashMap<RenderConfigId,model::MeshId>>,
	loaded_meshes:&'a HashMap<model::MeshId,MeshWithAabb>,
	old_mesh_id:model::MeshId,
	render:RenderConfigId,
)->Option<(model::MeshId,&'a Aabb)>{
	//ignore meshes that fail to load completely for now
	loaded_meshes.get(&old_mesh_id).map(|mesh_with_aabb|(
		*mesh_id_from_render_config_id.entry(old_mesh_id).or_insert_with(||HashMap::new())
		.entry(render).or_insert_with(||{
			let mesh_id=model::MeshId::new(primitive_meshes.len() as u32);
			let mut mesh_clone=mesh_with_aabb.mesh.clone();
			//set the render group lool
			if let Some(graphics_group)=mesh_clone.graphics_groups.first_mut(){
				graphics_group.render=render;
			}
			primitive_meshes.push(mesh_clone);
			mesh_id
		}),
		&mesh_with_aabb.aabb,
	))
}
fn acquire_union_id_from_render_config_id<'a>(
	primitive_meshes:&mut Vec<model::Mesh>,
	union_id_from_render_config_id:&mut HashMap<model::MeshId,HashMap<RobloxPartDescription,model::MeshId>>,
	loaded_meshes:&'a HashMap<model::MeshId,MeshWithAabb>,
	old_union_id:model::MeshId,
	part_texture_description:RobloxPartDescription,
)->Option<(model::MeshId,&'a Aabb)>{
	//ignore uniones that fail to load completely for now
	loaded_meshes.get(&old_union_id).map(|union_with_aabb|(
		*union_id_from_render_config_id.entry(old_union_id).or_insert_with(||HashMap::new())
		.entry(part_texture_description.clone()).or_insert_with(||{
			let union_id=model::MeshId::new(primitive_meshes.len() as u32);
			let mut union_clone=union_with_aabb.mesh.clone();
			//set the render groups
			for (graphics_group,maybe_face_texture_description) in union_clone.graphics_groups.iter_mut().zip(part_texture_description){
				if let Some(face_texture_description)=maybe_face_texture_description{
					graphics_group.render=face_texture_description.render;
				}
			}
			primitive_meshes.push(union_clone);
			union_id
		}),
		&union_with_aabb.aabb,
	))
}
pub struct PartialMap1<'a>{
	primitive_meshes:Vec<model::Mesh>,
	primitive_models_deferred_attributes:Vec<ModelDeferredAttributes<'a>>,
	deferred_models_deferred_attributes:Vec<DeferredModelDeferredAttributes<'a>>,
	deferred_unions_deferred_attributes:Vec<DeferredUnionDeferredAttributes<'a>>,
}
impl PartialMap1<'_>{
	pub fn add_meshpart_meshes_and_calculate_attributes(
		mut self,
		meshpart_meshes:Meshes,
	)->PartialMap2{
		//calculate attributes
		let mut modes_builder=ModesBuilder::default();
		let mut unique_attributes=Vec::new();
		let mut attributes_id_from_attributes=HashMap::new();

		let mut wormhole_in_model_to_id=HashMap::new();
		let mut wormhole_id_to_out_model=HashMap::new();

		//decode roblox meshes
		//generate mesh_id_map based on meshes that failed to load
		let loaded_meshes:HashMap<model::MeshId,MeshWithAabb>=
		meshpart_meshes.consume().map(|(old_mesh_id,mesh)|{
			let mut aabb=strafesnet_common::aabb::Aabb::default();
			for &pos in &mesh.unique_pos{
				aabb.grow(pos);
			}
			(old_mesh_id,MeshWithAabb{
				mesh,
				aabb,
			})
		}).collect();

		// SAFETY: I have no idea what I'm doing and this is definitely unsound in some subtle way
		// I just want to chain iterators together man
		let aint_no_way=core::cell::UnsafeCell::new(&mut self.primitive_meshes);

		let mut mesh_id_from_render_config_id=HashMap::new();
		let mut union_id_from_render_config_id=HashMap::new();
		//now that the meshes are loaded, these models can be generated
		let models_owned_attributes:Vec<ModelOwnedAttributes>=
		self.deferred_models_deferred_attributes.into_iter().flat_map(|deferred_model_deferred_attributes|{
			//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
			//insert into primitive_meshes
			let (mesh,aabb)=acquire_mesh_id_from_render_config_id(
				unsafe{*aint_no_way.get()},
				&mut mesh_id_from_render_config_id,
				&loaded_meshes,
				deferred_model_deferred_attributes.model.mesh,
				deferred_model_deferred_attributes.render
			)?;
			let size=aabb.size();
			Some(ModelDeferredAttributes{
				mesh,
				deferred_attributes:deferred_model_deferred_attributes.model.deferred_attributes,
				color:deferred_model_deferred_attributes.model.color,
				transform:Planar64Affine3::new(
					Planar64Mat3::from_cols([
						(deferred_model_deferred_attributes.model.transform.matrix3.x_axis*2/size.x).divide().fix_1(),
						(deferred_model_deferred_attributes.model.transform.matrix3.y_axis*2/size.y).divide().fix_1(),
						(deferred_model_deferred_attributes.model.transform.matrix3.z_axis*2/size.z).divide().fix_1()
					]),
					deferred_model_deferred_attributes.model.transform.translation
				),
			})
		}).chain(self.deferred_unions_deferred_attributes.into_iter().flat_map(|deferred_union_deferred_attributes|{
			//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
			//insert into primitive_meshes
			let (mesh,aabb)=acquire_union_id_from_render_config_id(
				unsafe{*aint_no_way.get()},
				&mut union_id_from_render_config_id,
				&loaded_meshes,
				deferred_union_deferred_attributes.model.mesh,
				deferred_union_deferred_attributes.render
			)?;
			let size=aabb.size();
			Some(ModelDeferredAttributes{
				mesh,
				deferred_attributes:deferred_union_deferred_attributes.model.deferred_attributes,
				color:deferred_union_deferred_attributes.model.color,
				transform:Planar64Affine3::new(
					Planar64Mat3::from_cols([
						(deferred_union_deferred_attributes.model.transform.matrix3.x_axis*2/size.x).divide().fix_1(),
						(deferred_union_deferred_attributes.model.transform.matrix3.y_axis*2/size.y).divide().fix_1(),
						(deferred_union_deferred_attributes.model.transform.matrix3.z_axis*2/size.z).divide().fix_1()
					]),
					deferred_union_deferred_attributes.model.transform.translation
				),
			})
		}))
		.chain(self.primitive_models_deferred_attributes.into_iter())
		.enumerate().map(|(model_id,model_deferred_attributes)|{
			let model_id=model::ModelId::new(model_id as u32);
			ModelOwnedAttributes{
				mesh:model_deferred_attributes.mesh,
				attributes:get_attributes(
					&model_deferred_attributes.deferred_attributes.name,
					model_deferred_attributes.deferred_attributes.can_collide,
					model_deferred_attributes.deferred_attributes.velocity,
					model_id,
					&mut modes_builder,
					&mut wormhole_in_model_to_id,
					&mut wormhole_id_to_out_model,
				),
				color:model_deferred_attributes.color,
				transform:model_deferred_attributes.transform,
			}
		}).collect();
		let models=models_owned_attributes.into_iter().enumerate().map(|(model_id,mut model_owned_attributes)|{
		//TODO: TAB
		let model_id=model::ModelId::new(model_id as u32);
		//update attributes with wormhole id
		//TODO: errors/prints
		if let Some(wormhole_id)=wormhole_in_model_to_id.get(&model_id){
			if let Some(&wormhole_out_model_id)=wormhole_id_to_out_model.get(wormhole_id){
				match &mut model_owned_attributes.attributes{
					attr::CollisionAttributes::Contact(attr::ContactAttributes{contacting:_,general})
					|attr::CollisionAttributes::Intersect(attr::IntersectAttributes{intersecting:_,general})
					=>general.wormhole=Some(attr::Wormhole{destination_model:wormhole_out_model_id}),
					attr::CollisionAttributes::Decoration=>println!("Not a wormhole"),
				}
			}
		}

		//index the attributes
		let attributes_id=if let Some(&attributes_id)=attributes_id_from_attributes.get(&model_owned_attributes.attributes){
			attributes_id
		}else{
			let attributes_id=attr::CollisionAttributesId::new(unique_attributes.len() as u32);
			attributes_id_from_attributes.insert(model_owned_attributes.attributes.clone(),attributes_id);
			unique_attributes.push(model_owned_attributes.attributes);
			attributes_id
		};
		model::Model{
			mesh:model_owned_attributes.mesh,
			transform:model_owned_attributes.transform,
			color:model_owned_attributes.color,
			attributes:attributes_id,
		}
	}).collect();
	PartialMap2{
		meshes:self.primitive_meshes,
		models,
		modes:modes_builder.build_normalized(),
		attributes:unique_attributes,
	}
	}
}

pub struct PartialMap2{
	meshes:Vec<model::Mesh>,
	models:Vec<model::Model>,
	modes:NormalizedModes,
	attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
}
impl PartialMap2{
	pub fn add_render_configs_and_textures(
		self,
		render_configs:RenderConfigs,
	)->map::CompleteMap{
		let (textures,render_configs)=render_configs.consume();
		let (textures,texture_id_map):(Vec<Vec<u8>>,HashMap<model::TextureId,model::TextureId>)
		=textures.into_iter().enumerate().map(|(new_texture_id,(old_texture_id,Texture::ImageDDS(texture)))|{
			(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
		}).unzip();
		let render_configs=render_configs.into_iter().map(|(_render_config_id,mut render_config)|{
			// This may generate duplicate no-texture render configs but idc
			//
			// This is because some textures may not exist, so the render config
			// that it points to is unique but is texture.
			//
			// I don't think this needs to be fixed because missing textures
			// should be a conversion error anyways.
			render_config.texture=render_config.texture.and_then(|texture_id|
				texture_id_map.get(&texture_id).copied()
			);
			render_config
		}).collect();
		map::CompleteMap{
			modes:self.modes,
			attributes:self.attributes,
			meshes:self.meshes,
			models:self.models,
			//the roblox legacy texture thing always works
			textures,
			render_configs,
		}
	}
}
