use std::collections::{HashSet,HashMap};
use crate::model::ModelId;
use crate::gameplay_style;

#[derive(Clone)]
pub struct StageElement{
	stage_id:StageId,//which stage spawn to send to
	force:bool,//allow setting to lower spawn id i.e. 7->3
	behaviour:StageElementBehaviour,
	jump_limit:Option<u8>,
}
impl StageElement{
	#[inline]
	pub const fn new(stage_id:StageId,force:bool,behaviour:StageElementBehaviour,jump_limit:Option<u8>)->Self{
		Self{
			stage_id,
			force,
			behaviour,
			jump_limit,
		}
	}
	#[inline]
	pub const fn stage_id(&self)->StageId{
		self.stage_id
	}
	#[inline]
	pub const fn force(&self)->bool{
		self.force
	}
	#[inline]
	pub const fn behaviour(&self)->StageElementBehaviour{
		self.behaviour
	}
	#[inline]
	pub const fn jump_limit(&self)->Option<u8>{
		self.jump_limit
	}
}

#[derive(Clone,Copy,Hash,Eq,PartialEq)]
pub enum StageElementBehaviour{
	SpawnAt,//must be standing on top to get effect. except cancollide false
	Trigger,
	Teleport,
	Platform,
	//Check(point) acts like a trigger if you haven't hit all the checkpoints on previous stages yet.
	//Note that all stage elements act like this, this is just the isolated behaviour.
	Check,
	Checkpoint,//this is a combined behaviour for Ordered & Unordered in case a model is used multiple times or for both.
}

#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)]
pub struct CheckpointId(u32);
impl CheckpointId{
	pub const FIRST:Self=Self(0);
}
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)]
pub struct StageId(u32);
impl StageId{
	pub const FIRST:Self=Self(0);
}
#[derive(Clone)]
pub struct Stage{
	spawn:ModelId,
	//open world support lol
	ordered_checkpoints_count:u32,
	unordered_checkpoints_count:u32,
	//currently loaded checkpoint models
	ordered_checkpoints:HashMap<CheckpointId,ModelId>,
	unordered_checkpoints:HashSet<ModelId>,
}
impl Stage{
	pub fn new(
		spawn:ModelId,
		ordered_checkpoints_count:u32,
		unordered_checkpoints_count:u32,
		ordered_checkpoints:HashMap<CheckpointId,ModelId>,
		unordered_checkpoints:HashSet<ModelId>,
	)->Self{
		Self{
			spawn,
			ordered_checkpoints_count,
			unordered_checkpoints_count,
			ordered_checkpoints,
			unordered_checkpoints,
		}
	}
	pub fn empty(spawn:ModelId)->Self{
		Self{
			spawn,
			ordered_checkpoints_count:0,
			unordered_checkpoints_count:0,
			ordered_checkpoints:HashMap::new(),
			unordered_checkpoints:HashSet::new(),
		}
	}
	#[inline]
	pub const fn spawn(&self)->ModelId{
		self.spawn
	}
	#[inline]
	pub const fn ordered_checkpoints_count(&self)->u32{
		self.ordered_checkpoints_count
	}
	#[inline]
	pub const fn unordered_checkpoints_count(&self)->u32{
		self.unordered_checkpoints_count
	}
	pub fn into_inner(self)->(HashMap<CheckpointId,ModelId>,HashSet<ModelId>){
		(self.ordered_checkpoints,self.unordered_checkpoints)
	}
	/// Returns true if the stage has no checkpoints.
	#[inline]
	pub const fn is_empty(&self)->bool{
		self.is_complete(0,0)
	}
	#[inline]
	pub const fn is_complete(&self,ordered_checkpoints_count:u32,unordered_checkpoints_count:u32)->bool{
		self.ordered_checkpoints_count==ordered_checkpoints_count&&self.unordered_checkpoints_count==unordered_checkpoints_count
	}
	#[inline]
	pub fn is_next_ordered_checkpoint(&self,next_ordered_checkpoint_id:CheckpointId,model_id:ModelId)->bool{
		self.ordered_checkpoints.get(&next_ordered_checkpoint_id).is_some_and(|&next_checkpoint|model_id==next_checkpoint)
	}
	#[inline]
	pub fn is_unordered_checkpoint(&self,model_id:ModelId)->bool{
		self.unordered_checkpoints.contains(&model_id)
	}
}

