use std::borrow::Cow;

use vbsp_entities_css::Entity;

use strafesnet_common::{map,model,integer,gameplay_attributes as attr};
use strafesnet_deferred_loader::deferred_loader::{MeshDeferredLoader,RenderConfigDeferredLoader};
use strafesnet_deferred_loader::mesh::Meshes;
use strafesnet_deferred_loader::texture::{RenderConfigs,Texture};
use strafesnet_common::gameplay_modes::{NormalizedMode,NormalizedModes,Mode,Stage};

use crate::valve_transform;

fn ingest_vertex(
	mb:&mut model::MeshBuilder,
	world_position:vbsp::Vector,
	texture_transform_u:glam::Vec4,
	texture_transform_v:glam::Vec4,
	normal:model::NormalId,
	color:model::ColorId,
)->model::VertexId{
	//world_model.origin seems to always be 0,0,0
	let vertex_xyz=world_position.into();
	let pos=mb.acquire_pos_id(valve_transform(vertex_xyz));

	//calculate texture coordinates
	let pos_4d=glam::Vec3::from_array(vertex_xyz).extend(1.0);
	let tex=glam::vec2(texture_transform_u.dot(pos_4d),texture_transform_v.dot(pos_4d));
	let tex=mb.acquire_tex_id(tex);

	mb.acquire_vertex_id(model::IndexedVertex{
		pos,
		tex,
		normal,
		color,
	})
}

fn add_brush<'a>(
	mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>,
	world_models:&mut Vec<model::Model>,
	prop_models:&mut Vec<model::Model>,
	model:&'a str,
	origin:vbsp::Vector,
	rendercolor:vbsp::Color,
	attributes:attr::CollisionAttributesId,
){
	let transform=integer::Planar64Affine3::from_translation(
		valve_transform(origin.into())
	);
	let color=(glam::Vec3::from_array([
		rendercolor.r as f32,
		rendercolor.g as f32,
		rendercolor.b as f32
	])/255.0).extend(1.0);

	match model.chars().next(){
		// The first character of brush.model is '*'
		Some('*')=>match model[1..].parse(){
			Ok(mesh_id)=>{
				let mesh=model::MeshId::new(mesh_id);
				world_models.push(
					model::Model{mesh,attributes,transform,color}
				);
			},
			Err(e)=>{
				println!("Brush model int parse error: {e} model={model}");
				return;
			},
		},
		_=>{
			let mesh=mesh_deferred_loader.acquire_mesh_id(model);
			prop_models.push(
				model::Model{mesh,attributes,transform,color}
			);
		}
	}
}

