use std::io::Read;

#[cfg(any(feature="roblox",feature="source"))]
use strafesnet_deferred_loader::deferred_loader::LoadFailureMode;

#[expect(dead_code)]
#[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),
	#[cfg(feature="snf")]
	StrafesNETBot(strafesnet_snf::bot::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 ReadFormat{
	#[cfg(feature="roblox")]
	Roblox(strafesnet_rbx_loader::Model),
	#[cfg(feature="source")]
	Source(strafesnet_bsp_loader::Bsp),
	#[cfg(feature="snf")]
	SNFM(strafesnet_common::map::CompleteMap),
	#[cfg(feature="snf")]
	SNFB(strafesnet_snf::bot::Segment),
}

pub fn read<R:Read+std::io::Seek>(input:R)->Result<ReadFormat,ReadError>{
	let mut buf=std::io::BufReader::new(input);
	let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?[0..4].to_owned();
	// reading the entire file is way faster than round tripping the disk constantly
	let mut entire_file=Vec::new();
	buf.read_to_end(&mut entire_file).map_err(ReadError::Io)?;
	let cursor=std::io::Cursor::new(entire_file);
	match peek.as_slice(){
		#[cfg(feature="roblox")]
		b"<rob"=>Ok(ReadFormat::Roblox(strafesnet_rbx_loader::read(cursor).map_err(ReadError::Roblox)?)),
		#[cfg(feature="source")]
		b"VBSP"=>Ok(ReadFormat::Source(strafesnet_bsp_loader::read(cursor).map_err(ReadError::Source)?)),
		#[cfg(feature="snf")]
		b"SNFM"=>Ok(ReadFormat::SNFM(
			strafesnet_snf::read_map(cursor).map_err(ReadError::StrafesNET)?
			.into_complete_map().map_err(ReadError::StrafesNETMap)?
		)),
		#[cfg(feature="snf")]
		b"SNFB"=>Ok(ReadFormat::SNFB(
			strafesnet_snf::read_bot(cursor).map_err(ReadError::StrafesNET)?
			.read_all().map_err(ReadError::StrafesNETBot)?
		)),
		_=>Err(ReadError::UnknownFileFormat),
	}
}

#[expect(dead_code)]
#[derive(Debug)]
pub enum LoadError{
	ReadError(ReadError),
	File(std::io::Error),
	#[cfg(feature="roblox")]
	LoadRoblox(strafesnet_rbx_loader::LoadError),
	#[cfg(feature="source")]
	LoadSource(strafesnet_bsp_loader::LoadError),
}
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 enum LoadFormat{
	#[cfg(any(feature="snf",feature="roblox",feature="source"))]
	Map(strafesnet_common::map::CompleteMap),
	#[cfg(feature="snf")]
	Bot(strafesnet_snf::bot::Segment),
}

pub fn load<P:AsRef<std::path::Path>>(path:P)->Result<LoadFormat,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")]
		ReadFormat::SNFB(bot)=>Ok(LoadFormat::Bot(bot)),
		#[cfg(feature="snf")]
		ReadFormat::SNFM(map)=>Ok(LoadFormat::Map(map)),
		#[cfg(feature="roblox")]
		ReadFormat::Roblox(model)=>{
			let mut place=strafesnet_rbx_loader::Place::from(model);
			let script_errors=place.run_scripts().unwrap();
			for error in script_errors{
				println!("Script error: {error}");
			}
			let (map,errors)=place.to_snf(LoadFailureMode::DefaultToNone).map_err(LoadError::LoadRoblox)?;
			if errors.count()!=0{
				print!("Errors encountered while loading the map:\n{}",errors);
			}
			Ok(LoadFormat::Map(map))
		},
		#[cfg(feature="source")]
		ReadFormat::Source(bsp)=>Ok(LoadFormat::Map(
			bsp.to_snf(LoadFailureMode::DefaultToNone,&[]).map_err(LoadError::LoadSource)?
		)),
	}
}
