use super::common::flag;
use super::integer::{Time,Ratio64,Planar64,Planar64Vec3};

pub type Controls=u32;
#[derive(Debug)]
pub enum ControlsError{
	UnknownBits,
}
impl std::fmt::Display for ControlsError{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
		write!(f,"{self:?}")
	}
}
impl std::error::Error for ControlsError{}

#[binrw::binrw]
#[brw(little)]
pub struct StyleModifiers{
	pub header:u8,
	pub controls_mask:Controls,
	pub controls_mask_state:Controls,
	#[br(if(header&Self::STRAFE!=0))]
	pub strafe:Option<StrafeSettings>,
	#[br(if(header&Self::ROCKET!=0))]
	pub rocket:Option<PropulsionSettings>,
	#[br(if(header&Self::JUMP!=0))]
	pub jump:Option<JumpSettings>,
	#[br(if(header&Self::WALK!=0))]
	pub walk:Option<WalkSettings>,
	#[br(if(header&Self::LADDER!=0))]
	pub ladder:Option<LadderSettings>,
	#[br(if(header&Self::SWIM!=0))]
	pub swim:Option<PropulsionSettings>,
	pub gravity:Planar64Vec3,
	pub hitbox:Hitbox,
	pub camera_offset:Planar64Vec3,
	pub mass:Planar64,
}
impl StyleModifiers{
	const STRAFE:u8=1<<0;
	const ROCKET:u8=1<<1;
	const JUMP:u8=1<<2;
	const WALK:u8=1<<3;
	const LADDER:u8=1<<4;
	const SWIM:u8=1<<5;
}
#[derive(Debug)]
pub enum StyleModifierError{
	Controls(ControlsError),
	JumpSettings(JumpSettingsError),
	StrafeSettings(StrafeSettingsError),
}
impl std::fmt::Display for StyleModifierError{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
		write!(f,"{self:?}")
	}
}
impl std::error::Error for StyleModifierError{}
impl TryInto<strafesnet_common::gameplay_style::StyleModifiers> for StyleModifiers{
	type Error=StyleModifierError;
	fn try_into(self)->Result<strafesnet_common::gameplay_style::StyleModifiers,Self::Error>{
		Ok(strafesnet_common::gameplay_style::StyleModifiers{
			controls_mask:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_mask).ok_or(StyleModifierError::Controls(ControlsError::UnknownBits))?,
			controls_mask_state:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_mask_state).ok_or(StyleModifierError::Controls(ControlsError::UnknownBits))?,
			strafe:self.strafe.map(TryInto::try_into).transpose().map_err(StyleModifierError::StrafeSettings)?,
			rocket:self.rocket.map(Into::into),
			jump:self.jump.map(TryInto::try_into).transpose().map_err(StyleModifierError::JumpSettings)?,
			walk:self.walk.map(Into::into),
			ladder:self.ladder.map(Into::into),
			swim:self.swim.map(Into::into),
			gravity:strafesnet_common::integer::vec3::raw_array(self.gravity),
			hitbox:self.hitbox.into(),
			camera_offset:strafesnet_common::integer::vec3::raw_array(self.camera_offset),
			mass:strafesnet_common::integer::Planar64::raw(self.mass),
		})
	}
}
impl From<strafesnet_common::gameplay_style::StyleModifiers> for StyleModifiers{
	fn from(value:strafesnet_common::gameplay_style::StyleModifiers)->Self{
		let header=
			flag(value.strafe.is_some(),StyleModifiers::STRAFE)
			|flag(value.rocket.is_some(),StyleModifiers::ROCKET)
			|flag(value.jump.is_some(),StyleModifiers::JUMP)
			|flag(value.walk.is_some(),StyleModifiers::WALK)
			|flag(value.ladder.is_some(),StyleModifiers::LADDER)
			|flag(value.swim.is_some(),StyleModifiers::SWIM);
		Self{
			header,
			controls_mask:value.controls_mask.bits(),
			controls_mask_state:value.controls_mask_state.bits(),
			strafe:value.strafe.map(Into::into),
			rocket:value.rocket.map(Into::into),
			jump:value.jump.map(Into::into),
			walk:value.walk.map(Into::into),
			ladder:value.ladder.map(Into::into),
			swim:value.swim.map(Into::into),
			gravity:value.gravity.map(|t|t.to_raw()).to_array(),
			hitbox:value.hitbox.into(),
			camera_offset:value.camera_offset.map(|t|t.to_raw()).to_array(),
			mass:value.mass.to_raw(),
		}
	}
}

