const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16

use crate::integer::{Time,Ratio64,Planar64,Planar64Vec3};
use crate::controls_bitflag::Controls;

#[derive(Clone,Debug)]
pub enum Continuity{
	FirstDegree(FlySettings),
	SecondDegree{
		//strafing
		strafe:Option<StrafeSettings>,
		//player gets a controllable rocket force
		rocket:Option<PropulsionSettings>,
		//flying
		//jumping is allowed
		jump:Option<JumpSettings>,
		//standing & walking is allowed
		walk:Option<WalkSettings>,
		//laddering is allowed
		ladder:Option<LadderSettings>,
		//water propulsion
		swim:Option<PropulsionSettings>,
		//maximum slope before sloped surfaces become frictionless
		gravity:Planar64Vec3,
		//unused
		mass:Planar64,
	}
}

#[derive(Clone,Debug)]
pub struct StyleModifiers{
	//controls which are allowed to pass into gameplay (usually all)
	pub controls_mask:Controls,
	//controls which are masked from control state (e.g. !jump in scroll style)
	pub controls_mask_state:Controls,
	pub continuity:Continuity,
	//hitbox
	pub hitbox:Hitbox,
	//camera location relative to the center (0,0,0) of the hitbox
	pub camera_offset:Planar64Vec3,
}
impl std::default::Default for StyleModifiers{
	fn default()->Self{
		Self::roblox_bhop()
	}
}

#[derive(Clone,Debug)]
pub struct FlySettings{
	speed:Planar64,
	clip:bool,//noclip
}

#[derive(Clone,Debug)]
pub enum JumpCalculation{
	Capped,//roblox
	Energy,//new
	Linear,//source
}

#[derive(Clone,Debug)]
pub enum JumpImpulse{
	FromTime(Time),//jump time is invariant across mass and gravity changes
	FromHeight(Planar64),//jump height is invariant across mass and gravity changes
	FromDeltaV(Planar64),//jump velocity is invariant across mass and gravity changes
	FromEnergy(Planar64),// :)
}
//Jumping acts on dot(walks_state.normal,body.velocity)
//Capped means it increases the dot to the cap
//Energy means it adds energy
//Linear means it linearly adds on
impl JumpImpulse{
	//fn get_jump_time(&self)->Planar64
	//fn get_jump_height(&self)->Planar64
	//fn get_jump_energy(&self)->Planar64
	pub fn get_jump_deltav(&self,gravity:&Planar64Vec3,mass:Planar64)->Planar64{
		//gravity.length() is actually the proper calculation because the jump is always opposite the gravity direction
		match self{
			&JumpImpulse::FromTime(time)=>gravity.length()*(time/2),
			&JumpImpulse::FromHeight(height)=>(gravity.length()*height*2).sqrt(),
			&JumpImpulse::FromDeltaV(deltav)=>deltav,
			&JumpImpulse::FromEnergy(energy)=>(energy*2/mass).sqrt(),
		}
	}
}

#[derive(Clone,Debug)]
pub struct ControlsActivation{
	//allowed keys
	controls_mask:Controls,
	//allow strafing only if any of the masked controls are held, eg W|S for shsw
	controls_intersects:Controls,
	//allow strafing only if all of the masked controls are held, eg W for hsw, w-only
	controls_contains:Controls,
	//Function(Box<dyn Fn(u32)->bool>),
}
impl ControlsActivation{
	pub const fn new(
		controls_mask:Controls,
		controls_intersects:Controls,
		controls_contains:Controls,
	)->Self{
		Self{
			controls_mask,
			controls_intersects,
			controls_contains,
		}
	}
	pub const fn mask(&self,controls:Controls)->Controls{
		controls.intersection(self.controls_mask)
	}
	pub const fn activates(&self,controls:Controls)->bool{
		(self.controls_intersects.is_empty()||controls.intersects(self.controls_intersects))
		&&controls.contains(self.controls_contains)
	}
	pub const fn full_3d()->Self{
		Self{
			controls_mask:Controls::wasdqe(),
			controls_intersects:Controls::wasdqe(),
			controls_contains:Controls::empty(),
		}
	}
	//classical styles
	//Normal
	pub const fn full_2d()->Self{
		Self{
			controls_mask:Controls::wasd(),
			controls_intersects:Controls::wasd(),
			controls_contains:Controls::empty(),
		}
	}
	//Sideways
	pub const fn sideways()->Self{
		Self{
			controls_mask:Controls::MoveForward.union(Controls::MoveBackward),
			controls_intersects:Controls::MoveForward.union(Controls::MoveBackward),
			controls_contains:Controls::empty(),
		}
	}
	//Half-Sideways
	pub const fn half_sideways()->Self{
		Self{
			controls_mask:Controls::MoveForward.union(Controls::MoveLeft).union(Controls::MoveRight),
			controls_intersects:Controls::MoveLeft.union(Controls::MoveRight),
			controls_contains:Controls::MoveForward,
		}
	}
	//Surf Half-Sideways
	pub const fn surf_half_sideways()->Self{
		Self{
			controls_mask:Controls::MoveForward.union(Controls::MoveBackward).union(Controls::MoveLeft).union(Controls::MoveRight),
			controls_intersects:Controls::MoveForward.union(Controls::MoveBackward),
			controls_contains:Controls::empty(),
		}
	}
	//W-Only
	pub const fn w_only()->Self{
		Self{
			controls_mask:Controls::MoveForward,
			controls_intersects:Controls::empty(),
			controls_contains:Controls::MoveForward,
		}
	}
	//A-Only
	pub const fn a_only()->Self{
		Self{
			controls_mask:Controls::MoveLeft,
			controls_intersects:Controls::empty(),
			controls_contains:Controls::MoveLeft,
		}
	}
	//Backwards
}

