use std::io::Read;
use rbx_dom_weak::WeakDom;
use strafesnet_deferred_loader::deferred_loader::{LoadFailureMode,MeshDeferredLoader,RenderConfigDeferredLoader};

mod rbx;
mod mesh;
mod union;
pub mod loader;
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 into_place(self)->Place{
		let Self{mut dom}=self;
		let context=roblox_emulator::context::Context::from_mut(&mut dom);
		let services=context.convert_into_place();
		Place{dom,services}
	}
	pub fn to_snf(&self,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
		to_snf(self,failure_mode)
	}
}
impl AsRef<WeakDom> for Model{
	fn as_ref(&self)->&WeakDom{
		&self.dom
	}
}

pub struct Place{
	dom:WeakDom,
	services:roblox_emulator::context::Services,
}
impl Place{
	pub fn new(dom:WeakDom)->Option<Self>{
		let context=roblox_emulator::context::Context::from_ref(&dom);
		Some(Self{
			services:context.find_services()?,
			dom,
		})
	}
	pub fn run_scripts(&mut self){
		let Place{dom,services}=self;
		let runner=roblox_emulator::runner::Runner::new().unwrap();
		let context=roblox_emulator::context::Context::from_mut(dom);
		let scripts=context.scripts();
		let runnable=runner.runnable_context_with_services(context,services).unwrap();
		for script in scripts{
			if let Err(e)=runnable.run_script(script){
				println!("runner error: {e}");
			}
		}
	}
	pub fn to_snf(&self,failure_mode:LoadFailureMode)->Result<strafesnet_common::map::CompleteMap,LoadError>{
		to_snf(self,failure_mode)
	}
}
impl AsRef<WeakDom> for Place{
	fn as_ref(&self)->&WeakDom{
		&self.dom
	}
}

#[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[0..8]{
		b"<roblox!"=>rbx_binary::from_reader(buf).map(Model::new).map_err(ReadError::RbxBinary),
		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<strafesnet_common::map::CompleteMap,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)?;

	let map=map_step2.add_render_configs_and_textures(render_configs);

	Ok(map)
}