#[binrw::binrw]
#[brw(little,repr=u8)]
#[expect(dead_code)]
pub enum JumpCalculation{
	Max,
	BoostThenJump,
	JumpThenBoost,
}
impl Into<strafesnet_common::gameplay_style::JumpCalculation> for JumpCalculation{
	fn into(self)->strafesnet_common::gameplay_style::JumpCalculation{
		match self{
			JumpCalculation::Max=>strafesnet_common::gameplay_style::JumpCalculation::Max,
			JumpCalculation::BoostThenJump=>strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump,
			JumpCalculation::JumpThenBoost=>strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost,
		}
	}
}
impl From<strafesnet_common::gameplay_style::JumpCalculation> for JumpCalculation{
	fn from(value:strafesnet_common::gameplay_style::JumpCalculation)->Self{
		match value{
			strafesnet_common::gameplay_style::JumpCalculation::Max=>JumpCalculation::Max,
			strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump=>JumpCalculation::BoostThenJump,
			strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost=>JumpCalculation::JumpThenBoost,
		}
	}
}

#[expect(dead_code)]
pub enum JumpImpulse{
	Time(Time),
	Height(Planar64),
	Linear(Planar64),
	Energy(Planar64),
}
impl Into<strafesnet_common::gameplay_style::JumpImpulse> for JumpImpulse{
	fn into(self)->strafesnet_common::gameplay_style::JumpImpulse{
		match self{
			JumpImpulse::Time(time)=>strafesnet_common::gameplay_style::JumpImpulse::Time(strafesnet_common::integer::Time::raw(time)),
			JumpImpulse::Height(height)=>strafesnet_common::gameplay_style::JumpImpulse::Height(strafesnet_common::integer::Planar64::raw(height)),
			JumpImpulse::Linear(deltav)=>strafesnet_common::gameplay_style::JumpImpulse::Linear(strafesnet_common::integer::Planar64::raw(deltav)),
			JumpImpulse::Energy(energy)=>strafesnet_common::gameplay_style::JumpImpulse::Energy(strafesnet_common::integer::Planar64::raw(energy)),
		}
	}
}
impl From<strafesnet_common::gameplay_style::JumpImpulse> for JumpImpulse{
	fn from(value:strafesnet_common::gameplay_style::JumpImpulse)->Self{
		match value{
			strafesnet_common::gameplay_style::JumpImpulse::Time(time)=>JumpImpulse::Time(time.get()),
			strafesnet_common::gameplay_style::JumpImpulse::Height(height)=>JumpImpulse::Height(height.to_raw()),
			strafesnet_common::gameplay_style::JumpImpulse::Linear(deltav)=>JumpImpulse::Linear(deltav.to_raw()),
			strafesnet_common::gameplay_style::JumpImpulse::Energy(energy)=>JumpImpulse::Energy(energy.to_raw()),
		}
	}
}

