use std::io::Read;
use rbxassetid::{RobloxAssetId,RobloxAssetIdParseErr};
use strafesnet_common::model::Mesh;
use strafesnet_deferred_loader::{loader::Loader,texture::Texture};

use crate::data::RobloxMeshBytes;
use crate::rbx::RobloxPartDescription;

// disallow non-static lifetimes
fn static_ustr(s:&'static str)->rbx_dom_weak::Ustr{
	rbx_dom_weak::ustr(s)
}

fn read_entire_file(path:impl AsRef<std::path::Path>)->Result<Vec<u8>,std::io::Error>{
	let mut file=std::fs::File::open(path)?;
	let mut data=Vec::new();
	file.read_to_end(&mut data)?;
	Ok(data)
}

#[derive(Debug)]
pub enum TextureError{
	Io(std::io::Error),
	RobloxAssetIdParse(RobloxAssetIdParseErr),
}
impl std::fmt::Display for TextureError{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
		write!(f,"{self:?}")
	}
}
impl std::error::Error for TextureError{}
impl From<std::io::Error> for TextureError{
	fn from(value:std::io::Error)->Self{
		Self::Io(value)
	}
}
impl From<RobloxAssetIdParseErr> for TextureError{
	fn from(value:RobloxAssetIdParseErr)->Self{
		Self::RobloxAssetIdParse(value)
	}
}

pub struct TextureLoader;
impl TextureLoader{
	pub fn new()->Self{
		Self
	}
}
impl Loader for TextureLoader{
	type Error=TextureError;
	type Index<'a>=&'a str;
	type Resource=Texture;
	fn load(&mut self,index:Self::Index<'_>)->Result<Self::Resource,Self::Error>{
		let RobloxAssetId(asset_id)=index.parse()?;
		let file_name=format!("textures/{}.dds",asset_id);
		let data=read_entire_file(file_name)?;
		Ok(Texture::ImageDDS(data))
	}
}

#[derive(Debug)]
pub enum MeshError{
	Io(std::io::Error),
	RobloxAssetIdParse(RobloxAssetIdParseErr),
	Mesh(crate::mesh::Error),
	Union(crate::union::Error),
	DecodeBinary(rbx_binary::DecodeError),
	OneChildPolicy,
	MissingInstance,
}
impl std::fmt::Display for MeshError{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
		write!(f,"{self:?}")
	}
}
impl std::error::Error for MeshError{}
impl From<std::io::Error> for MeshError{
	fn from(value:std::io::Error)->Self{
		Self::Io(value)
	}
}
impl From<RobloxAssetIdParseErr> for MeshError{
	fn from(value:RobloxAssetIdParseErr)->Self{
		Self::RobloxAssetIdParse(value)
	}
}
impl From<crate::mesh::Error> for MeshError{
	fn from(value:crate::mesh::Error)->Self{
		Self::Mesh(value)
	}
}
impl From<crate::union::Error> for MeshError{
	fn from(value:crate::union::Error)->Self{
		Self::Union(value)
	}
}
impl From<rbx_binary::DecodeError> for MeshError{
	fn from(value:rbx_binary::DecodeError)->Self{
		Self::DecodeBinary(value)
	}
}

#[derive(Hash,Eq,PartialEq)]
pub enum MeshType<'a>{
	FileMesh,
	Union{
		mesh_data:&'a [u8],
		physics_data:&'a [u8],
		size_float_bits:[u32;3],
		part_texture_description:RobloxPartDescription,
	},
}
#[derive(Hash,Eq,PartialEq)]
pub struct MeshIndex<'a>{
	mesh_type:MeshType<'a>,
	content:&'a str,
}
impl MeshIndex<'_>{
	pub fn file_mesh(content:&str)->MeshIndex<'_>{
		MeshIndex{
			mesh_type:MeshType::FileMesh,
			content,
		}
	}
	pub fn union<'a>(
		content:&'a str,
		mesh_data:&'a [u8],
		physics_data:&'a [u8],
		size:&rbx_dom_weak::types::Vector3,
		part_texture_description:RobloxPartDescription,
	)->MeshIndex<'a>{
		MeshIndex{
			mesh_type:MeshType::Union{
				mesh_data,
				physics_data,
				size_float_bits:[size.x.to_bits(),size.y.to_bits(),size.z.to_bits()],
				part_texture_description,
			},
			content,
		}
	}
}

#[derive(Clone)]
pub struct MeshWithSize{
	pub(crate) mesh:Mesh,
	pub(crate) size:strafesnet_common::integer::Planar64Vec3,
}

pub struct MeshLoader;
impl MeshLoader{
	pub fn new()->Self{
		Self
	}
}
impl Loader for MeshLoader{
	type Error=MeshError;
	type Index<'a>=MeshIndex<'a>;
	type Resource=MeshWithSize;
	fn load(&mut self,index:Self::Index<'_>)->Result<Self::Resource,Self::Error>{
		let mesh=match index.mesh_type{
			MeshType::FileMesh=>{
				let RobloxAssetId(asset_id)=index.content.parse()?;
				let file_name=format!("meshes/{}",asset_id);
				let data=read_entire_file(file_name)?;
				crate::mesh::convert(RobloxMeshBytes::new(data))?
			},
			MeshType::Union{mut physics_data,mut mesh_data,size_float_bits,part_texture_description}=>{
				// decode asset
				let size=glam::Vec3::from_array(size_float_bits.map(f32::from_bits));
				if !index.content.is_empty()&&(physics_data.is_empty()||mesh_data.is_empty()){
					let RobloxAssetId(asset_id)=index.content.parse()?;
					let file_name=format!("unions/{}",asset_id);
					let data=read_entire_file(file_name)?;
					let dom=rbx_binary::from_reader(std::io::Cursor::new(data))?;
					let &[referent]=dom.root().children()else{
						return Err(MeshError::OneChildPolicy);
					};
					let Some(instance)=dom.get_by_ref(referent)else{
						return Err(MeshError::MissingInstance);
					};
					if physics_data.is_empty(){
						if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get(&static_ustr("PhysicsData")){
							physics_data=data.as_ref();
						}
					}
					if mesh_data.is_empty(){
						if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get(&static_ustr("MeshData")){
							mesh_data=data.as_ref();
						}
					}
					crate::union::convert(physics_data,mesh_data,size,part_texture_description)?
				}else{
					crate::union::convert(physics_data,mesh_data,size,part_texture_description)?
				}
			},
		};
		Ok(mesh)
	}
}
