#[cfg(feature = "experimental")]
use crate::pw::PipewireObject;
#[cfg(feature = "libspa")]
use crate::spa::SpaPodParser;
use {
	crate::{
		prelude::*,
		pw::SpaPropertyKey,
		spa::{SpaIdValue, SpaPod, SpaPodBuilder, SpaPrimitive, SpaType, SpaValue},
	},
	libspa_sys::{spa_fraction, spa_pod, spa_rectangle},
};
impl SpaPod {
	pub unsafe fn with_pod_unchecked(bytes: &[u8]) -> Self {
		assert!(bytes.len() >= mem::size_of::<spa_pod>());
		let pod = bytes.as_ptr() as *const spa_pod;
		assert_eq!(pod as usize % mem::align_of::<spa_pod>(), 0);
		assert!(bytes.len() >= (*pod).size as usize);
		Self::new_wrap_raw_const(pod)
	}
	pub unsafe fn with_pod_mut_unchecked(bytes: &mut [u8]) -> Self {
		assert!(bytes.len() >= mem::size_of::<spa_pod>());
		let pod = bytes.as_ptr() as *mut spa_pod;
		assert_eq!(pod as usize % mem::align_of::<spa_pod>(), 0);
		assert!(bytes.len() >= (*pod).size as usize);
		Self::new_wrap_raw_mut(pod)
	}
	pub fn with_copy(pod: &SpaPod) -> Self {
		pod.copy().unwrap()
	}
	pub fn with_pod(bytes: &[u8]) -> Self {
		unsafe { Self::with_copy(&Self::with_pod_unchecked(bytes)) }
	}
	#[cfg(feature = "libspa")]
	fn parse_<R, F: FnOnce(&SpaPodParser, Option<&str>) -> R>(&self, f: F) -> Result<R, Error> {
		let (parser, id_name) = match () {
			_ if self.is_object() => Ok(SpaPodParser::new_object(self)),
			_ if self.is_struct() => Ok((SpaPodParser::new_struct(self), None)),
			_ => Err(Error::new(
				LibraryErrorEnum::InvalidArgument,
				&format!("unsupported SPA type {:?}", self.spa_type()),
			)),
		}?;
		let res = f(&parser, id_name);
		parser.end();
		Ok(res)
	}
	#[cfg(feature = "libspa")]
	pub(crate) fn parse_struct<R, F: FnOnce(&SpaPodParser) -> R>(&self, f: F) -> R {
		self.parse_(|parser, _| f(parser)).unwrap()
	}
	#[cfg(feature = "libspa")]
	pub(crate) fn parse_object<R, F: FnOnce(&SpaPodParser, Option<&str>) -> R>(&self, f: F) -> R {
		self.parse_(|parser, id_name| f(parser, id_name)).unwrap()
	}
	pub unsafe fn as_bytes(&self) -> &[u8] {
		let pod = self.spa_pod_raw();
		slice::from_raw_parts(pod as *const _ as *const u8, (*pod).size as usize)
	}
	pub fn to_bytes(&self) -> Vec<u8> {
		unsafe { self.as_bytes().into() }
	}
	#[doc(alias = "wp_spa_pod_new_wrap")]
	pub unsafe fn new_wrap_raw_mut(pod: *mut spa_pod) -> SpaPod {
		from_glib_full(ffi::wp_spa_pod_new_wrap(pod))
	}
	#[doc(alias = "wp_spa_pod_new_wrap_const")]
	pub unsafe fn new_wrap_raw_const(pod: *const spa_pod) -> SpaPod {
		from_glib_full(ffi::wp_spa_pod_new_wrap_const(pod))
	}
	#[doc(alias = "wp_spa_pod_new_bytes")]
	pub fn new_bytes(value: &[u8]) -> SpaPod {
		unsafe { from_glib_full(ffi::wp_spa_pod_new_bytes(value.as_ptr() as *const _, value.len() as _)) }
	}
	#[doc(alias = "wp_spa_pod_new_pointer")]
	pub fn new_pointer(type_name: &str, value: gconstpointer) -> SpaPod {
		unsafe { from_glib_full(ffi::wp_spa_pod_new_pointer(type_name.to_glib_none().0, value)) }
	}
	#[doc(alias = "wp_spa_pod_get_spa_type")]
	#[doc(alias = "get_spa_type")]
	pub fn spa_type(&self) -> Option<SpaType> {
		unsafe { from_glib(ffi::wp_spa_pod_get_spa_type(self.to_glib_none().0)) }
	}
	#[doc(alias = "wp_spa_pod_get_bytes")]
	#[doc(alias = "get_bytes")]
	pub fn bytes(&self) -> Option<&[u8]> {
		let mut value = ptr::null();
		let mut len = 0;
		unsafe {
			if from_glib(ffi::wp_spa_pod_get_bytes(self.to_glib_none().0, &mut value, &mut len)) {
				Some(slice::from_raw_parts(value as *const _, len as usize))
			} else {
				None
			}
		}
	}
	#[doc(alias = "wp_spa_pod_get_choice_type")]
	#[doc(alias = "get_choice_type")]
	pub fn choice_type(&self) -> Option<SpaIdValue> {
		unsafe { from_glib(ffi::wp_spa_pod_get_choice_type(self.to_glib_none().0)) }
	}
	#[doc(alias = "wp_spa_pod_get_pointer")]
	#[doc(alias = "get_pointer")]
	pub fn pointer(&self) -> Option<gconstpointer> {
		let mut res = ptr::null();
		unsafe {
			if from_glib(ffi::wp_spa_pod_get_pointer(self.to_glib_none().0, &mut res)) {
				Some(res)
			} else {
				None
			}
		}
	}
	#[doc(alias = "wp_spa_pod_set_pointer")]
	pub fn set_pointer(&self, type_name: &str, value: gconstpointer) -> bool {
		unsafe {
			from_glib(ffi::wp_spa_pod_set_pointer(
				self.to_glib_none().0,
				type_name.to_glib_none().0,
				value,
			))
		}
	}
	pub fn iterator(&self) -> IntoValueIterator<SpaPod> {
		IntoValueIterator::with_inner(self.new_iterator().unwrap())
	}
	pub fn array_pointers(&self) -> IntoValueIterator<Pointer> {
		IntoValueIterator::with_inner(self.new_iterator().unwrap())
	}
	pub fn array_iterator<T: SpaPrimitive>(&self) -> impl Iterator<Item = T> {
		self.array_pointers().into_iter().map(|p| unsafe { *(p as *const T) })
	}
	#[doc(alias = "wp_spa_pod_get_spa_pod")]
	#[doc(alias = "get_spa_pod")]
	pub fn spa_pod_raw(&self) -> &spa_pod {
		unsafe { &*ffi::wp_spa_pod_get_spa_pod(self.to_glib_none().0) }
	}
	pub fn spa_rectangle(&self) -> Option<spa_rectangle> {
		self.rectangle().map(|(width, height)| spa_rectangle { width, height })
	}
	pub fn spa_fraction(&self) -> Option<spa_fraction> {
		self.fraction().map(|(num, denom)| spa_fraction { num, denom })
	}
	#[cfg(feature = "experimental")]
	#[cfg_attr(docsrs, doc(cfg(feature = "experimental")))]
	pub fn struct_fields(&self, length_prefix: bool) -> crate::Result<std::vec::IntoIter<(String, SpaPod)>> {
		let mut params = self.iterator().into_iter();
		let length: Option<i32> = if length_prefix {
			Some(
				params
					.next()
					.ok_or_else(|| {
						Error::new(
							LibraryErrorEnum::InvalidArgument,
							&format!("pod struct {self:?} is missing expected length prefix"),
						)
					})
					.and_then(|pod| {
						(&pod).try_into().map_err(|e| {
							Error::new(
								LibraryErrorEnum::InvalidArgument,
								&format!("pod struct {self:?} length could not be parsed from {pod:?}: {e:?}"),
							)
						})
					})?,
			)
		} else {
			None
		};
		let length = match length {
			Some(len) if len < 0 =>
				return Err(Error::new(
					LibraryErrorEnum::InvalidArgument,
					&format!("pod struct {self:?} has invalid length {len}"),
				)),
			Some(len) => Some(len as usize),
			None => None,
		};
		let mut values = Vec::with_capacity(length.unwrap_or_default());
		while let Some(key) = params.next() {
			if let Some(length) = length {
				if values.len() >= length {
					return Err(Error::new(
						LibraryErrorEnum::InvalidArgument,
						&format!("too many entries in pod struct {self:?}: {values:?}"),
					))
				}
			}
			let key: String = (&key).try_into().map_err(|e| {
				Error::new(
					LibraryErrorEnum::InvalidArgument,
					&format!("key {key:?} was not a string: {e:?}"),
				)
			})?;
			let value = match params.next() {
				Some(v) => v,
				None =>
					return Err(Error::new(
						LibraryErrorEnum::InvalidArgument,
						&format!("unexpected key {key:?} due to uneven amount of params on {self:?}"),
					)),
			};
			values.push((key, value));
		}
		Ok(values.into_iter())
	}
	pub fn spa_properties(&self) -> impl Iterator<Item = (Result<SpaIdValue, ffi::WpSpaType>, SpaPod)> {
		let type_ = self.spa_type();
		let values = type_.and_then(|ty| ty.values_table());
		self
			.iterator()
			.into_iter()
			.map(move |pod| pod.property().unwrap())
			.map(move |(key_name, pod)| {
				(
					SpaIdValue::value_or_name(
						&type_,
						&key_name,
						values.and_then(|values| values.find_value_from_short_name(&key_name)),
					),
					pod,
				)
			})
	}
	pub fn find_spa_property<K: SpaPropertyKey>(&self, key: &K) -> Option<SpaPod> {
		let values = self.spa_type().and_then(|ty| ty.values_table());
		let find_id = match key.spa_property_key_with_table(values) {
			Ok(id) => id,
			Err(e) => {
				wp_warning!("unknown spa key {key:?} for {self:?}: {e:?}");
				return None
			},
		};
		self
			.spa_properties()
			.find(|&(id, ..)| SpaIdValue::result_number(id) == find_id)
			.map(|(_, pod)| pod)
	}
	pub fn spa_property<T, K: SpaPropertyKey>(&self, key: &K) -> Option<T>
	where
		for<'a> &'a SpaPod: TryInto<T>,
		for<'a> <&'a SpaPod as TryInto<T>>::Error: Debug,
	{
		self
			.find_spa_property(key)
			.and_then(|pod| match TryInto::try_into(&pod) {
				Ok(v) => Some(v),
				Err(e) => {
					wp_warning!("failed to convert spa key {key:?} for {self:?}: {e:?}");
					None
				},
			})
	}
	pub fn set_spa_property<K: SpaPropertyKey>(&self, key: &K, value: &SpaPod) -> Option<SpaPod> {
		let pod = match self.find_spa_property(key) {
			Some(pod) => pod,
			None => todo!(),
		};
		if pod.set_pod(value) {
			Some(pod)
		} else {
			wp_warning!("failed to set spa key {key:?} of type {pod:?} to {value:?}");
			None
		}
	}
	#[cfg(feature = "experimental")]
	#[cfg_attr(docsrs, doc(cfg(feature = "experimental")))]
	pub fn apply<O: IsA<PipewireObject>>(self, obj: &O) -> crate::Result<()> {
		if !self.is_object() {
			return Err(Error::new(
				LibraryErrorEnum::InvalidArgument,
				&format!("failed to apply spa pod to {obj:?}: {self:?} is not an object"),
			))
		}
		let type_ = self.spa_type().unwrap();
		let name = match type_.number() {
			libspa_sys::SPA_TYPE_OBJECT_Props => "Props",
			libspa_sys::SPA_TYPE_OBJECT_ParamRoute => "Route",
			_ =>
				return Err(Error::new(
					LibraryErrorEnum::InvalidArgument,
					&format!("could not apply unknown spa type {type_:?} to {obj:?}"),
				)),
		};
		let flags = Default::default();
		if obj.set_param(name, flags, self) {
			Ok(())
		} else {
			Err(Error::new(
				LibraryErrorEnum::InvalidArgument,
				&format!("failed to apply param {name} to {obj:?}"),
			))
		}
	}
}
impl<T: SpaValue> FromIterator<T> for SpaPod {
	fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
		SpaPodBuilder::from_iter(iter).end().unwrap()
	}
}