//! Sound trigger routing and simulator sound event decoding.

#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::collapsible_if)] // Packet decoding and payload bounds remain distinct checks.
#![allow(clippy::unnecessary_wraps)] // Mapped constructors have fixed fallible signatures.

use crate::agent_manager::EventRegistry;
use crate::packet_catalog::{GeneratedPacket, PacketType};
use crate::{Error, GridClient, Simulator};
use libremetaverse_types::compat::{EventHandler, Subscription};
use libremetaverse_types::{SoundFlags, UUID, Vector3};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};

const MAX_SOUND_PACKET_BYTES: usize = 1024 * 1024;
fn mutex<T>(value: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
    value
        .lock()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}

#[derive(Clone)]
pub struct AttachedSoundEventArgs {
    sim: Simulator,
    sound_id: UUID,
    owner_id: UUID,
    object_id: UUID,
    gain: f32,
    flags: SoundFlags,
}
impl AttachedSoundEventArgs {
    pub fn new(
        sim: Simulator,
        sound_id: UUID,
        owner_id: UUID,
        object_id: UUID,
        gain: f32,
        flags: SoundFlags,
    ) -> Result<Self, Error> {
        Ok(Self {
            sim,
            sound_id,
            owner_id,
            object_id,
            gain,
            flags,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.sim.clone()
    }
    pub fn sound_id(&self) -> UUID {
        self.sound_id
    }
    pub fn owner_id(&self) -> UUID {
        self.owner_id
    }
    pub fn object_id(&self) -> UUID {
        self.object_id
    }
    pub fn gain(&self) -> f32 {
        self.gain
    }
    pub fn flags(&self) -> SoundFlags {
        self.flags
    }
}
#[derive(Clone)]
pub struct AttachedSoundGainChangeEventArgs {
    sim: Simulator,
    object_id: UUID,
    gain: f32,
}
impl AttachedSoundGainChangeEventArgs {
    pub fn new(sim: Simulator, object_id: UUID, gain: f32) -> Result<Self, Error> {
        Ok(Self {
            sim,
            object_id,
            gain,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.sim.clone()
    }
    pub fn object_id(&self) -> UUID {
        self.object_id
    }
    pub fn gain(&self) -> f32 {
        self.gain
    }
}
#[derive(Clone)]
pub struct PreloadSoundEventArgs {
    sim: Simulator,
    sound_id: UUID,
    owner_id: UUID,
    object_id: UUID,
}
impl PreloadSoundEventArgs {
    pub fn new(
        sim: Simulator,
        sound_id: UUID,
        owner_id: UUID,
        object_id: UUID,
    ) -> Result<Self, Error> {
        Ok(Self {
            sim,
            sound_id,
            owner_id,
            object_id,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.sim.clone()
    }
    pub fn sound_id(&self) -> UUID {
        self.sound_id
    }
    pub fn owner_id(&self) -> UUID {
        self.owner_id
    }
    pub fn object_id(&self) -> UUID {
        self.object_id
    }
}
#[derive(Clone)]
pub struct SoundTriggerEventArgs {
    sim: Simulator,
    sound_id: UUID,
    owner_id: UUID,
    object_id: UUID,
    parent_id: UUID,
    gain: f32,
    region_handle: u64,
    position: Vector3,
}
impl SoundTriggerEventArgs {
    pub fn new(
        sim: Simulator,
        sound_id: UUID,
        owner_id: UUID,
        object_id: UUID,
        parent_id: UUID,
        gain: f32,
        region_handle: u64,
        position: Vector3,
    ) -> Result<Self, Error> {
        Ok(Self {
            sim,
            sound_id,
            owner_id,
            object_id,
            parent_id,
            gain,
            region_handle,
            position,
        })
    }
    pub fn simulator(&self) -> Simulator {
        self.sim.clone()
    }
    pub fn sound_id(&self) -> UUID {
        self.sound_id
    }
    pub fn owner_id(&self) -> UUID {
        self.owner_id
    }
    pub fn object_id(&self) -> UUID {
        self.object_id
    }
    pub fn parent_id(&self) -> UUID {
        self.parent_id
    }
    pub fn gain(&self) -> f32 {
        self.gain
    }
    pub fn region_handle(&self) -> u64 {
        self.region_handle
    }
    pub fn position(&self) -> Vector3 {
        self.position
    }
}

#[derive(Default)]
struct SoundEvents {
    attached: EventRegistry<AttachedSoundEventArgs>,
    gain: EventRegistry<AttachedSoundGainChangeEventArgs>,
    preload: EventRegistry<PreloadSoundEventArgs>,
    trigger: EventRegistry<SoundTriggerEventArgs>,
}
pub(crate) struct SoundManagerInner {
    client: Arc<GridClient>,
    events: SoundEvents,
    raw_subscription: Mutex<Option<Subscription>>,
    disposed: AtomicBool,
}
#[derive(Clone)]
pub struct SoundManager {
    pub(crate) inner: Arc<SoundManagerInner>,
}
impl SoundManager {
    pub fn new(client: GridClient) -> Result<Self, Error> {
        Self::native_new(Arc::new(client))
    }
    pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
        if let Some(inner) = client.cached_sound_manager_inner() {
            return Ok(Self { inner });
        }
        let inner = Arc::new(SoundManagerInner {
            client: Arc::clone(&client),
            events: SoundEvents::default(),
            raw_subscription: Mutex::new(None),
            disposed: AtomicBool::new(false),
        });
        let weak = Arc::downgrade(&inner);
        *mutex(&inner.raw_subscription) = Some(client.network().subscribe_raw_packet(Arc::new(
            move |event| {
                if let Some(inner) = weak.upgrade() {
                    inner.handle(event);
                }
            },
        )));
        Ok(Self {
            inner: client.install_sound_manager_inner(inner),
        })
    }
    pub fn subscribe_attached_sound(
        &self,
        handler: EventHandler<AttachedSoundEventArgs>,
    ) -> Subscription {
        self.inner.events.attached.subscribe(handler)
    }
    pub fn subscribe_attached_sound_gain_change(
        &self,
        handler: EventHandler<AttachedSoundGainChangeEventArgs>,
    ) -> Subscription {
        self.inner.events.gain.subscribe(handler)
    }
    pub fn subscribe_preload_sound(
        &self,
        handler: EventHandler<PreloadSoundEventArgs>,
    ) -> Subscription {
        self.inner.events.preload.subscribe(handler)
    }
    pub fn subscribe_sound_trigger(
        &self,
        handler: EventHandler<SoundTriggerEventArgs>,
    ) -> Subscription {
        self.inner.events.trigger.subscribe(handler)
    }
    pub fn dispose(&self) -> Result<(), Error> {
        self.inner.disposed.store(true, Ordering::Release);
        mutex(&self.inner.raw_subscription).take();
        Ok(())
    }
    pub fn play_sound(&self, sound_id: UUID) -> Result<(), Error> {
        let mut client = (*self.inner.client).clone();
        let position = client.self_().sim_position();
        self.send_sound_trigger_with_uuid_vector3_single(sound_id, position, 1.0)
    }
    pub fn send_sound_trigger_with_uuid_vector3(
        &self,
        sound_id: UUID,
        position: Vector3,
    ) -> Result<(), Error> {
        self.send_sound_trigger_with_uuid_vector3_single(sound_id, position, 1.0)
    }
    pub fn send_sound_trigger_with_uuid_vector3_single(
        &self,
        sound_id: UUID,
        position: Vector3,
        gain: f32,
    ) -> Result<(), Error> {
        let sim = self
            .inner
            .client
            .network()
            .current_sim()
            .ok_or(Error::InvalidOperation)?;
        self.send_to_sim(sound_id, &sim, sim.handle, position, gain)
    }
    pub fn send_sound_trigger_with_uuid_simulator_vector3_single(
        &self,
        sound_id: UUID,
        sim: Simulator,
        position: Vector3,
        gain: f32,
    ) -> Result<(), Error> {
        self.send_to_sim(sound_id, &sim, sim.handle, position, gain)
    }
    pub fn send_sound_trigger_with_uuid_u_int64_vector3_single(
        &self,
        sound_id: UUID,
        handle: u64,
        position: Vector3,
        gain: f32,
    ) -> Result<(), Error> {
        let sim = self
            .inner
            .client
            .network()
            .current_sim()
            .ok_or(Error::InvalidOperation)?;
        self.send_to_sim(sound_id, &sim, handle, position, gain)
    }
    fn send_to_sim(
        &self,
        sound_id: UUID,
        sim: &Simulator,
        handle: u64,
        position: Vector3,
        gain: f32,
    ) -> Result<(), Error> {
        if self.inner.disposed.load(Ordering::Acquire) {
            return Err(Error::InvalidOperation);
        }
        if sound_id == UUID::zero()
            || !gain.is_finite()
            || !position.x.is_finite()
            || !position.y.is_finite()
            || !position.z.is_finite()
        {
            return Err(Error::Argument);
        }
        let mut packet = crate::packets::SoundTriggerPacket::new_with_constructor()?;
        packet.sound_data.sound_id = sound_id;
        packet.sound_data.object_id = UUID::zero();
        packet.sound_data.owner_id = UUID::zero();
        packet.sound_data.parent_id = UUID::zero();
        packet.sound_data.handle = handle;
        packet.sound_data.position = position;
        packet.sound_data.gain = gain.clamp(0.0, 1.0);
        let bytes = packet.to_bytes_with_method()?;
        sim.native_send_packet_data(
            bytes.clone(),
            i32::try_from(bytes.len()).map_err(|_| Error::Argument)?,
            PacketType::SoundTrigger,
            false,
        )
    }
}

impl SoundManagerInner {
    fn handle(&self, event: crate::network_manager::RawPacketReceivedEventArgs) {
        if self.disposed.load(Ordering::Acquire) || event.data.len() > MAX_SOUND_PACKET_BYTES {
            return;
        }
        let mut position = 0;
        match event.packet_type {
            PacketType::AttachedSound => {
                if let Ok(packet) =
                    crate::packets::AttachedSoundPacket::new_from_bytes(&event.data, &mut position)
                {
                    let value = packet.data_block;
                    self.events.attached.emit(AttachedSoundEventArgs {
                        sim: event.simulator,
                        sound_id: value.sound_id,
                        owner_id: value.owner_id,
                        object_id: value.object_id,
                        gain: value.gain,
                        flags: SoundFlags(value.flags),
                    });
                }
            }
            PacketType::AttachedSoundGainChange => {
                if let Ok(packet) = crate::packets::AttachedSoundGainChangePacket::new_from_bytes(
                    &event.data,
                    &mut position,
                ) {
                    self.events.gain.emit(AttachedSoundGainChangeEventArgs {
                        sim: event.simulator,
                        object_id: packet.data_block.object_id,
                        gain: packet.data_block.gain,
                    });
                }
            }
            PacketType::PreloadSound => {
                if let Ok(packet) =
                    crate::packets::PreloadSoundPacket::new_from_bytes(&event.data, &mut position)
                {
                    if packet.data_block.len() <= 65_535 {
                        for value in packet.data_block {
                            self.events.preload.emit(PreloadSoundEventArgs {
                                sim: event.simulator.clone(),
                                sound_id: value.sound_id,
                                owner_id: value.owner_id,
                                object_id: value.object_id,
                            });
                        }
                    }
                }
            }
            PacketType::SoundTrigger => {
                if let Ok(packet) =
                    crate::packets::SoundTriggerPacket::new_from_bytes(&event.data, &mut position)
                {
                    let value = packet.sound_data;
                    self.events.trigger.emit(SoundTriggerEventArgs {
                        sim: event.simulator,
                        sound_id: value.sound_id,
                        owner_id: value.owner_id,
                        object_id: value.object_id,
                        parent_id: value.parent_id,
                        gain: value.gain,
                        region_handle: value.handle,
                        position: value.position,
                    });
                }
            }
            _ => {}
        }
    }
}

#[cfg(test)]
#[allow(clippy::float_cmp)] // Sound fixtures use exactly representable values.
mod tests {
    use super::*;

    #[test]
    fn sound_trigger_rejects_zero_ids_and_non_finite_gain() {
        let client = GridClient::new().unwrap();
        let manager = SoundManager::new(client).unwrap();
        assert!(
            manager
                .send_sound_trigger_with_uuid_u_int64_vector3_single(
                    UUID::zero(),
                    0,
                    Vector3::default(),
                    1.0
                )
                .is_err()
        );
    }
    #[test]
    fn event_args_preserve_gain_and_queue_identity() {
        let client = GridClient::new().unwrap();
        let sim = Simulator::new(client, "127.0.0.1:13".parse().unwrap(), 0, None, None).unwrap();
        let id = UUID::random().unwrap();
        let value =
            AttachedSoundEventArgs::new(sim, id, UUID::zero(), UUID::zero(), 0.25, SoundFlags(2))
                .unwrap();
        assert_eq!(value.sound_id(), id);
        assert_eq!(value.gain(), 0.25);
        assert_eq!(value.flags(), SoundFlags(2));
    }

    #[test]
    fn attached_sound_packet_preserves_ids_gain_and_queue_flag() {
        let client = GridClient::new().unwrap();
        let sim = Simulator::new(
            client.clone(),
            "127.0.0.1:13".parse().unwrap(),
            0,
            None,
            None,
        )
        .unwrap();
        let manager = SoundManager::new(client).unwrap();
        let observed = Arc::new(Mutex::new(None));
        let captured = Arc::clone(&observed);
        let _subscription = manager.subscribe_attached_sound(Arc::new(move |event| {
            *mutex(&captured) = Some((
                event.sound_id(),
                event.owner_id(),
                event.object_id(),
                event.gain(),
                event.flags(),
            ));
        }));
        let sound_id = UUID::random().unwrap();
        let owner_id = UUID::random().unwrap();
        let object_id = UUID::random().unwrap();
        let mut packet = crate::packets::AttachedSoundPacket::new_with_constructor().unwrap();
        packet.data_block.sound_id = sound_id;
        packet.data_block.owner_id = owner_id;
        packet.data_block.object_id = object_id;
        packet.data_block.gain = 0.375;
        packet.data_block.flags = 2;

        manager
            .inner
            .handle(crate::network_manager::RawPacketReceivedEventArgs {
                packet_type: PacketType::AttachedSound,
                data: packet.to_bytes_with_method().unwrap(),
                simulator: sim,
            });

        assert_eq!(
            mutex(&observed).clone(),
            Some((sound_id, owner_id, object_id, 0.375, SoundFlags(2)))
        );
    }
}