#[binrw::binrw]
#[brw(little)]
pub struct ControlsActivation{
	controls_mask:Controls,
	controls_intersects:Controls,
	controls_contains:Controls,
}
impl TryInto<strafesnet_common::gameplay_style::ControlsActivation> for ControlsActivation{
	type Error=ControlsError;
	fn try_into(self)->Result<strafesnet_common::gameplay_style::ControlsActivation,Self::Error>{
		Ok(strafesnet_common::gameplay_style::ControlsActivation{
			controls_mask:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_mask).ok_or(ControlsError::UnknownBits)?,
			controls_intersects:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_intersects).ok_or(ControlsError::UnknownBits)?,
			controls_contains:strafesnet_common::controls_bitflag::Controls::from_bits(self.controls_contains).ok_or(ControlsError::UnknownBits)?,
		})
	}
}
impl From<strafesnet_common::gameplay_style::ControlsActivation> for ControlsActivation{
	fn from(value:strafesnet_common::gameplay_style::ControlsActivation)->Self{
		Self{
			controls_mask:value.controls_mask.bits(),
			controls_intersects:value.controls_intersects.bits(),
			controls_contains:value.controls_contains.bits(),
		}
	}
}

#[binrw::binrw]
#[brw(little)]
pub struct StrafeSettings{
	header:u8,
	enable:ControlsActivation,
	mv:Planar64,
	#[br(if(header&Self::AIR_ACCEL_LIMIT!=0))]
	air_accel_limit:Option<Planar64>,
	tick_rate:Ratio64,
}
impl StrafeSettings{
	const AIR_ACCEL_LIMIT:u8=1<<0;
}
#[derive(Debug)]
pub enum StrafeSettingsError{
	Ratio(super::integer::RatioError),
	Controls(ControlsError),
}
impl std::fmt::Display for StrafeSettingsError{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
		write!(f,"{self:?}")
	}
}
impl std::error::Error for StrafeSettingsError{}
impl TryInto<strafesnet_common::gameplay_style::StrafeSettings> for StrafeSettings{
	type Error=StrafeSettingsError;
	fn try_into(self)->Result<strafesnet_common::gameplay_style::StrafeSettings,Self::Error>{
		Ok(strafesnet_common::gameplay_style::StrafeSettings{
			enable:self.enable.try_into().map_err(StrafeSettingsError::Controls)?,
			mv:strafesnet_common::integer::Planar64::raw(self.mv),
			air_accel_limit:self.air_accel_limit.map(strafesnet_common::integer::Planar64::raw),
			tick_rate:self.tick_rate.try_into().map_err(StrafeSettingsError::Ratio)?,
		})
	}
}
impl From<strafesnet_common::gameplay_style::StrafeSettings> for StrafeSettings{
	fn from(value:strafesnet_common::gameplay_style::StrafeSettings)->Self{
		let header=flag(value.air_accel_limit.is_some(),StrafeSettings::AIR_ACCEL_LIMIT);
		Self{
			header,
			enable:value.enable.into(),
			mv:value.mv.to_raw(),
			air_accel_limit:value.air_accel_limit.map(|a|a.to_raw()),
			tick_rate:value.tick_rate.into(),
		}
	}
}

#[binrw::binrw]
#[brw(little)]
pub struct PropulsionSettings{
	magnitude:Planar64,
}
impl Into<strafesnet_common::gameplay_style::PropulsionSettings> for PropulsionSettings{
	fn into(self)->strafesnet_common::gameplay_style::PropulsionSettings{
		strafesnet_common::gameplay_style::PropulsionSettings{
			magnitude:strafesnet_common::integer::Planar64::raw(self.magnitude)
		}
	}
}
impl From<strafesnet_common::gameplay_style::PropulsionSettings> for PropulsionSettings{
	fn from(value:strafesnet_common::gameplay_style::PropulsionSettings)->Self{
		Self{
			magnitude:value.magnitude.to_raw(),
		}
	}
}

