use std::io::Read;

#[derive(Debug)]
pub enum ReadError{
	#[cfg(feature="roblox")]
	Roblox(strafesnet_rbx_loader::ReadError),
	#[cfg(feature="source")]
	Source(strafesnet_bsp_loader::ReadError),
	#[cfg(feature="snf")]
	StrafesNET(strafesnet_snf::Error),
	#[cfg(feature="snf")]
	StrafesNETMap(strafesnet_snf::map::Error),
	Io(std::io::Error),
	UnknownFileFormat,
}
impl std::fmt::Display for ReadError{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
		write!(f,"{self:?}")
	}
}
impl std::error::Error for ReadError{}

pub enum DataStructure{
	#[cfg(feature="roblox")]
	Roblox(strafesnet_rbx_loader::Model),
	#[cfg(feature="source")]
	Source(strafesnet_bsp_loader::Bsp),
	#[cfg(feature="snf")]
	StrafesNET(strafesnet_common::map::CompleteMap),
}

pub fn read<R:Read+std::io::Seek>(input:R)->Result<DataStructure,ReadError>{
	let mut buf=std::io::BufReader::new(input);
	let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
	match &peek[0..4]{
		#[cfg(feature="roblox")]
		b"<rob"=>Ok(DataStructure::Roblox(strafesnet_rbx_loader::read(buf).map_err(ReadError::Roblox)?)),
		#[cfg(feature="source")]
		b"VBSP"=>Ok(DataStructure::Source(strafesnet_bsp_loader::read(buf).map_err(ReadError::Source)?)),
		#[cfg(feature="snf")]
		b"SNFM"=>Ok(DataStructure::StrafesNET(
			strafesnet_snf::read_map(buf).map_err(ReadError::StrafesNET)?
			.into_complete_map().map_err(ReadError::StrafesNETMap)?
		)),
		_=>Err(ReadError::UnknownFileFormat),
	}
}

#[derive(Debug)]
pub enum LoadError{
	ReadError(ReadError),
	File(std::io::Error),
	Io(std::io::Error),
}
impl std::fmt::Display for LoadError{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
		write!(f,"{self:?}")
	}
}
impl std::error::Error for LoadError{}

pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<strafesnet_common::map::CompleteMap,LoadError>{
	//blocking because it's simpler...
	let file=std::fs::File::open(path).map_err(LoadError::File)?;
	match read(file).map_err(LoadError::ReadError)?{
		#[cfg(feature="snf")]
		DataStructure::StrafesNET(map)=>Ok(map),
		#[cfg(feature="roblox")]
		DataStructure::Roblox(model)=>{
			let mut place=model.into_place();
			place.run_scripts();

			let mut loader=strafesnet_deferred_loader::roblox_legacy();

			let (texture_loader,mesh_loader)=loader.get_inner_mut();

			let map_step1=strafesnet_rbx_loader::convert(
				&place,
				|name|texture_loader.acquire_render_config_id(name),
				|name|mesh_loader.acquire_mesh_id(name),
			);

			let meshpart_meshes=mesh_loader.load_meshes().map_err(LoadError::Io)?;

			let map_step2=map_step1.add_meshpart_meshes_and_calculate_attributes(
				meshpart_meshes.into_iter().map(|(mesh_id,loader_model)|
					(mesh_id,strafesnet_rbx_loader::data::RobloxMeshBytes::new(loader_model.get()))
				)
			);

			let (textures,render_configs)=loader.into_render_configs().map_err(LoadError::Io)?.consume();

			let map=map_step2.add_render_configs_and_textures(
				render_configs.into_iter(),
				textures.into_iter().map(|(texture_id,texture)|
					(texture_id,match texture{
						strafesnet_deferred_loader::texture::Texture::ImageDDS(data)=>data,
					})
				)
			);

			Ok(map)
		},
		#[cfg(feature="source")]
		DataStructure::Source(bsp)=>{
			let mut loader=strafesnet_deferred_loader::source_legacy();

			let (texture_loader,mesh_loader)=loader.get_inner_mut();

			let map_step1=strafesnet_bsp_loader::convert(
				&bsp,
				|name|texture_loader.acquire_render_config_id(name),
				|name|mesh_loader.acquire_mesh_id(name),
			);

			let prop_meshes=mesh_loader.load_meshes(bsp.as_ref());

			let map_step2=map_step1.add_prop_meshes(
				//the type conflagulator 9000
				prop_meshes.into_iter().map(|(mesh_id,loader_model)|
					(mesh_id,strafesnet_bsp_loader::data::ModelData{
						mdl:strafesnet_bsp_loader::data::MdlData::new(loader_model.mdl.get()),
						vtx:strafesnet_bsp_loader::data::VtxData::new(loader_model.vtx.get()),
						vvd:strafesnet_bsp_loader::data::VvdData::new(loader_model.vvd.get()),
					})
				),
				|name|texture_loader.acquire_render_config_id(name),
			);

			let (textures,render_configs)=loader.into_render_configs().map_err(LoadError::Io)?.consume();

			let map=map_step2.add_render_configs_and_textures(
				render_configs.into_iter(),
				textures.into_iter().map(|(texture_id,texture)|
					(texture_id,match texture{
						strafesnet_deferred_loader::texture::Texture::ImageDDS(data)=>data,
					})
				),
			);

			Ok(map)
		},
	}
}
