use super::common::flag;

pub type ModeId=u32;
pub type StageId=u32;

#[binrw::binrw]
#[brw(little)]
pub struct StageElement{
	pub header:u8,
	pub stage_id:StageId,
	#[br(if(header&Self::JUMP_LIMIT!=0))]
	pub jump_limit:Option<u8>,
}
impl StageElement{
	const BEHAVIOUR:u8=0b00111;
	const JUMP_LIMIT:u8=1<<3;
	const FORCE:u8=1<<4;
	const fn behaviour(&self)->Option<strafesnet_common::gameplay_modes::StageElementBehaviour>{
		match self.header&Self::BEHAVIOUR{
			0=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::SpawnAt),
			1=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Trigger),
			2=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Teleport),
			3=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Platform),
			4=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Check),
			5=>Some(strafesnet_common::gameplay_modes::StageElementBehaviour::Checkpoint),
			_=>None,
		}
	}
	const fn force(&self)->bool{
		self.header&Self::FORCE!=0
	}
}
#[derive(Debug)]
pub enum StageElementError{
	InvalidBehaviour,
}
impl std::fmt::Display for StageElementError{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
		write!(f,"{self:?}")
	}
}
impl std::error::Error for StageElementError{}
impl TryInto<strafesnet_common::gameplay_modes::StageElement> for StageElement{
	type Error=StageElementError;
	fn try_into(self)->Result<strafesnet_common::gameplay_modes::StageElement,Self::Error>{
		Ok(strafesnet_common::gameplay_modes::StageElement::new(
			strafesnet_common::gameplay_modes::StageId::new(self.stage_id),
			self.force(),
			self.behaviour().ok_or(StageElementError::InvalidBehaviour)?,
			self.jump_limit,
		))
	}
}
impl From<strafesnet_common::gameplay_modes::StageElement> for StageElement{
	fn from(value:strafesnet_common::gameplay_modes::StageElement)->Self{
		let behaviour=match value.behaviour(){
			strafesnet_common::gameplay_modes::StageElementBehaviour::SpawnAt=>0,
			strafesnet_common::gameplay_modes::StageElementBehaviour::Trigger=>1,
			strafesnet_common::gameplay_modes::StageElementBehaviour::Teleport=>2,
			strafesnet_common::gameplay_modes::StageElementBehaviour::Platform=>3,
			strafesnet_common::gameplay_modes::StageElementBehaviour::Check=>4,
			strafesnet_common::gameplay_modes::StageElementBehaviour::Checkpoint=>5,
		};
		let header=
			behaviour
			|flag(value.jump_limit().is_some(),StageElement::JUMP_LIMIT)
			|flag(value.force(),StageElement::FORCE);
		Self{
			header,
			stage_id:value.stage_id().get(),
			jump_limit:value.jump_limit(),
		}
	}
}

#[binrw::binrw]
#[brw(little)]
pub struct Stage{
	pub spawn:u32,
	//open world support lol
	pub ordered_checkpoints_count:u32,
	pub unordered_checkpoints_count:u32,
	//currently loaded checkpoint models
	#[br(count=ordered_checkpoints_count)]
	pub ordered_checkpoints:Vec<(u32,u32)>,
	#[br(count=unordered_checkpoints_count)]
	pub unordered_checkpoints:Vec<u32>,
}
impl Into<strafesnet_common::gameplay_modes::Stage> for Stage{
	fn into(self)->strafesnet_common::gameplay_modes::Stage{
		strafesnet_common::gameplay_modes::Stage::new(
			strafesnet_common::model::ModelId::new(self.spawn),
			self.ordered_checkpoints_count,
			self.unordered_checkpoints_count,
			self.ordered_checkpoints.into_iter().map(|(checkpoint_id,model_id)|(
				strafesnet_common::gameplay_modes::CheckpointId::new(checkpoint_id),
				strafesnet_common::model::ModelId::new(model_id),
			)).collect(),
			self.unordered_checkpoints.into_iter()
			.map(strafesnet_common::model::ModelId::new)
			.collect(),
		)
	}
}
impl From<strafesnet_common::gameplay_modes::Stage> for Stage{
	fn from(value:strafesnet_common::gameplay_modes::Stage)->Self{
		let spawn=value.spawn().get();
		let ordered_checkpoints_count=value.ordered_checkpoints_count();
		let unordered_checkpoints_count=value.unordered_checkpoints_count();
		let (ordered_checkpoints,unordered_checkpoints)=value.into_inner();
		Self{
			spawn,
			ordered_checkpoints_count,
			unordered_checkpoints_count,
			ordered_checkpoints:ordered_checkpoints.into_iter()
			.map(|(checkpoint_id,model_id)|(checkpoint_id.get(),model_id.get()))
			.collect(),
			unordered_checkpoints:unordered_checkpoints.into_iter()
			.map(|model_id|model_id.get())
			.collect(),
		}
	}
}