#[binrw::binrw]
#[brw(little)]
pub struct JumpSettings{
	header:u8,
	impulse:i64,
}
impl JumpSettings{
	const IMPULSE:u8=0b00011;
	const CALCULATION:u8=0b01100;
	const LIMIT_MINIMUM:u8=0b10000;
	const fn impulse(&self)->Option<strafesnet_common::gameplay_style::JumpImpulse>{
		match self.header&Self::IMPULSE{
			0=>Some(strafesnet_common::gameplay_style::JumpImpulse::Time(strafesnet_common::integer::Time::raw(self.impulse))),
			1=>Some(strafesnet_common::gameplay_style::JumpImpulse::Height(strafesnet_common::integer::Planar64::raw(self.impulse))),
			2=>Some(strafesnet_common::gameplay_style::JumpImpulse::Linear(strafesnet_common::integer::Planar64::raw(self.impulse))),
			3=>Some(strafesnet_common::gameplay_style::JumpImpulse::Energy(strafesnet_common::integer::Planar64::raw(self.impulse))),
			_=>None,
		}
	}
	const fn calculation(&self)->Option<strafesnet_common::gameplay_style::JumpCalculation>{
		match (self.header&Self::CALCULATION)>>2{
			0=>Some(strafesnet_common::gameplay_style::JumpCalculation::Max),
			1=>Some(strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost),
			2=>Some(strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump),
			_=>None,
		}
	}
	const fn limit_minimum(&self)->bool{
		self.header&Self::LIMIT_MINIMUM!=0
	}
}
#[derive(Debug)]
pub enum JumpSettingsError{
	InvalidImpulseDiscriminant,
	InvalidCalculationDiscriminant,
}
impl TryInto<strafesnet_common::gameplay_style::JumpSettings> for JumpSettings{
	type Error=JumpSettingsError;
	fn try_into(self)->Result<strafesnet_common::gameplay_style::JumpSettings,Self::Error>{
		Ok(strafesnet_common::gameplay_style::JumpSettings{
			impulse:self.impulse().ok_or(JumpSettingsError::InvalidImpulseDiscriminant)?,
			calculation:self.calculation().ok_or(JumpSettingsError::InvalidCalculationDiscriminant)?,
			limit_minimum:self.limit_minimum(),
		})
	}
}
impl From<strafesnet_common::gameplay_style::JumpSettings> for JumpSettings{
	fn from(value:strafesnet_common::gameplay_style::JumpSettings)->Self{
		let (impulse,impulse_header)=match value.impulse{
			strafesnet_common::gameplay_style::JumpImpulse::Time(impulse)=>(impulse.get(),0),
			strafesnet_common::gameplay_style::JumpImpulse::Height(impulse)=>(impulse.to_raw(),1),
			strafesnet_common::gameplay_style::JumpImpulse::Linear(impulse)=>(impulse.to_raw(),2),
			strafesnet_common::gameplay_style::JumpImpulse::Energy(impulse)=>(impulse.to_raw(),3),
		};
		let calculation_header=match value.calculation{
			strafesnet_common::gameplay_style::JumpCalculation::Max=>0,
			strafesnet_common::gameplay_style::JumpCalculation::JumpThenBoost=>1,
			strafesnet_common::gameplay_style::JumpCalculation::BoostThenJump=>2,
		};
		let header=
			impulse_header
			|(calculation_header<<2)
			|((value.limit_minimum as u8)<<4);
		Self{
			header,
			impulse,
		}
	}
}

#[binrw::binrw]
#[brw(little)]
pub struct AccelerateSettings{
	accel:Planar64,
	topspeed:Planar64,
}
impl Into<strafesnet_common::gameplay_style::AccelerateSettings> for AccelerateSettings{
	fn into(self)->strafesnet_common::gameplay_style::AccelerateSettings{
		strafesnet_common::gameplay_style::AccelerateSettings{
			accel:strafesnet_common::integer::Planar64::raw(self.accel),
			topspeed:strafesnet_common::integer::Planar64::raw(self.topspeed),
		}
	}
}
impl From<strafesnet_common::gameplay_style::AccelerateSettings> for AccelerateSettings{
	fn from(value:strafesnet_common::gameplay_style::AccelerateSettings)->Self{
		Self{
			accel:value.accel.to_raw(),
			topspeed:value.topspeed.to_raw(),
		}
	}
}

