use std::error::Error;
use std::fmt::{Debug, Display, Formatter};

#[derive(Debug)]
pub enum LoaderError {
	#[cfg(feature = "json_loader")]
	Json(serde_json::Error),
	#[cfg(feature = "toml_loader")]
	Toml(toml::de::Error),
	Custom(String),
}

impl Display for LoaderError {
	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
		match self {
			Self::Custom(message) => Display::fmt(message, f),
			#[cfg(feature = "json_loader")]
			Self::Json(err) => Display::fmt(err, f),
			#[cfg(feature = "toml_loader")]
			Self::Toml(err) => Display::fmt(err, f),
		}
	}
}

impl Error for LoaderError {}

#[cfg(feature = "json_loader")]
mod json_loader {
	use bevy_asset::io::Reader;
	use bevy_asset::{AssetLoader, LoadContext};

	use crate::definitions::AnimationSet;
	use crate::loader::LoaderError;

	#[derive(bevy_reflect::TypePath)]
	pub struct AnimationLoader;
	impl AssetLoader for AnimationLoader {
		type Asset = AnimationSet;
		type Settings = ();
		type Error = LoaderError;

		async fn load(
			&self,
			reader: &mut dyn Reader,
			_settings: &Self::Settings,
			_load_context: &mut LoadContext<'_>,
		) -> Result<Self::Asset, Self::Error> {
			let mut bytes = Vec::new();
			reader
				.read_to_end(&mut bytes)
				.await
				.expect("Failed to read all bytes");

			serde_json::from_slice(bytes.as_slice()).map_err(LoaderError::Json)
		}

		fn extensions(&self) -> &[&str] {
			static EXTENSIONS: &[&str] = &["anim.json"];
			EXTENSIONS
		}
	}
}

#[cfg(feature = "toml_loader")]
mod toml_loader {
	use bevy_asset::io::Reader;
	use bevy_asset::{AssetLoader, AsyncReadExt, LoadContext};

	use crate::definitions::AnimationSet;
	use crate::loader::LoaderError;

	#[derive(bevy_reflect::TypePath)]
	pub struct AnimationLoader;
	impl AssetLoader for AnimationLoader {
		type Asset = AnimationSet;
		type Settings = ();
		type Error = LoaderError;

		async fn load(
			&self,
			reader: &mut dyn Reader,
			_settings: &Self::Settings,
			_load_context: &mut LoadContext<'_>,
		) -> Result<Self::Asset, Self::Error> {
			let mut bytes = String::new();
			reader
				.read_to_string(&mut bytes)
				.await
				.expect("Failed to read all bytes");

			toml::from_str(bytes.as_str()).map_err(LoaderError::Toml)
		}

		fn extensions(&self) -> &[&str] {
			static EXTENSIONS: &[&str] = &["anim.toml"];
			EXTENSIONS
		}
	}
}

mod _plugin {
	use bevy_app::{App, Plugin};
	use bevy_asset::AssetApp;

	pub struct AnimationLoadersPlugin;
	impl Plugin for AnimationLoadersPlugin {
		fn build(&self, app: &mut App) {
			#[cfg(any(feature = "json_loader", feature = "toml_loader"))]
			app.init_asset::<crate::definitions::AnimationSet>();

			#[cfg(feature = "json_loader")]
			app.register_asset_loader(super::json_loader::AnimationLoader);
			#[cfg(feature = "toml_loader")]
			app.register_asset_loader(super::toml_loader::AnimationLoader);
		}
	}
}

pub use _plugin::AnimationLoadersPlugin;