#[derive(Clone,Copy,Hash,Eq,PartialEq)]
pub enum Zone{
	Start,
	Finish,
	Anticheat,
}
#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)]
pub struct ModeId(u32);
impl ModeId{
	pub const MAIN:Self=Self(0);
	pub const BONUS:Self=Self(1);
}
impl core::fmt::Display for ModeId{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->core::fmt::Result{
		match self{
			&Self::MAIN=>write!(f,"Main"),
			&Self::BONUS=>write!(f,"Bonus"),
			&Self(mode_id)=>write!(f,"Bonus{mode_id}"),
		}
	}
}
#[derive(Clone)]
pub struct Mode{
	style:gameplay_style::StyleModifiers,
	start:ModelId,//when you press reset you go here
	zones:HashMap<ModelId,Zone>,
	stages:Vec<Stage>,//when you load the map you go to stages[0].spawn
	//mutually exlusive stage element behaviour
	elements:HashMap<ModelId,StageElement>,
}
impl Mode{
	pub fn new(
		style:gameplay_style::StyleModifiers,
		start:ModelId,
		zones:HashMap<ModelId,Zone>,
		stages:Vec<Stage>,
		elements:HashMap<ModelId,StageElement>,
	)->Self{
		Self{
			style,
			start,
			zones,
			stages,
			elements,
		}
	}
	pub fn empty(style:gameplay_style::StyleModifiers,start:ModelId)->Self{
		Self{
			style,
			start,
			zones:HashMap::new(),
			stages:Vec::new(),
			elements:HashMap::new(),
		}
	}
	pub fn into_inner(self)->(
		gameplay_style::StyleModifiers,
		ModelId,
		HashMap<ModelId,Zone>,
		Vec<Stage>,
		HashMap<ModelId,StageElement>,
	){
		(
			self.style,
			self.start,
			self.zones,
			self.stages,
			self.elements,
		)
	}
	pub const fn get_start(&self)->ModelId{
		self.start
	}
	pub const fn get_style(&self)->&gameplay_style::StyleModifiers{
		&self.style
	}
	pub fn push_stage(&mut self,stage:Stage){
		self.stages.push(stage)
	}
	pub fn get_stage_mut(&mut self,StageId(stage_id):StageId)->Option<&mut Stage>{
		self.stages.get_mut(stage_id as usize)
	}
	pub fn get_spawn_model_id(&self,StageId(stage_id):StageId)->Option<ModelId>{
		self.stages.get(stage_id as usize).map(|s|s.spawn)
	}
	pub fn get_zone(&self,model_id:ModelId)->Option<&Zone>{
		self.zones.get(&model_id)
	}
	pub fn get_stage(&self,StageId(stage_id):StageId)->Option<&Stage>{
		self.stages.get(stage_id as usize)
	}
	pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{
		self.elements.get(&model_id)
	}
}

#[derive(Clone)]
pub struct NormalizedMode(Mode);
impl NormalizedMode{
	pub fn new(mode:Mode)->Self{
		Self(mode)
	}
	pub fn into_inner(self)->Mode{
		let Self(mode)=self;
		mode
	}
	//TODO: put this in the SNF
	pub fn denormalize(self)->Mode{
		let NormalizedMode(mut mode)=self;
		//expand and index normalized data
		mode.zones.insert(mode.start,Zone::Start);
		for (stage_id,stage) in mode.stages.iter().enumerate(){
			mode.elements.insert(stage.spawn,StageElement{
				stage_id:StageId(stage_id as u32),
				force:false,
				behaviour:StageElementBehaviour::SpawnAt,
				jump_limit:None,
			});
			for (_,&model) in &stage.ordered_checkpoints{
				mode.elements.insert(model,StageElement{
					stage_id:StageId(stage_id as u32),
					force:false,
					behaviour:StageElementBehaviour::Checkpoint,
					jump_limit:None,
				});
			}
			for &model in &stage.unordered_checkpoints{
				mode.elements.insert(model,StageElement{
					stage_id:StageId(stage_id as u32),
					force:false,
					behaviour:StageElementBehaviour::Checkpoint,
					jump_limit:None,
				});
			}
		}
		mode
	}
}