#[binrw::binrw]
#[brw(little)]
pub struct WalkSettings{
	accelerate:AccelerateSettings,
	static_friction:Planar64,
	kinetic_friction:Planar64,
	surf_dot:Planar64,
}
impl Into<strafesnet_common::gameplay_style::WalkSettings> for WalkSettings{
	fn into(self)->strafesnet_common::gameplay_style::WalkSettings{
		strafesnet_common::gameplay_style::WalkSettings{
			accelerate:self.accelerate.into(),
			static_friction:strafesnet_common::integer::Planar64::raw(self.static_friction),
			kinetic_friction:strafesnet_common::integer::Planar64::raw(self.kinetic_friction),
			surf_dot:strafesnet_common::integer::Planar64::raw(self.surf_dot),
		}
	}
}
impl From<strafesnet_common::gameplay_style::WalkSettings> for WalkSettings{
	fn from(value:strafesnet_common::gameplay_style::WalkSettings)->Self{
		Self{
			accelerate:value.accelerate.into(),
			static_friction:value.static_friction.to_raw(),
			kinetic_friction:value.kinetic_friction.to_raw(),
			surf_dot:value.surf_dot.to_raw(),
		}
	}
}

#[binrw::binrw]
#[brw(little)]
pub struct LadderSettings{
	accelerate:AccelerateSettings,
	dot:Planar64,
}
impl Into<strafesnet_common::gameplay_style::LadderSettings> for LadderSettings{
	fn into(self)->strafesnet_common::gameplay_style::LadderSettings{
		strafesnet_common::gameplay_style::LadderSettings{
			accelerate:self.accelerate.into(),
			dot:strafesnet_common::integer::Planar64::raw(self.dot),
		}
	}
}
impl From<strafesnet_common::gameplay_style::LadderSettings> for LadderSettings{
	fn from(value:strafesnet_common::gameplay_style::LadderSettings)->Self{
		Self{
			accelerate:value.accelerate.into(),
			dot:value.dot.to_raw(),
		}
	}
}

#[binrw::binrw]
#[brw(little,repr=u8)]
pub enum HitboxMesh{
	Box,
	Cylinder,
}
impl Into<strafesnet_common::gameplay_style::HitboxMesh> for HitboxMesh{
	fn into(self)->strafesnet_common::gameplay_style::HitboxMesh{
		match self{
			HitboxMesh::Box=>strafesnet_common::gameplay_style::HitboxMesh::Box,
			HitboxMesh::Cylinder=>strafesnet_common::gameplay_style::HitboxMesh::Cylinder,
		}
	}
}
impl From<strafesnet_common::gameplay_style::HitboxMesh> for HitboxMesh{
	fn from(value:strafesnet_common::gameplay_style::HitboxMesh)->Self{
		match value{
			strafesnet_common::gameplay_style::HitboxMesh::Box=>HitboxMesh::Box,
			strafesnet_common::gameplay_style::HitboxMesh::Cylinder=>HitboxMesh::Cylinder,
		}
	}
}

#[binrw::binrw]
#[brw(little)]
pub struct Hitbox{
	pub halfsize:Planar64Vec3,
	pub mesh:HitboxMesh,
}
impl Into<strafesnet_common::gameplay_style::Hitbox> for Hitbox{
	fn into(self)->strafesnet_common::gameplay_style::Hitbox{
		strafesnet_common::gameplay_style::Hitbox{
			halfsize:strafesnet_common::integer::vec3::raw_array(self.halfsize),
			mesh:self.mesh.into(),
		}
	}
}
impl From<strafesnet_common::gameplay_style::Hitbox> for Hitbox{
	fn from(value:strafesnet_common::gameplay_style::Hitbox)->Self{
		Self{
			halfsize:value.halfsize.map(|t|t.to_raw()).to_array(),
			mesh:value.mesh.into(),
		}
	}
}
