use std::{borrow::Cow, io::Read};

use strafesnet_common::model::Mesh;
use strafesnet_deferred_loader::{loader::Loader,texture::Texture};

use crate::{Bsp,Vpk};

#[derive(Debug)]
pub enum TextureError{
	Io(std::io::Error),
}
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)
	}
}

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

#[derive(Debug)]
pub enum MeshError{
	Io(std::io::Error),
	VMDL(vmdl::ModelError),
	VBSP(vbsp::BspError),
	MissingMdl(String),
	MissingVtx,
	MissingVvd,
}
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<vmdl::ModelError> for MeshError{
	fn from(value:vmdl::ModelError)->Self{
		Self::VMDL(value)
	}
}
impl From<vbsp::BspError> for MeshError{
	fn from(value:vbsp::BspError)->Self{
		Self::VBSP(value)
	}
}

#[derive(Clone,Copy)]
pub struct BspFinder<'bsp,'vpk>{
	pub bsp:&'bsp Bsp,
	pub vpks:&'vpk [Vpk],
}
impl<'bsp,'vpk> BspFinder<'bsp,'vpk>{
	pub fn find<'a>(&self,path:&str)->Result<Option<Cow<'a,[u8]>>,vbsp::BspError>
		where
			'bsp:'a,
			'vpk:'a,
	{
		// search bsp
		if let Some(data)=self.bsp.pack_get(path)?{
			return Ok(Some(Cow::Owned(data)));
		}

		//search each vpk
		for vpk in self.vpks{
			if let Some(vpk_entry)=vpk.tree_get(path){
				return Ok(Some(vpk_entry.get()?));
			}
		}

		Ok(None)
	}
}

pub struct ModelLoader<'bsp,'vpk>{
	finder:BspFinder<'bsp,'vpk>,
}
impl ModelLoader<'_,'_>{
	#[inline]
	pub const fn new<'bsp,'vpk>(
		finder:BspFinder<'bsp,'vpk>,
	)->ModelLoader<'bsp,'vpk>{
		ModelLoader{
			finder,
		}
	}
}
impl Loader for ModelLoader<'_,'_>{
	type Error=MeshError;
	type Index<'a>=&'a str where Self:'a;
	type Resource=vmdl::Model;
	fn load<'a>(&'a mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
		let mdl_path_lower=index.to_lowercase();
		//.mdl, .vvd, .dx90.vtx
		let path=std::path::PathBuf::from(mdl_path_lower.as_str());
		let mut vvd_path=path.clone();
		let mut vtx_path=path;
		vvd_path.set_extension("vvd");
		vtx_path.set_extension("dx90.vtx");
		// TODO: search more packs, possibly using an index of multiple packs
		let mdl=self.finder.find(mdl_path_lower.as_str())?.ok_or(MeshError::MissingMdl(mdl_path_lower))?;
		let vtx=self.finder.find(vtx_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVtx)?;
		let vvd=self.finder.find(vvd_path.as_os_str().to_str().unwrap())?.ok_or(MeshError::MissingVvd)?;
		Ok(vmdl::Model::from_parts(
			vmdl::mdl::Mdl::read(mdl.as_ref())?,
			vmdl::vtx::Vtx::read(vtx.as_ref())?,
			vmdl::vvd::Vvd::read(vvd.as_ref())?,
		))
	}
}

pub struct MeshLoader<'bsp,'vpk,'load,'str>{
	finder:BspFinder<'bsp,'vpk>,
	deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'str,str>>,
}
impl MeshLoader<'_,'_,'_,'_>{
	#[inline]
	pub const fn new<'bsp,'vpk,'load,'str>(
		finder:BspFinder<'bsp,'vpk>,
		deferred_loader:&'load mut strafesnet_deferred_loader::deferred_loader::RenderConfigDeferredLoader<Cow<'str,str>>,
	)->MeshLoader<'bsp,'vpk,'load,'str>{
		MeshLoader{
			finder,
			deferred_loader
		}
	}
}
impl Loader for MeshLoader<'_,'_,'_,'_>{
	type Error=MeshError;
	type Index<'a>=&'a str where Self:'a;
	type Resource=Mesh;
	fn load<'a>(&'a mut self,index:Self::Index<'a>)->Result<Self::Resource,Self::Error>{
		let model=ModelLoader::new(self.finder).load(index)?;
		let mesh=crate::mesh::convert_mesh(model,&mut self.deferred_loader);
		Ok(mesh)
	}
}