#[derive(Default,Clone)]
pub struct Modes{
	modes:Vec<Mode>,
}
impl Modes{
	pub const fn new(modes:Vec<Mode>)->Self{
		Self{
			modes,
		}
	}
	pub fn into_inner(self)->Vec<Mode>{
		self.modes
	}
	pub fn push_mode(&mut self,mode:Mode){
		self.modes.push(mode)
	}
	pub fn get_mode(&self,ModeId(mode_id):ModeId)->Option<&Mode>{
		self.modes.get(mode_id as usize)
	}
}

#[derive(Clone)]
pub struct NormalizedModes{
	modes:Vec<NormalizedMode>,
}
impl NormalizedModes{
	pub fn new(modes:Vec<NormalizedMode>)->Self{
		Self{modes}
	}
	pub fn len(&self)->usize{
		self.modes.len()
	}
	pub fn denormalize(self)->Modes{
		Modes{
			modes:self.modes.into_iter().map(NormalizedMode::denormalize).collect(),
		}
	}
}
impl IntoIterator for NormalizedModes{
	type Item=<Vec<NormalizedMode> as IntoIterator>::Item;
	type IntoIter=<Vec<NormalizedMode> as IntoIterator>::IntoIter;
	fn into_iter(self)->Self::IntoIter{
		self.modes.into_iter()
	}
}


#[derive(Default)]
pub struct StageUpdate{
	//other behaviour models of this stage can have
	ordered_checkpoints:HashMap<CheckpointId,ModelId>,
	unordered_checkpoints:HashSet<ModelId>,
}
impl StageUpdate{
	fn apply_to(self,stage:&mut Stage){
		stage.ordered_checkpoints.extend(self.ordered_checkpoints);
		stage.unordered_checkpoints.extend(self.unordered_checkpoints);
	}
}

//this would be nice as a macro
#[derive(Default)]
pub struct ModeUpdate{
	zones:Option<(ModelId,Zone)>,
	stages:Option<(StageId,StageUpdate)>,
	//mutually exlusive stage element behaviour
	elements:Option<(ModelId,StageElement)>,
}
impl ModeUpdate{
	fn apply_to(self,mode:&mut Mode){
		mode.zones.extend(self.zones);
		if let Some((StageId(stage_id),stage_update))=self.stages{
			if let Some(stage)=mode.stages.get_mut(stage_id as usize){
				stage_update.apply_to(stage);
			}
		}
		mode.elements.extend(self.elements);
	}
}
impl ModeUpdate{
	pub fn zone(model_id:ModelId,zone:Zone)->Self{
		Self{
			zones:Some((model_id,zone)),
			stages:None,
			elements:None,
		}
	}
	pub fn stage(stage_id:StageId,stage_update:StageUpdate)->Self{
		Self{
			zones:None,
			stages:Some((stage_id,stage_update)),
			elements:None,
		}
	}
	pub fn element(model_id:ModelId,element:StageElement)->Self{
		Self{
			zones:None,
			stages:None,
			elements:Some((model_id,element)),
		}
	}
	pub fn map_stage_element_ids<F:Fn(StageId)->StageId>(&mut self,f:F){
		for (_,stage_element) in self.elements.iter_mut(){
			stage_element.stage_id=f(stage_element.stage_id);
		}
	}
}