#[binrw::binrw]
#[brw(little,repr=u8)]
pub enum Zone{
	Start,
	Finish,
	Anticheat,
}
impl Into<strafesnet_common::gameplay_modes::Zone> for Zone{
	fn into(self)->strafesnet_common::gameplay_modes::Zone{
		match self{
			Zone::Start=>strafesnet_common::gameplay_modes::Zone::Start,
			Zone::Finish=>strafesnet_common::gameplay_modes::Zone::Finish,
			Zone::Anticheat=>strafesnet_common::gameplay_modes::Zone::Anticheat,
		}
	}
}
impl From<strafesnet_common::gameplay_modes::Zone> for Zone{
	fn from(value:strafesnet_common::gameplay_modes::Zone)->Self{
		match value{
			strafesnet_common::gameplay_modes::Zone::Start=>Zone::Start,
			strafesnet_common::gameplay_modes::Zone::Finish=>Zone::Finish,
			strafesnet_common::gameplay_modes::Zone::Anticheat=>Zone::Anticheat,
		}
	}
}

#[binrw::binrw]
#[brw(little)]
pub struct ModeHeader{
	pub zones:u32,
	pub stages:u32,
	pub elements:u32,
}
#[binrw::binrw]
#[brw(little)]
pub struct Mode{
	pub header:ModeHeader,
	pub style:super::gameplay_style::StyleModifiers,
	pub start:u32,
	#[br(count=header.zones)]
	pub zones:Vec<(u32,Zone)>,
	#[br(count=header.stages)]
	pub stages:Vec<Stage>,
	#[br(count=header.elements)]
	pub elements:Vec<(u32,StageElement)>,
}
#[derive(Debug)]
pub enum ModeError{
	StyleModifier(super::gameplay_style::StyleModifierError),
	StageElement(StageElementError),
}
impl std::fmt::Display for ModeError{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
		write!(f,"{self:?}")
	}
}
impl std::error::Error for ModeError{}
impl TryInto<strafesnet_common::gameplay_modes::Mode> for Mode{
	type Error=ModeError;
	fn try_into(self)->Result<strafesnet_common::gameplay_modes::Mode,Self::Error>{
		Ok(strafesnet_common::gameplay_modes::Mode::new(
			self.style.try_into().map_err(ModeError::StyleModifier)?,
			strafesnet_common::model::ModelId::new(self.start),
			self.zones.into_iter().map(|(model_id,zone)|
				(strafesnet_common::model::ModelId::new(model_id),zone.into())
			).collect(),
			self.stages.into_iter().map(Into::into).collect(),
			self.elements.into_iter().map(|(model_id,stage_element)|
				Ok((strafesnet_common::model::ModelId::new(model_id),stage_element.try_into()?))
			).collect::<Result<_,_>>().map_err(ModeError::StageElement)?,
		))
	}
}
impl From<strafesnet_common::gameplay_modes::Mode> for Mode{
	fn from(value:strafesnet_common::gameplay_modes::Mode)->Self{
		let (style,start,zones,stages,elements)=value.into_inner();
		Self{
			header:ModeHeader{
				zones:zones.len() as u32,
				stages:stages.len() as u32,
				elements:elements.len() as u32,
			},
			style:style.into(),
			start:start.get(),
			zones:zones.into_iter()
			.map(|(model_id,zone)|(model_id.get(),zone.into()))
			.collect(),
			stages:stages.into_iter()
			.map(Into::into)
			.collect(),
			elements:elements.into_iter()
			.map(|(model_id,stage_element)|(model_id.get(),stage_element.into()))
			.collect(),
		}
	}
}