#[derive(Clone,Debug)]
pub struct StrafeSettings{
	enable:ControlsActivation,
	mv:Planar64,
	air_accel_limit:Option<Planar64>,
	tick_rate:Ratio64,
}
impl StrafeSettings{
	pub fn tick_velocity(&self,velocity:Planar64Vec3,control_dir:Planar64Vec3)->Option<Planar64Vec3>{
		let d=velocity.dot(control_dir);
		match d<self.mv{
			true=>Some(velocity+control_dir*self.air_accel_limit.map_or(self.mv-d,|limit|limit.min(self.mv-d))),
			false=>None,
		}
	}
	pub fn next_tick(&self,time:Time)->Time{
		Time::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1))
	}
	pub const fn activates(&self,controls:Controls)->bool{
		self.enable.activates(controls)
	}
	pub const fn mask(&self,controls:Controls)->Controls{
		self.enable.mask(controls)
	}
}

#[derive(Clone,Debug)]
pub struct PropulsionSettings{
	magnitude:Planar64,
}
impl PropulsionSettings{
	pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{
		control_dir*self.magnitude
	}
}

#[derive(Clone,Debug)]
pub struct JumpSettings{
	//information used to calculate jump power
	impulse:JumpImpulse,
	//information used to calculate jump behaviour
	calculation:JumpCalculation,
}
impl JumpSettings{
	pub fn jumped_velocity(&self,style:&StyleModifiers,jump_dir:Planar64Vec3,velocity:Planar64Vec3)->Planar64Vec3{
		match self.calculation{
			//roblox style
			JumpCalculation::Capped=>todo!(),
			//something different
			JumpCalculation::Energy=>todo!(),
			//source style
			JumpCalculation::Linear=>velocity+jump_dir*(self.impulse.get_jump_deltav(&style.gravity,style.mass)/jump_dir.length()),
		}
	}
}

#[derive(Clone,Debug)]
pub struct AccelerateSettings{
	accel:Planar64,
	topspeed:Planar64,
}
#[derive(Clone,Debug)]
pub struct WalkSettings{
	accelerate:AccelerateSettings,
	static_friction:Planar64,
	kinetic_friction:Planar64,
	//if a surf slope angle does not exist, then everything is slippery and walking is impossible
	surf_dot:Planar64,//surf_dot<n.dot(up)/n.length()
}
impl WalkSettings{
	pub fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{
		//TODO: fallible walk accel
		let diff_len=target_diff.length();
		let friction=if diff_len<self.accelerate.topspeed{
			self.static_friction
		}else{
			self.kinetic_friction
		};
		self.accelerate.accel.min(-Planar64Vec3::Y.dot(gravity)*friction)
	}
	pub fn get_walk_target_velocity(&self,control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
		if control_dir==Planar64Vec3::ZERO{
			return control_dir;
		}
		let n=normal.length();
		let m=control_dir.length();
		let d=normal.dot(control_dir)/m;
		if d<n{
			let cr=normal.cross(control_dir);
			if cr==Planar64Vec3::ZERO{
				Planar64Vec3::ZERO
			}else{
				cr.cross(normal)*(self.accelerate.topspeed/(n*(n*n-d*d).sqrt()*m))
			}
		}else{
			Planar64Vec3::ZERO
		}
	}
	pub fn is_slope_walkable(&self,normal:Planar64Vec3,up:Planar64Vec3)->bool{
		//normal is not guaranteed to be unit length
		let ny=normal.dot(up);
		let h=normal.length();
		//remember this is a normal vector
		Planar64::ZERO<ny&&h*self.surf_dot<ny
	}
}