pub fn convert<'a>(
	bsp:&'a crate::Bsp,
	render_config_deferred_loader:&mut RenderConfigDeferredLoader<Cow<'a,str>>,
	mesh_deferred_loader:&mut MeshDeferredLoader<&'a str>,
)->PartialMap1{
	let bsp=bsp.as_ref();
	//figure out real attributes later
	let unique_attributes=vec![
		attr::CollisionAttributes::Decoration,
		attr::CollisionAttributes::contact_default(),
		attr::CollisionAttributes::intersect_default(),
		// ladder
		attr::CollisionAttributes::Contact(
			attr::ContactAttributes{
				contacting:attr::ContactingAttributes{
					contact_behaviour:Some(attr::ContactingBehaviour::Ladder(
						attr::ContactingLadder{sticky:true}
					))
				},
				general:attr::GeneralAttributes::default(),
			}
		),
		// water
		attr::CollisionAttributes::Intersect(
			attr::IntersectAttributes{
				intersecting:attr::IntersectingAttributes{
					water:Some(attr::IntersectingWater{
						viscosity:integer::Planar64::ONE,
						density:integer::Planar64::ONE,
						velocity:integer::vec3::zero(),
					}),
				},
				general:attr::GeneralAttributes::default(),
			}
		),
	];
	const ATTRIBUTE_DECORATION:attr::CollisionAttributesId=attr::CollisionAttributesId::new(0);
	const ATTRIBUTE_CONTACT_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(1);
	const ATTRIBUTE_INTERSECT_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(2);
	const ATTRIBUTE_LADDER_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(3);
	const ATTRIBUTE_WATER_DEFAULT:attr::CollisionAttributesId=attr::CollisionAttributesId::new(4);

	//declare all prop models to Loader
	let mut prop_models=bsp.static_props().map(|prop|{
		const DEG_TO_RAD:f32=std::f32::consts::TAU/360.0;
		//get or create mesh_id
		let mesh_id=mesh_deferred_loader.acquire_mesh_id(prop.model());
		model::Model{
			mesh:mesh_id,
			attributes:ATTRIBUTE_DECORATION,
			transform:integer::Planar64Affine3::new(
				integer::mat3::try_from_f32_array_2d(
					//TODO: figure this out
					glam::Mat3A::from_euler(
						glam::EulerRot::XYZ,
						prop.angles.pitch*DEG_TO_RAD,
						prop.angles.yaw*DEG_TO_RAD,
						prop.angles.roll*DEG_TO_RAD
					).to_cols_array_2d()
				).unwrap(),
				valve_transform(prop.origin.into()),
			),
			color:glam::Vec4::ONE,
		}
	}).collect();

	//TODO: make the main map one single mesh with a bunch of different physics groups and graphics groups

	//the generated MeshIds in here will collide with the Loader Mesh Ids
	//but I can't think of a good workaround other than just remapping one later.
	let mut world_meshes:Vec<model::Mesh>=bsp.models().map(|world_model|{
		let mut mb=model::MeshBuilder::new();

		let color=mb.acquire_color_id(glam::Vec4::ONE);
		let mut graphics_groups=Vec::new();
		let mut render_id_to_graphics_group_id=std::collections::HashMap::new();
		let polygon_groups=world_model.faces().enumerate().map(|(polygon_group_id,face)|{
			let polygon_group_id=model::PolygonGroupId::new(polygon_group_id as u32);
			let face_texture=face.texture();
			let face_texture_data=face_texture.texture_data();
			//this would be better as a 4x2 matrix
			let texture_transform_u=glam::Vec4::from_array(face_texture.texture_transforms_u)/(face_texture_data.width as f32);
			let texture_transform_v=glam::Vec4::from_array(face_texture.texture_transforms_v)/(face_texture_data.height as f32);

			//normal
			let normal=mb.acquire_normal_id(valve_transform(face.normal().into()));
			let mut polygon_iter=face.vertex_positions().map(|vertex_position|
				world_model.origin+vertex_position
			);
			let polygon_list=std::iter::from_fn(move||{
				match (polygon_iter.next(),polygon_iter.next(),polygon_iter.next()){
					(Some(v1),Some(v2),Some(v3))=>Some([v1,v2,v3]),
					//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
					_=>None,
				}
			}).map(|triplet|{
				triplet.map(|world_position|
					ingest_vertex(&mut mb,world_position,texture_transform_u,texture_transform_v,normal,color)
				).to_vec()
			}).collect();
			if face.is_visible(){
				//this automatically figures out what the texture is trying to do and creates
				//a render config for it, and then returns the id to that render config
				let render_id=render_config_deferred_loader.acquire_render_config_id(Some(Cow::Borrowed(face_texture_data.name())));
				//deduplicate graphics groups by render id
				let graphics_group_id=*render_id_to_graphics_group_id.entry(render_id).or_insert_with(||{
					let graphics_group_id=graphics_groups.len();
					graphics_groups.push(model::IndexedGraphicsGroup{
						render:render_id,
						groups:vec![],
					});
					graphics_group_id
				});
				graphics_groups[graphics_group_id].groups.push(polygon_group_id);
			}
			model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))
		}).collect();

		mb.build(polygon_groups,graphics_groups,vec![])
	}).collect();

	let mut found_spawn=None;

	let mut world_models=Vec::new();

	// the one and only world model 0
	world_models.push(model::Model{
		mesh:model::MeshId::new(0),
		attributes:ATTRIBUTE_DECORATION,
		transform:integer::Planar64Affine3::IDENTITY,
		color:glam::Vec4::W,
	});

	const WHITE:vbsp::Color=vbsp::Color{r:255,g:255,b:255};
	for raw_ent in &bsp.entities{
		match raw_ent.parse(){
			Ok(Entity::Cycler(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::EnvSprite(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncBreakable(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncBrush(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncButton(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncDoor(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncDoorRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncIllusionary(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncMonitor(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncMovelinear(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncPhysbox(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncPhysboxMultiplayer(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncRotButton(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncTracktrain(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncTrain(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncWall(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncWallToggle(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin.unwrap_or_default(),brush.rendercolor,ATTRIBUTE_DECORATION),
			Ok(Entity::FuncWaterAnalog(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,brush.rendercolor.unwrap_or(WHITE),ATTRIBUTE_DECORATION),
			Ok(Entity::PropDoorRotating(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::PropDynamic(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::PropDynamicOverride(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::PropPhysics(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::PropPhysicsMultiplayer(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::PropPhysicsOverride(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::PropRagdoll(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::TriggerGravity(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::TriggerHurt(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::TriggerLook(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::TriggerMultiple(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::TriggerOnce(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::TriggerProximity(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::TriggerPush(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::TriggerSoundscape(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::TriggerTeleport(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model.unwrap_or_default(),brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::TriggerVphysicsMotion(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::TriggerWind(brush))=>add_brush(mesh_deferred_loader,&mut world_models,&mut prop_models,brush.model,brush.origin,WHITE,ATTRIBUTE_DECORATION),
			Ok(Entity::InfoPlayerCounterterrorist(spawn))=>{
				found_spawn=Some(valve_transform(spawn.origin.into()));
			},
			Ok(Entity::InfoPlayerTerrorist(spawn))=>{
				found_spawn=Some(valve_transform(spawn.origin.into()));
			},
			Err(e)=>{
				println!("Bsp Entity parse error: {e}");
			},
			_=>(),
		}
	}

	// physics models
	for brush in &bsp.brushes{
		const RELEVANT:vbsp::BrushFlags=
			vbsp::BrushFlags::SOLID
			.union(vbsp::BrushFlags::PLAYERCLIP)
			.union(vbsp::BrushFlags::WATER)
			.union(vbsp::BrushFlags::MOVEABLE)
			.union(vbsp::BrushFlags::LADDER);
		if !brush.flags.intersects(RELEVANT){
			continue;
		}
		let is_ladder=brush.flags.contains(vbsp::BrushFlags::LADDER);
		let is_water=brush.flags.contains(vbsp::BrushFlags::WATER);
		let attributes=match (is_ladder,is_water){
			(true,false)=>ATTRIBUTE_LADDER_DEFAULT,
			(false,true)=>ATTRIBUTE_WATER_DEFAULT,
			(false,false)=>ATTRIBUTE_CONTACT_DEFAULT,
			(true,true)=>{
				// water ladder? wtf
				println!("brush is a water ladder o_o defaulting to ladder");
				ATTRIBUTE_LADDER_DEFAULT
			}
		};
		let mesh_result=crate::brush::brush_to_mesh(bsp,brush);
		match mesh_result{
			Ok(mesh)=>{
				let mesh_id=model::MeshId::new(world_meshes.len() as u32);
				world_meshes.push(mesh);
				world_models.push(model::Model{
					mesh:mesh_id,
					attributes,
					transform:integer::Planar64Affine3::new(
						integer::mat3::identity(),
						integer::vec3::zero(),
					),
					color:glam::Vec4::ONE,
				});
			},
			Err(e)=>println!("Brush mesh error: {e}"),
		}
	}

	let mut modes_list=Vec::new();
	if let Some(spawn_point)=found_spawn{
		// create a new mesh
		let mesh_id=model::MeshId::new(world_meshes.len() as u32);
		world_meshes.push(crate::brush::unit_cube());
		// create a new model
		let model_id=model::ModelId::new(world_models.len() as u32);
		world_models.push(model::Model{
			mesh:mesh_id,
			attributes:ATTRIBUTE_INTERSECT_DEFAULT,
			transform:integer::Planar64Affine3::from_translation(spawn_point),
			color:glam::Vec4::W,
		});

		let first_stage=Stage::empty(model_id);
		let main_mode=Mode::new(
			strafesnet_common::gameplay_style::StyleModifiers::source_bhop(),
			model_id,
			std::collections::HashMap::new(),
			vec![first_stage],
			std::collections::HashMap::new(),
		);
		modes_list.push(NormalizedMode::new(main_mode));
	}

	PartialMap1{
		attributes:unique_attributes,
		world_meshes,
		prop_models,
		world_models,
		modes:NormalizedModes::new(modes_list),
	}
}

//partially constructed map types
pub struct PartialMap1{
	attributes:Vec<attr::CollisionAttributes>,
	prop_models:Vec<model::Model>,
	world_meshes:Vec<model::Mesh>,
	world_models:Vec<model::Model>,
	modes:NormalizedModes,
}
impl PartialMap1{
	pub fn add_prop_meshes(
		self,
		prop_meshes:Meshes<model::Mesh>,
	)->PartialMap2{
		PartialMap2{
			attributes:self.attributes,
			prop_meshes:prop_meshes.consume().collect(),
			prop_models:self.prop_models,
			world_meshes:self.world_meshes,
			world_models:self.world_models,
			modes:self.modes,
		}
	}
}
pub struct PartialMap2{
	attributes:Vec<attr::CollisionAttributes>,
	prop_meshes:Vec<(model::MeshId,model::Mesh)>,
	prop_models:Vec<model::Model>,
	world_meshes:Vec<model::Mesh>,
	world_models:Vec<model::Model>,
	modes:NormalizedModes,
}
impl PartialMap2{
	pub fn add_render_configs_and_textures(
		mut self,
		render_configs:RenderConfigs,
	)->map::CompleteMap{
		//merge mesh and model lists, flatten and remap all ids
		let mesh_id_offset=self.world_meshes.len();
		println!("prop_meshes.len()={}",self.prop_meshes.len());
		let (mut prop_meshes,prop_mesh_id_map):(Vec<model::Mesh>,std::collections::HashMap<model::MeshId,model::MeshId>)
		=self.prop_meshes.into_iter().enumerate().map(|(new_mesh_id,(old_mesh_id,mesh))|{
			(mesh,(old_mesh_id,model::MeshId::new((mesh_id_offset+new_mesh_id) as u32)))
		}).unzip();
		self.world_meshes.append(&mut prop_meshes);
		//there is no modes or runtime behaviour with references to the model ids currently
		//so just relentlessly cull them if the mesh is missing
		self.world_models.extend(self.prop_models.into_iter().filter_map(|mut model|
			prop_mesh_id_map.get(&model.mesh).map(|&new_mesh_id|{
				model.mesh=new_mesh_id;
				model
			})
		));
		//let mut models=Vec::new();
		let (textures,render_configs)=render_configs.consume();
		let (textures,texture_id_map):(Vec<Vec<u8>>,std::collections::HashMap<model::TextureId,model::TextureId>)
		=textures.into_iter()
		//.filter_map(f) cull unused textures
		.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
			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.world_meshes,
			models:self.world_models,
			textures,
			render_configs,
		}
	}
}
