use std::collections::HashMap;
use crate::loader::Loader;
use crate::mesh::Meshes;
use crate::texture::{RenderConfigs,Texture};
use strafesnet_common::model::{MeshId,RenderConfig,RenderConfigId,TextureId};

#[derive(Clone,Copy,Debug)]
pub enum LoadFailureMode{
	DefaultToNone,
	Fatal,
}

pub struct RenderConfigDeferredLoader<H>{
	texture_count:u32,
	render_configs:Vec<RenderConfig>,
	render_config_id_from_asset_id:HashMap<Option<H>,RenderConfigId>,
}
impl<H> RenderConfigDeferredLoader<H>{
	pub fn new()->Self{
		Self{
			texture_count:0,
			render_configs:Vec::new(),
			render_config_id_from_asset_id:HashMap::new(),
		}
	}
}

impl<H:core::hash::Hash+Eq> RenderConfigDeferredLoader<H>{
	pub fn acquire_render_config_id(&mut self,index:Option<H>)->RenderConfigId{
		let some_texture=index.is_some();
		*self.render_config_id_from_asset_id.entry(index).or_insert_with(||{
			//create the render config.
			let render_config=if some_texture{
				let render_config=RenderConfig::texture(TextureId::new(self.texture_count));
				self.texture_count+=1;
				render_config
			}else{
				RenderConfig::default()
			};
			let render_id=RenderConfigId::new(self.render_configs.len() as u32);
			self.render_configs.push(render_config);
			render_id
		})
	}
	pub fn into_indices(self)->impl Iterator<Item=H>{
		self.render_config_id_from_asset_id.into_keys().flatten()
	}
	pub fn into_render_configs<'a,L:Loader<Resource=Texture,Index<'a>=H>+'a>(mut self,loader:&mut L,failure_mode:LoadFailureMode)->Result<RenderConfigs,L::Error>{
		let mut sorted_textures=vec![None;self.texture_count as usize];
		for (index_option,render_config_id) in self.render_config_id_from_asset_id{
			let render_config=&mut self.render_configs[render_config_id.get() as usize];
			if let (Some(index),Some(texture_id))=(index_option,render_config.texture){
				let resource_result=loader.load(index);
				let texture=match failure_mode{
					// if texture fails to load, use no texture
					LoadFailureMode::DefaultToNone=>match resource_result{
						Ok(texture)=>Some(texture),
						Err(e)=>{
							render_config.texture=None;
							println!("Error loading texture: {e}");
							None
						},
					},
					// loading failure is fatal
					LoadFailureMode::Fatal=>Some(resource_result?)
				};
				sorted_textures[texture_id.get() as usize]=texture;
			}
		}
		Ok(RenderConfigs::new(
			sorted_textures,
			self.render_configs,
		))
	}
}

pub struct MeshDeferredLoader<H>{
	mesh_id_from_asset_id:HashMap<H,MeshId>,
}
impl<H> MeshDeferredLoader<H>{
	pub fn new()->Self{
		Self{
			mesh_id_from_asset_id:HashMap::new(),
		}
	}
}

impl<H:core::hash::Hash+Eq> MeshDeferredLoader<H>{
	pub fn acquire_mesh_id(&mut self,index:H)->MeshId{
		let mesh_id=MeshId::new(self.mesh_id_from_asset_id.len() as u32);
		*self.mesh_id_from_asset_id.entry(index).or_insert(mesh_id)
	}
	pub fn into_indices(self)->impl Iterator<Item=H>{
		self.mesh_id_from_asset_id.into_keys()
	}
	pub fn into_meshes<'a,M:Clone,L:Loader<Resource=M,Index<'a>=H>+'a>(self,loader:&mut L,failure_mode:LoadFailureMode)->Result<Meshes<M>,L::Error>{
		let mut mesh_list=vec![None;self.mesh_id_from_asset_id.len()];
		for (index,mesh_id) in self.mesh_id_from_asset_id{
			let resource_result=loader.load(index);
			let mesh=match failure_mode{
				// if mesh fails to load, use no mesh
				LoadFailureMode::DefaultToNone=>match resource_result{
					Ok(mesh)=>Some(mesh),
					Err(e)=>{
						println!("Error loading mesh: {e}");
						None
					},
				},
				// loading failure is fatal
				LoadFailureMode::Fatal=>Some(resource_result?)
			};
			mesh_list[mesh_id.get() as usize]=mesh;
		}
		Ok(Meshes::new(mesh_list))
	}
}