#[derive(Clone,Debug)]
pub struct LadderSettings{
	accelerate:AccelerateSettings,
	//how close to pushing directly into/out of the ladder normal
	//does your input need to be to redirect straight up/down the ladder
	dot:Planar64,
}
impl LadderSettings{
	pub fn accel(&self,target_diff:Planar64Vec3,gravity:Planar64Vec3)->Planar64{
		//TODO: fallible ladder accel
		self.accelerate.accel
	}
	pub fn get_ladder_target_velocity(&self,mut control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{
		if control_dir==Planar64Vec3::ZERO{
			return control_dir;
		}
		let n=normal.length();
		let m=control_dir.length();
		let mut d=normal.dot(control_dir)/m;
		if d< -self.dot*n{
			control_dir=Planar64Vec3::Y*m;
			d=normal.y();
		}else if self.dot*n<d{
			control_dir=Planar64Vec3::NEG_Y*m;
			d=-normal.y();
		}
		//n=d if you are standing on top of a ladder and press E.
		//two fixes:
		//- ladder movement is not allowed on walkable surfaces
		//- fix the underlying issue
		if d.get().unsigned_abs()<n.get().unsigned_abs(){
			let cr=normal.cross(control_dir);
			if cr==Planar64Vec3::ZERO{
				Planar64Vec3::ZERO
			}else{
				cr.cross(normal)*(self.accelerate.topspeed/(n*(n*n-d*d).sqrt()))
			}
		}else{
			Planar64Vec3::ZERO
		}
	}
}

#[derive(Clone,Debug)]
pub enum HitboxMesh{
	Box,//source
	Cylinder,//roblox
	//Sphere,//roblox old physics
	//Point,
	//Line,
	//DualCone,
}

#[derive(Clone,Debug)]
pub struct Hitbox{
	pub halfsize:Planar64Vec3,
	pub mesh:HitboxMesh,
}
impl Hitbox{
	pub fn roblox()->Self{
		Self{
			halfsize:Planar64Vec3::int(2,5,2)/2,
			mesh:HitboxMesh::Cylinder,
		}
	}
	pub fn source()->Self{
		Self{
			halfsize:Planar64Vec3::raw(33,73,33)/2*VALVE_SCALE,
			mesh:HitboxMesh::Box,
		}
	}
}

impl StyleModifiers{
	pub const RIGHT_DIR:Planar64Vec3=Planar64Vec3::X;
	pub const UP_DIR:Planar64Vec3=Planar64Vec3::Y;
	pub const FORWARD_DIR:Planar64Vec3=Planar64Vec3::NEG_Z;

	pub fn neo()->Self{
		Self{
			controls_mask:Controls::all(),
			controls_mask_state:Controls::all(),
			strafe:Some(StrafeSettings{
				enable:ControlsActivation::full_2d(),
				air_accel_limit:None,
				mv:Planar64::int(3),
				tick_rate:Ratio64::new(64,Time::ONE_SECOND.nanos() as u64).unwrap(),
			}),
			jump:Some(JumpSettings{
				impulse:JumpImpulse::FromEnergy(Planar64::int(512)),
				calculation:JumpCalculation::Energy,
			}),
			gravity:Planar64Vec3::int(0,-80,0),
			mass:Planar64::int(1),
			rocket:None,
			walk:Some(WalkSettings{
				accelerate:AccelerateSettings{
					topspeed:Planar64::int(16),
					accel:Planar64::int(80),
				},
				static_friction:Planar64::int(2),
				kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static
				surf_dot:Planar64::int(3)/4,
			}),
			ladder:Some(LadderSettings{
				accelerate:AccelerateSettings{
					topspeed:Planar64::int(16),
					accel:Planar64::int(160),
				},
				dot:(Planar64::int(1)/2).sqrt(),
			}),
			swim:Some(PropulsionSettings{
				magnitude:Planar64::int(12),
			}),
			hitbox:Hitbox::roblox(),
			camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2
		}
	}

