/*!
Types related to spatial listeners.

To create a listener, use [`AudioManager::add_listener`](crate::AudioManager::add_listener).

For more information, see the documentation on [spatial mixer tracks](crate::track#spatial-tracks).
*/

mod handle;

use atomic_arena::Key;
pub use handle::*;

use std::sync::{
	Arc,
	atomic::{AtomicBool, Ordering},
};

use glam::{Quat, Vec3};

use crate::{
	Parameter, Value,
	command::{ValueChangeCommand, read_commands_into_parameters},
	command_writers_and_readers,
	info::Info,
};

/// A unique identifier for a listener.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ListenerId(pub(crate) Key);

pub(crate) struct Listener {
	pub shared: Arc<ListenerShared>,
	pub position: Parameter<Vec3>,
	pub orientation: Parameter<Quat>,
	pub command_readers: CommandReaders,
}

impl Listener {
	pub fn new(
		id: ListenerId,
		position: Value<Vec3>,
		orientation: Value<Quat>,
	) -> (Self, ListenerHandle) {
		let shared = Arc::new(ListenerShared::new());
		let (command_writers, command_readers) = command_writers_and_readers();
		(
			Self {
				shared: shared.clone(),
				position: Parameter::new(position, Vec3::ZERO),
				orientation: Parameter::new(orientation, Quat::IDENTITY),
				command_readers,
			},
			ListenerHandle {
				id,
				shared,
				command_writers,
			},
		)
	}

	pub fn on_start_processing(&mut self) {
		read_commands_into_parameters!(self, position, orientation);
	}

	pub(crate) fn update(&mut self, dt: f64, info: &Info) {
		self.position.update(dt, info);
		self.orientation.update(dt, info);
	}
}

impl Default for Listener {
	fn default() -> Self {
		let (_, command_readers) = command_writers_and_readers();
		Self {
			shared: Arc::new(ListenerShared::new()),
			position: Parameter::new(Value::Fixed(Vec3::ZERO), Vec3::ZERO),
			orientation: Parameter::new(Value::Fixed(Quat::IDENTITY), Quat::IDENTITY),
			command_readers,
		}
	}
}

#[derive(Debug)]
pub(crate) struct ListenerShared {
	removed: AtomicBool,
}

impl ListenerShared {
	pub fn new() -> Self {
		Self {
			removed: AtomicBool::new(false),
		}
	}

	#[must_use]
	pub fn is_marked_for_removal(&self) -> bool {
		self.removed.load(Ordering::SeqCst)
	}

	pub fn mark_for_removal(&self) {
		self.removed.store(true, Ordering::SeqCst);
	}
}

command_writers_and_readers! {
	set_position: ValueChangeCommand<Vec3>,
	set_orientation: ValueChangeCommand<Quat>,
}
