use comfy::{error, texture_id, EngineContext, HashSet, Lazy, Mutex, TextureHandle};
use std::{fmt::Debug, fs, io, path::PathBuf, sync::Arc};

static ASSETS_LOADED: Lazy<Arc<Mutex<HashSet<String>>>> =
	Lazy::new(|| Arc::new(Mutex::new(HashSet::new())));

#[derive(Debug)]
struct FurnitureAsset {
	folder: String,
	name: String
}

#[derive(Debug)]
struct FurnitureTextureHandles {
	human: Option<TextureHandle>,
	magnet: Option<TextureHandle>,
	elec: Option<TextureHandle>
}

impl FurnitureAsset {
	fn asset_path(&self) -> String {
		format!("{}/{}.png", self.folder, self.name)
	}

	fn asset_path_magnet(&self) -> String {
		format!("{}/metal/{}.png", self.folder, self.name)
	}

	fn asset_path_elec(&self) -> String {
		format!("{}/elec/{}.png", self.folder, self.name)
	}

	fn load_asset_path(
		&self,
		path: String,
		ctx: &mut EngineContext<'_>
	) -> Option<TextureHandle> {
		let mut loaded = ASSETS_LOADED.lock();
		if loaded.contains(&path) {
			return Some(texture_id(&path));
		}

		let mut asset_path = PathBuf::from(format!("assets/furniture/{path}"));
		if !asset_path.exists() {
			asset_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(asset_path);
		}

		let bytes = match fs::read(asset_path) {
			Ok(bytes) => bytes,
			Err(err) if err.kind() == io::ErrorKind::NotFound => return None,
			Err(err) => {
				error!("Failed to load asset {path:?}: {err}");
				return None;
			}
		};
		ctx.load_texture_from_bytes(&path, &bytes);
		let handle = texture_id(&path);
		loaded.insert(path);
		Some(handle)
	}

	/// Attempt to load the assets. Silently ignore missing assets.
	fn load_assets(&self, ctx: &mut EngineContext<'_>) -> FurnitureTextureHandles {
		FurnitureTextureHandles {
			human: self.load_asset_path(self.asset_path(), ctx),
			magnet: self.load_asset_path(self.asset_path_magnet(), ctx),
			elec: self.load_asset_path(self.asset_path_elec(), ctx)
		}
	}
}

pub struct Furniture {
	asset: FurnitureAsset,
	handles: FurnitureTextureHandles,
	on: Box<dyn Fn() -> bool>
}

impl Debug for Furniture {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		f.debug_struct("Furniture")
			.field("asset", &self.asset)
			.field("handles", &self.handles)
			.finish_non_exhaustive()
	}
}

impl Furniture {
	pub fn new<F: Into<String>, N: Into<String>>(
		folder: F,
		name: N,
		ctx: &mut EngineContext<'_>
	) -> Self {
		let asset = FurnitureAsset {
			folder: folder.into(),
			name: name.into()
		};
		let handles = asset.load_assets(ctx);
		Self {
			asset,
			handles,
			on: Box::new(|| false)
		}
	}

	pub fn get_human_texture_handle(&self) -> Option<TextureHandle> {
		self.handles.human
	}

	pub fn get_elec_texture_handle(&self) -> Option<TextureHandle> {
		self.handles.elec
	}

	pub fn get_magnet_texture_handle(&self) -> Option<TextureHandle> {
		self.handles.magnet
	}

	pub fn is_on(&self) -> bool {
		(self.on)()
	}
}