struct ModeBuilder{
	mode:Mode,
	stage_id_map:HashMap<StageId,StageId>,
}
#[derive(Default)]
pub struct ModesBuilder{
	modes:HashMap<ModeId,Mode>,
	stages:HashMap<ModeId,HashMap<StageId,Stage>>,
	mode_updates:Vec<(ModeId,ModeUpdate)>,
	stage_updates:Vec<(ModeId,StageId,StageUpdate)>,
}
impl ModesBuilder{
	pub fn build_normalized(mut self)->NormalizedModes{
		//collect modes and stages into contiguous arrays
		let mut unique_modes:Vec<(ModeId,Mode)>
		=self.modes.into_iter().collect();
		unique_modes.sort_by_key(|&(mode_id,_)|mode_id);
		let (mut modes,mode_id_map):(Vec<ModeBuilder>,HashMap<ModeId,ModeId>)
		=unique_modes.into_iter().enumerate()
		.map(|(final_mode_id,(builder_mode_id,mut mode))|{
			(
				ModeBuilder{
					stage_id_map:self.stages.remove(&builder_mode_id).map_or_else(||HashMap::new(),|stages|{
						let mut unique_stages:Vec<(StageId,Stage)>
						=stages.into_iter().collect();
						unique_stages.sort_by_key(|&(StageId(stage_id),_)|stage_id);
						unique_stages.into_iter().enumerate()
						.map(|(final_stage_id,(builder_stage_id,stage))|{
							mode.push_stage(stage);
							(builder_stage_id,StageId::new(final_stage_id as u32))
						}).collect()
					}),
					mode,
				},
				(
					builder_mode_id,
					ModeId::new(final_mode_id as u32)
				)
			)
		}).unzip();
		//TODO: failure messages or errors or something
		//push stage updates
		for (builder_mode_id,builder_stage_id,stage_update) in self.stage_updates{
			if let Some(final_mode_id)=mode_id_map.get(&builder_mode_id){
				if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
					if let Some(&final_stage_id)=mode.stage_id_map.get(&builder_stage_id){
						if let Some(stage)=mode.mode.get_stage_mut(final_stage_id){
							stage_update.apply_to(stage);
						}
					}
				}
			}
		}
		//push mode updates
		for (builder_mode_id,mut mode_update) in self.mode_updates{
			if let Some(final_mode_id)=mode_id_map.get(&builder_mode_id){
				if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
					//map stage id on stage elements
					mode_update.map_stage_element_ids(|stage_id|
						//walk down one stage id at a time until a stage is found
						//TODO use better logic like BTreeMap::upper_bound instead of walking
						// final_stage_id_from_builder_stage_id.upper_bound(Bound::Included(&stage_id))
						// .value().copied().unwrap_or(StageId::FIRST)
						(0..=stage_id.get()).rev().find_map(|builder_stage_id|
							//map the stage element to that stage
							mode.stage_id_map.get(&StageId::new(builder_stage_id)).copied()
						).unwrap_or(StageId::FIRST)
					);
					mode_update.apply_to(&mut mode.mode);
				}
			}
		}
		NormalizedModes::new(modes.into_iter().map(|mode_builder|NormalizedMode(mode_builder.mode)).collect())
	}
	pub fn insert_mode(&mut self,mode_id:ModeId,mode:Mode)->Result<(),ExistingEntryError>{
		error_if_exists(self.modes.insert(mode_id,mode))
	}
	pub fn insert_stage(&mut self,mode_id:ModeId,stage_id:StageId,stage:Stage)->Result<(),ExistingEntryError>{
		error_if_exists(self.stages.entry(mode_id).or_insert(HashMap::new()).insert(stage_id,stage))
	}
	pub fn push_mode_update(&mut self,mode_id:ModeId,mode_update:ModeUpdate){
		self.mode_updates.push((mode_id,mode_update));
	}
	// fn push_stage_update(&mut self,mode_id:ModeId,stage_id:StageId,stage_update:StageUpdate){
	// 	self.stage_updates.push((mode_id,stage_id,stage_update));
	// }
}

#[derive(Debug)]
pub struct ExistingEntryError;
fn error_if_exists<T>(value:Option<T>)->Result<(),ExistingEntryError>{
	match value{
		Some(_)=>Err(ExistingEntryError),
		None=>Ok(())
	}
}
