use {
	crate::{
		core::{Core, Object, ObjectImpl},
		plugin::{Plugin, PluginFeatures},
		prelude::*,
		util::Transition,
	},
	glib::{
		object::{BorrowedObject, ObjectSubclassIs},
		subclass::prelude::*,
		MainContext, SourceId,
	},
	std::{cell::RefCell, panic::catch_unwind},
};
pub trait PluginImpl: ObjectImpl + PluginImplExt {
	fn enable(&self, plugin: &Self::Type, error_handler: Transition) {
		self.parent_enable(plugin, error_handler)
	}
	fn disable(&self, plugin: &Self::Type) {
		self.parent_disable(plugin)
	}
}
pub trait PluginImplExt: ObjectSubclass {
	fn parent_class(&self) -> &ffi::WpPluginClass;
	fn parent_enable(&self, plugin: &Self::Type, error_handler: Transition);
	fn parent_disable(&self, plugin: &Self::Type);
}
impl<T: PluginImpl> PluginImplExt for T {
	fn parent_class(&self) -> &ffi::WpPluginClass {
		unsafe {
			let data = T::type_data();
			let parent_class = data.as_ref().parent_class() as *mut _;
			&*parent_class
		}
	}
	fn parent_enable(&self, plugin: &Self::Type, error_handler: Transition) {
		let parent = PluginImplExt::parent_class(self);
		let f = parent.enable.expect("No parent class implementation for \"enable\"");
		unsafe {
			f(
				plugin.unsafe_cast_ref::<Plugin>().to_glib_none().0,
				error_handler.to_glib_none().0,
			)
		}
	}
	fn parent_disable(&self, plugin: &Self::Type) {
		let parent = PluginImplExt::parent_class(self);
		let f = parent.disable.expect("No parent class implementation for \"disable\"");
		unsafe { f(plugin.unsafe_cast_ref::<Plugin>().to_glib_none().0) }
	}
}
unsafe impl<T: PluginImpl> IsSubclassable<T> for Plugin {
	fn class_init(class: &mut glib::Class<Self>) {
		Self::parent_class_init::<T>(class);
		unsafe extern "C" fn enable<T: PluginImpl>(plugin: *mut ffi::WpPlugin, error_handler: *mut ffi::WpTransition) {
			let this = &*(plugin as *mut T::Instance);
			let this = this.imp();
			let plugin: Borrowed<Plugin> = from_glib_borrow(plugin);
			this.enable(plugin.unsafe_cast_ref(), from_glib_none(error_handler))
		}
		unsafe extern "C" fn disable<T: PluginImpl>(plugin: *mut ffi::WpPlugin) {
			let this = &*(plugin as *mut T::Instance);
			let this = this.imp();
			let plugin: Borrowed<Plugin> = from_glib_borrow(plugin);
			this.disable(plugin.unsafe_cast_ref())
		}
		let klass = class.as_mut();
		klass.enable = Some(enable::<T>);
		klass.disable = Some(disable::<T>);
	}
}
pub trait AsyncPluginImpl: ObjectSubclass {
	type EnableFuture: Future<Output = Result<(), Error>>;
	fn enable(&self, plugin: Self::Type) -> Self::EnableFuture;
	fn disable(&self) {}
	fn register_source(&self, source: SourceId) {
		let _ = source;
	}
}
pub trait AsyncPluginExt: IsA<Plugin> {
	fn as_plugin(&self) -> &Plugin;
	fn plugin_core(&self) -> Core;
	fn plugin_context(&self) -> MainContext;
	fn spawn_local<F: Future<Output = ()> + 'static>(&self, f: F);
}
impl Plugin {
	pub fn core(&self) -> Core {
		self
			.upcast_ref::<Object>()
			.core()
			.expect("plugin requires an active Core")
	}
}
impl<T: IsA<Plugin> + ObjectSubclassIsExt> AsyncPluginExt for T
where
	<T as ObjectSubclassIs>::Subclass: AsyncPluginImpl,
{
	fn as_plugin(&self) -> &Plugin {
		self.upcast_ref()
	}
	fn plugin_core(&self) -> Core {
		self.as_plugin().core()
	}
	fn plugin_context(&self) -> MainContext {
		self.plugin_core().default_context()
	}
	fn spawn_local<F: Future<Output = ()> + 'static>(&self, f: F) {
		let source = self.plugin_context().spawn_local(f);
		self.imp().register_source(source.into_source_id().unwrap());
	}
}
impl<T: AsyncPluginImpl + ObjectImpl> PluginImpl for T
where
	<T as ObjectSubclass>::Type: AsyncPluginExt,
{
	fn enable(&self, this: &Self::Type, error_handler: Transition) {
		let plugin = this.clone();
		let enable = self.enable(plugin.clone());
		let enable_handle = this.plugin_context().spawn_local(async move {
			match enable.await {
				Ok(()) => plugin
					.as_plugin()
					.update_features(PluginFeatures::ENABLED, PluginFeatures::empty()),
				Err(e) => error_handler.return_error(e),
			}
		});
		self.register_source(enable_handle.into_source_id().unwrap());
		}
	fn disable(&self, _plugin: &Self::Type) {
		self.disable();
	}
}
#[derive(Debug)]
pub struct SourceHandles {
	context: MainContext,
	handles: Vec<SourceId>,
}
impl SourceHandles {
	pub fn new(context: MainContext) -> Self {
		Self {
			context,
			handles: Vec::new(),
		}
	}
	pub fn push(&mut self, source: SourceId) {
		self.handles.push(source);
	}
	pub fn clear(&mut self) {
		for source in self.handles.drain(..) {
			if let Some(source) = self.context.find_source_by_id(&source) {
				source.destroy();
			}
		}
	}
}
#[derive(Default, Debug)]
pub struct SourceHandlesCell(RefCell<Option<SourceHandles>>);
impl SourceHandlesCell {
	pub fn init(&self, context: MainContext) {
		*self.cell().borrow_mut() = Some(SourceHandles::new(context));
	}
	pub fn try_init(&self, context: MainContext) -> Result<(), MainContext> {
		match &mut *self.cell().borrow_mut() {
			&mut Some(..) => Err(context),
			opt @ None => {
				*opt = Some(SourceHandles::new(context));
				Ok(())
			},
		}
	}
	pub fn push(&self, source: SourceId) {
		self.borrow_mut(|handles| handles.push(source))
	}
	pub fn clear(&self) {
		let res = self.borrow_mut(|handles| handles.clear());
		self.cell().replace(None);
		res
	}
	#[inline]
	pub fn cell(&self) -> &RefCell<Option<SourceHandles>> {
		&self.0
	}
	pub fn borrow_mut<R, F: FnOnce(&mut SourceHandles) -> R>(&self, f: F) -> R {
		match *self.cell().borrow_mut() {
			Some(ref mut handles) => f(handles),
			None => panic!("SourceHandles cell uninitialized"),
		}
	}
}
pub trait SimplePlugin: ObjectSubclass {
	type Args;
	fn instance_ref(&self) -> BorrowedObject<Self::Type> {
		self.obj()
	}
	fn decode_args(args: Option<Variant>) -> Result<Self::Args, Error>;
	fn init_args(&self, args: Self::Args) {
		let _ = args;
		unimplemented!()
	}
	fn new_plugin(core: &Core, args: Self::Args) -> Self::Type
	where
		Self::Type: IsA<GObject>,
	{
		let res = GObject::with_mut_values(Self::Type::static_type(), &mut [
			("name", Self::NAME.to_value()),
			("core", core.to_value()),
		]);
		let res: Self::Type = unsafe { res.unsafe_cast() };
		res.imp().init_args(args);
		res
	}
}
glib::wrapper! {
	pub struct SimplePluginObject<T: SimplePlugin>(ObjectSubclass<T>) @extends Plugin, Object;
}
impl<T> Deref for SimplePluginObject<T>
where
	T: SimplePlugin + ObjectSubclass<Type = Self>,
{
	type Target = T;
	fn deref(&self) -> &Self::Target {
		self.imp()
	}
}
#[macro_export]
macro_rules! simple_plugin_subclass {
	(impl ObjectSubclass for $name:tt as $ty:ident { $($subclass:tt)* }) => {
		$crate::plugin::simple_plugin_subclass! {
			impl ObjectSubclass<$crate::plugin::Plugin> for $name as $ty { $($subclass)* }
		}
	};
	(impl ObjectSubclass<$parent:ty> for $name:tt as $ty:ident { $($subclass:tt)* }) => {
		#[$crate::lib::glib::object_subclass]
		impl $crate::lib::glib::subclass::types::ObjectSubclass for $ty {
			type Type = $crate::plugin::SimplePluginObject<Self>;
			type ParentType = $parent;
			const NAME: &'static str = $name;
			$($subclass)*
		}
		impl $crate::lib::glib::subclass::object::ObjectImpl for $ty { }
		impl $crate::core::ObjectImpl for $ty { }
	};
}
pub use simple_plugin_subclass;
pub trait ModuleExport {
	fn init(core: Core, args: Option<Variant>) -> Result<(), Error>;
}
pub struct ModuleWrapper<T>(PhantomData<T>);
impl<T: ModuleExport> ModuleExport for ModuleWrapper<T> {
	fn init(core: Core, args: Option<Variant>) -> Result<(), Error> {
		let res = catch_unwind(|| T::init(core, args));
		match res {
			Ok(res) => res,
			Err(panic) => Err({
				let op = LibraryErrorEnum::OperationFailed;
				if let Some(panic) = panic.downcast_ref::<String>() {
					Error::new(op, panic)
				} else if let Some(panic) = panic.downcast_ref::<&'static str>() {
					Error::new(op, *panic)
				} else {
					Error::new(op, "ModuleExport::init panicked")
				}
			}),
		}
	}
}
impl<T: SimplePlugin> ModuleExport for T
where
	T::Type: IsA<GObject> + IsA<Plugin>,
{
	fn init(core: Core, args: Option<Variant>) -> Result<(), Error> {
		let args = T::decode_args(args)?;
		let plugin = T::new_plugin(&core, args);
		plugin.register();
		Ok(())
	}
}
#[macro_export]
macro_rules! plugin_export {
	($desc:ty) => {
		$crate::plugin::plugin_export! { @nowrap $crate::plugin::ModuleWrapper::<$desc> }
	};
	(@nowrap $desc:ty) => {
		#[no_mangle]
		pub unsafe extern "C" fn wireplumber__module_init(
			core: std::ptr::NonNull<$crate::ffi::WpCore>,
			args: *mut $crate::lib::glib::ffi::GVariant,
			error: std::ptr::NonNull<*mut $crate::lib::glib::ffi::GError>,
		) -> glib::ffi::gboolean {
			use $crate::lib::glib::translate::{IntoGlib, IntoGlibPtr};
			let core = unsafe { glib::translate::from_glib_none(core.as_ptr()) };
			let args = unsafe { glib::translate::from_glib_none(args) };
			match <$desc as $crate::plugin::ModuleExport>::init(core, args) {
				Ok(()) => true.into_glib(),
				Err(e) => {
					*error.as_ptr() = e.into_glib_ptr();
					false.into_glib()
				},
			}
		}
	};
}
pub use plugin_export;