use strafesnet_physics::physics::{InternalInstruction,PhysicsData,PhysicsState,PhysicsContext};
use strafesnet_common::gameplay_modes::NormalizedModes;
use strafesnet_common::gameplay_attributes::{CollisionAttributes,CollisionAttributesId};
use strafesnet_common::integer::{vec3,mat3,Planar64Affine3,Time};
use strafesnet_common::model::{Mesh,Model,MeshId,ModelId,RenderConfigId};
use strafesnet_common::map::CompleteMap;
use strafesnet_rbx_loader::primitives::{unit_cube,CubeFaceDescription};

struct TestSceneBuilder{
	meshes:Vec<Mesh>,
	models:Vec<Model>,
}
impl TestSceneBuilder{
	fn new()->Self{
		Self{
			meshes:Vec::new(),
			models:Vec::new(),
		}
	}
	fn push_mesh(&mut self,mesh:Mesh)->MeshId{
		let mesh_id=self.meshes.len();
		self.meshes.push(mesh);
		MeshId::new(mesh_id as u32)
	}
	fn push_mesh_instance(&mut self,mesh:MeshId,transform:Planar64Affine3)->ModelId{
		let model=Model{
			mesh,
			attributes:CollisionAttributesId::new(0),
			color:glam::Vec4::ONE,
			transform,
		};
		let model_id=self.models.len();
		self.models.push(model);
		ModelId::new(model_id as u32)
	}
	fn build(self)->PhysicsData{
		let modes=NormalizedModes::new(Vec::new());
		let attributes=vec![CollisionAttributes::contact_default()];
		let meshes=self.meshes;
		let models=self.models;
		let textures=Vec::new();
		let render_configs=Vec::new();
		PhysicsData::new(&CompleteMap{
			modes,
			attributes,
			meshes,
			models,
			textures,
			render_configs,
		})
	}
}

fn test_scene()->PhysicsData{
	let mut builder=TestSceneBuilder::new();
	let cube_face_description=CubeFaceDescription::new(Default::default(),RenderConfigId::new(0));
	let mesh=builder.push_mesh(unit_cube(cube_face_description));
	// place two 5x5x5 cubes.
	builder.push_mesh_instance(mesh,Planar64Affine3::new(
		mat3::from_diagonal(vec3::int(5,5,5)>>1),
		vec3::int(0,0,0)
	));
	builder.push_mesh_instance(mesh,Planar64Affine3::new(
		mat3::from_diagonal(vec3::int(5,5,5)>>1),
		vec3::int(5,-5,0)
	));
	builder.build()
}

#[test]
fn simultaneous_collision(){
	let physics_data=test_scene();
	let body=strafesnet_physics::physics::Body::new(
		(vec3::int(5+2,0,0)>>1)+vec3::int(1,1,0),
		vec3::int(-1,-1,0),
		vec3::int(0,0,0),
		Time::ZERO,
	);
	let mut physics=PhysicsState::new_with_body(body);
	physics.style_mut().gravity=vec3::zero();
	let mut phys_iter=PhysicsContext::iter_internal(&mut physics,&physics_data,Time::from_secs(2))
		.filter(|ins|!matches!(ins.instruction,InternalInstruction::StrafeTick));
	// the order that they hit does matter, but we aren't currently worrying about that.
	// See multi-collision branch
	assert_eq!(phys_iter.next().unwrap().time,Time::from_secs(1));
	assert_eq!(phys_iter.next().unwrap().time,Time::from_secs(1));
	assert!(phys_iter.next().is_none());
	let body=physics.body();
	assert_eq!(body.position,vec3::int(5,0,0));
	assert_eq!(body.velocity,vec3::int(0,0,0));
	assert_eq!(body.acceleration,vec3::int(0,0,0));
	assert_eq!(body.time,Time::from_secs(1));
}
#[test]
fn bug_3(){
	let physics_data=test_scene();
	let body=strafesnet_physics::physics::Body::new(
		(vec3::int(5+2,0,0)>>1)+vec3::int(1,2,0),
		vec3::int(-1,-1,0),
		vec3::int(0,0,0),
		Time::ZERO,
	);
	let mut physics=PhysicsState::new_with_body(body);
	physics.style_mut().gravity=vec3::zero();
	let mut phys_iter=PhysicsContext::iter_internal(&mut physics,&physics_data,Time::from_secs(3))
		.filter(|ins|!matches!(ins.instruction,InternalInstruction::StrafeTick));
	// touch side of part at 0,0,0
	assert_eq!(phys_iter.next().unwrap().time,Time::from_secs(1));
	// touch top of part at 5,-5,0
	assert_eq!(phys_iter.next().unwrap().time,Time::from_secs(2));
	assert!(phys_iter.next().is_none());
	let body=physics.body();
	assert_eq!(body.position,vec3::int(5+2,0,0)>>1);
	assert_eq!(body.velocity,vec3::int(0,0,0));
	assert_eq!(body.acceleration,vec3::int(0,0,0));
	assert_eq!(body.time,Time::from_secs(2));
}