	pub fn roblox_bhop()->Self{
		Self{
			controls_mask:Controls::all(),
			controls_mask_state:Controls::all(),
			strafe:Some(StrafeSettings{
				enable:ControlsActivation::full_2d(),
				air_accel_limit:None,
				mv:Planar64::int(27)/10,
				tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
			}),
			jump:Some(JumpSettings{
				impulse:JumpImpulse::FromTime(Time::from_micros(715_588)),
				calculation:JumpCalculation::Linear,//Should be capped
			}),
			gravity:Planar64Vec3::int(0,-100,0),
			mass:Planar64::int(1),
			rocket:None,
			walk:Some(WalkSettings{
				accelerate:AccelerateSettings{
					topspeed:Planar64::int(18),
					accel:Planar64::int(90),
				},
				static_friction:Planar64::int(2),
				kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static
				surf_dot:Planar64::int(3)/4,// normal.y=0.75
			}),
			ladder:Some(LadderSettings{
				accelerate:AccelerateSettings{
					topspeed:Planar64::int(18),
					accel:Planar64::int(180),
				},
				dot:(Planar64::int(1)/2).sqrt(),
			}),
			swim:Some(PropulsionSettings{
				magnitude:Planar64::int(12),
			}),
			hitbox:Hitbox::roblox(),
			camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2
		}
	}
	pub fn roblox_surf()->Self{
		Self{
			gravity:Planar64Vec3::int(0,-50,0),
			..Self::roblox_bhop()
		}
	}
	pub fn roblox_rocket()->Self{
		Self{
			strafe:None,
			rocket:Some(PropulsionSettings{
				magnitude:Planar64::int(200),
			}),
			..Self::roblox_bhop()
		}
	}

	pub fn source_bhop()->Self{
		Self{
			controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown,
			controls_mask_state:Controls::all(),
			strafe:Some(StrafeSettings{
				enable:ControlsActivation::full_2d(),
				air_accel_limit:Some(Planar64::raw(150<<28)*100),
				mv:Planar64::raw(30)*VALVE_SCALE,
				tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(),
			}),
			jump:Some(JumpSettings{
				impulse:JumpImpulse::FromHeight(Planar64::int(52)*VALVE_SCALE),
				calculation:JumpCalculation::Linear,
			}),
			gravity:Planar64Vec3::int(0,-800,0)*VALVE_SCALE,
			mass:Planar64::int(1),
			rocket:None,
			walk:Some(WalkSettings{
				accelerate:AccelerateSettings{
					topspeed:Planar64::int(18),//?
					accel:Planar64::int(90),//?
				},
				static_friction:Planar64::int(2),//?
				kinetic_friction:Planar64::int(3),//?
				surf_dot:Planar64::int(3)/4,// normal.y=0.75
			}),
			ladder:Some(LadderSettings{
				accelerate:AccelerateSettings{
					topspeed:Planar64::int(18),//?
					accel:Planar64::int(180),//?
				},
				dot:(Planar64::int(1)/2).sqrt(),//?
			}),
			swim:Some(PropulsionSettings{
				magnitude:Planar64::int(12),//?
			}),
			hitbox:Hitbox::source(),
			camera_offset:(Planar64Vec3::int(0,64,0)-Planar64Vec3::int(0,73,0)/2)*VALVE_SCALE,
		}
	}
	pub fn source_surf()->Self{
		Self{
			controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown,
			controls_mask_state:Controls::all(),
			strafe:Some(StrafeSettings{
				enable:ControlsActivation::full_2d(),
				air_accel_limit:Some(Planar64::int(150)*66*VALVE_SCALE),
				mv:Planar64::int(30)*VALVE_SCALE,
				tick_rate:Ratio64::new(66,Time::ONE_SECOND.nanos() as u64).unwrap(),
			}),
			jump:Some(JumpSettings{
				impulse:JumpImpulse::FromHeight(Planar64::int(52)*VALVE_SCALE),
				calculation:JumpCalculation::Linear,
			}),
			gravity:Planar64Vec3::int(0,-800,0)*VALVE_SCALE,
			mass:Planar64::int(1),
			rocket:None,
			walk:Some(WalkSettings{
				accelerate:AccelerateSettings{
					topspeed:Planar64::int(18),//?
					accel:Planar64::int(90),//?
				},
				static_friction:Planar64::int(2),//?
				kinetic_friction:Planar64::int(3),//?
				surf_dot:Planar64::int(3)/4,// normal.y=0.75
			}),
			ladder:Some(LadderSettings{
				accelerate:AccelerateSettings{
					topspeed:Planar64::int(18),//?
					accel:Planar64::int(180),//?
				},
				dot:(Planar64::int(1)/2).sqrt(),//?
			}),
			swim:Some(PropulsionSettings{
				magnitude:Planar64::int(12),//?
			}),
			hitbox:Hitbox::source(),
			camera_offset:(Planar64Vec3::int(0,64,0)-Planar64Vec3::int(0,73,0)/2)*VALVE_SCALE,
		}
	}
}