use std::io::Read;
use rbx_dom_weak::WeakDom;
use roblox_emulator::context::Context;
use strafesnet_common::map::CompleteMap;
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};

pub use error::RecoverableErrors;
pub use roblox_emulator::runner::Error as RunnerError;

mod rbx;
mod mesh;
mod error;
mod union;
pub mod loader;
pub mod primitives;

pub mod data{
	pub struct RobloxMeshBytes(Vec<u8>);
	impl RobloxMeshBytes{
		pub fn new(bytes:Vec<u8>)->Self{
			Self(bytes)
		}
		pub(crate) fn cursor(self)->std::io::Cursor<Vec<u8>>{
			std::io::Cursor::new(self.0)
		}
	}
}

pub struct Model{
	dom:WeakDom,
}
impl Model{
	fn new(dom:WeakDom)->Self{
		Self{dom}
	}
	pub fn to_snf(&self,failure_mode:LoadFailureMode)->Result<(CompleteMap,RecoverableErrors),LoadError>{
		to_snf(self,failure_mode)
	}
}
impl AsRef<WeakDom> for Model{
	fn as_ref(&self)->&WeakDom{
		&self.dom
	}
}

pub struct Place{
	context:Context,
}
impl Place{
	pub fn new(dom:WeakDom)->Result<Self,roblox_emulator::context::ServicesError>{
		let context=Context::from_place(dom)?;
		Ok(Self{
			context,
		})
	}
	pub fn run_scripts(&mut self)->Result<Vec<RunnerError>,RunnerError>{
		let Place{context}=self;
		let runner=roblox_emulator::runner::Runner::new()?;
		let scripts=context.scripts();
		let runnable=runner.runnable_context(context)?;
		let mut errors=Vec::new();
		for script in scripts{
			if let Err(e)=runnable.run_script(script){
				errors.push(e);
			}
		}
		Ok(errors)
	}
	pub fn to_snf(&self,failure_mode:LoadFailureMode)->Result<(CompleteMap,RecoverableErrors),LoadError>{
		to_snf(self,failure_mode)
	}
}
impl AsRef<WeakDom> for Place{
	fn as_ref(&self)->&WeakDom{
		self.context.as_ref()
	}
}
impl From<Model> for Place{
	fn from(model:Model)->Self{
		let context=Context::from_model(model.dom);
		Self{
			context,
		}
	}
}

#[derive(Debug)]
pub enum ReadError{
	RbxBinary(rbx_binary::DecodeError),
	RbxXml(rbx_xml::DecodeError),
	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 fn read<R:Read>(input:R)->Result<Model,ReadError>{
	let mut buf=std::io::BufReader::new(input);
	let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
	match peek.get(0..8){
		Some(b"<roblox!")=>rbx_binary::from_reader(buf).map(Model::new).map_err(ReadError::RbxBinary),
		Some(b"<roblox ")=>rbx_xml::from_reader_default(buf).map(Model::new).map_err(ReadError::RbxXml),
		_=>Err(ReadError::UnknownFileFormat),
	}
}

#[derive(Debug)]
pub enum LoadError{
	Texture(loader::TextureError),
	Mesh(loader::MeshError),
}
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{}
impl From<loader::TextureError> for LoadError{
	fn from(value:loader::TextureError)->Self{
		Self::Texture(value)
	}
}
impl From<loader::MeshError> for LoadError{
	fn from(value:loader::MeshError)->Self{
		Self::Mesh(value)
	}
}

fn to_snf(dom:impl AsRef<WeakDom>,failure_mode:LoadFailureMode)->Result<(CompleteMap,RecoverableErrors),LoadError>{
	let dom=dom.as_ref();

	let mut texture_deferred_loader=RenderConfigDeferredLoader::new();
	let mut mesh_deferred_loader=MeshDeferredLoader::new();

	let map_step1=rbx::convert(
		dom,
		&mut texture_deferred_loader,
		&mut mesh_deferred_loader,
	);

	let mut mesh_loader=loader::MeshLoader::new();
	let meshpart_meshes=mesh_deferred_loader.into_meshes(&mut mesh_loader,failure_mode).map_err(LoadError::Mesh)?;

	let map_step2=map_step1.add_meshpart_meshes_and_calculate_attributes(meshpart_meshes);

	let mut texture_loader=loader::TextureLoader::new();
	let render_configs=texture_deferred_loader.into_render_configs(&mut texture_loader,failure_mode).map_err(LoadError::Texture)?;

	Ok(map_step2.add_render_configs_and_textures(render_configs))
}
