//! Friend presence, rights, offers, calling cards, and map tracking.

#![allow(clippy::missing_errors_doc)]
#![allow(clippy::must_use_candidate)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::inherent_to_string)] // Fixed mapped CLR ToString method.

use crate::agent_manager::EventRegistry;
use crate::network_manager::RawPacketReceivedEventArgs;
use crate::packet_catalog::{GeneratedPacket, PacketType};
use crate::{Error, FriendRights, GridClient, InstantMessageDialog};
use libremetaverse_structured_data::{OSD, OSDFormat, OSDParser};
use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription};
use libremetaverse_types::{FolderType, UUID, Vector3};
use std::collections::{BTreeMap, HashMap};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};

const MAX_FRIENDS: usize = 100_000;

fn mutex<T>(value: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
    value
        .lock()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn read<T>(value: &RwLock<T>) -> std::sync::RwLockReadGuard<'_, T> {
    value
        .read()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn write<T>(value: &RwLock<T>) -> std::sync::RwLockWriteGuard<'_, T> {
    value
        .write()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn wire_string(value: &[u8]) -> String {
    String::from_utf8_lossy(value.split(|b| *b == 0).next().unwrap_or_default()).into_owned()
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FriendInfo {
    id: UUID,
    name: String,
    online: bool,
    their_rights: FriendRights,
    my_rights: FriendRights,
}
impl FriendInfo {
    pub(crate) fn native_new(
        id: UUID,
        their_rights: FriendRights,
        my_rights: FriendRights,
    ) -> Self {
        Self {
            id,
            name: String::new(),
            online: false,
            their_rights,
            my_rights,
        }
    }
    pub fn uuid(&self) -> UUID {
        self.id
    }
    pub fn name(&self) -> String {
        self.name.clone()
    }
    pub fn set_name(&mut self, value: String) {
        self.name = value;
    }
    pub fn is_online(&self) -> bool {
        self.online
    }
    pub fn set_is_online(&mut self, value: bool) {
        self.online = value;
    }
    pub fn their_friend_rights(&self) -> FriendRights {
        self.their_rights
    }
    pub fn set_their_friend_rights(&mut self, value: FriendRights) {
        self.their_rights = value;
    }
    pub fn my_friend_rights(&self) -> FriendRights {
        self.my_rights
    }
    pub fn set_my_friend_rights(&mut self, value: FriendRights) {
        self.my_rights = value;
    }
    pub fn can_see_me_online(&self) -> bool {
        self.their_rights.0 & FriendRights::CAN_SEE_ONLINE.0 != 0
    }
    pub fn set_can_see_me_online(&mut self, value: bool) {
        set_right(&mut self.their_rights, FriendRights::CAN_SEE_ONLINE, value);
        if !value {
            set_right(&mut self.their_rights, FriendRights::CAN_SEE_ON_MAP, false);
        }
    }
    pub fn can_see_me_on_map(&self) -> bool {
        self.their_rights.0 & FriendRights::CAN_SEE_ON_MAP.0 != 0
    }
    pub fn set_can_see_me_on_map(&mut self, value: bool) {
        if self.can_see_me_online() {
            set_right(&mut self.their_rights, FriendRights::CAN_SEE_ON_MAP, value);
        }
    }
    pub fn can_modify_my_objects(&self) -> bool {
        self.their_rights.0 & FriendRights::CAN_MODIFY_OBJECTS.0 != 0
    }
    pub fn set_can_modify_my_objects(&mut self, value: bool) {
        set_right(
            &mut self.their_rights,
            FriendRights::CAN_MODIFY_OBJECTS,
            value,
        );
    }
    pub fn can_see_them_online(&self) -> bool {
        self.my_rights.0 & FriendRights::CAN_SEE_ONLINE.0 != 0
    }
    pub fn set_can_see_them_online(&mut self, value: bool) {
        set_right(&mut self.my_rights, FriendRights::CAN_SEE_ONLINE, value);
    }
    pub fn can_see_them_on_map(&self) -> bool {
        self.my_rights.0 & FriendRights::CAN_SEE_ON_MAP.0 != 0
    }
    pub fn set_can_see_them_on_map(&mut self, value: bool) {
        set_right(&mut self.my_rights, FriendRights::CAN_SEE_ON_MAP, value);
    }
    pub fn can_modify_their_objects(&self) -> bool {
        self.my_rights.0 & FriendRights::CAN_MODIFY_OBJECTS.0 != 0
    }
    pub fn set_can_modify_their_objects(&mut self, value: bool) {
        set_right(&mut self.my_rights, FriendRights::CAN_MODIFY_OBJECTS, value);
    }
    pub fn to_string(&self) -> String {
        let who = if self.name.is_empty() {
            self.id.to_string()
        } else {
            self.name.clone()
        };
        format!(
            "{who} (Their Rights: {}, My Rights: {})",
            self.their_rights.0, self.my_rights.0
        )
    }
}
fn set_right(rights: &mut FriendRights, flag: FriendRights, enabled: bool) {
    if enabled {
        rights.0 |= flag.0;
    } else {
        rights.0 &= !flag.0;
    }
}

macro_rules! simple_event {
    ($name:ident { $($field:ident : $ty:ty => $getter:ident),+ $(,)? }) => {
        #[derive(Clone)] pub struct $name { $( $field: $ty ),+ }
        impl $name {
            pub fn new($($field: $ty),+) -> Result<Self, Error> { Ok(Self { $($field),+ }) }
            $(pub fn $getter(&self) -> $ty { self.$field.clone() })+
        }
    };
}
simple_event!(CallingCardOfferedEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name, transaction_id: UUID => transaction_id });
simple_event!(FriendFoundReplyEventArgs { agent_id: UUID => agent_id, region_handle: u64 => region_handle, location: Vector3 => location });
simple_event!(FriendInfoEventArgs { friend: FriendInfo => friend });
simple_event!(FriendNamesEventArgs { names: HashMap<UUID, String> => names });
simple_event!(FriendsReadyEventArgs { count: i32 => count });
simple_event!(FriendshipOfferedEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name, im_session_id: UUID => session_id });
simple_event!(FriendshipResponseEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name, accepted: bool => accepted });
simple_event!(FriendshipTerminatedEventArgs { agent_id: UUID => agent_id, agent_name: String => agent_name });

#[derive(Default)]
struct FriendEvents {
    calling_card: EventRegistry<CallingCardOfferedEventArgs>,
    found: EventRegistry<FriendFoundReplyEventArgs>,
    names: EventRegistry<FriendNamesEventArgs>,
    offline: EventRegistry<FriendInfoEventArgs>,
    online: EventRegistry<FriendInfoEventArgs>,
    rights: EventRegistry<FriendInfoEventArgs>,
    offered: EventRegistry<FriendshipOfferedEventArgs>,
    response: EventRegistry<FriendshipResponseEventArgs>,
    terminated: EventRegistry<FriendshipTerminatedEventArgs>,
    ready: EventRegistry<FriendsReadyEventArgs>,
}
pub(crate) struct FriendsManagerInner {
    client: Arc<GridClient>,
    friends: RwLock<HashMap<UUID, FriendInfo>>,
    requests: RwLock<HashMap<UUID, UUID>>,
    events: FriendEvents,
    subscriptions: Mutex<Vec<Subscription>>,
    login_callback: Mutex<Option<crate::NetworkManagerLoginResponseCallback>>,
    disposed: AtomicBool,
}
#[derive(Clone)]
pub struct FriendsManager {
    pub(crate) inner: Arc<FriendsManagerInner>,
}

impl FriendsManager {
    pub(crate) fn native_new(client: Arc<GridClient>) -> Result<Self, Error> {
        if let Some(inner) = client.cached_friends_manager_inner() {
            return Ok(Self { inner });
        }
        let inner = Arc::new(FriendsManagerInner {
            client: Arc::clone(&client),
            friends: RwLock::new(HashMap::new()),
            requests: RwLock::new(HashMap::new()),
            events: FriendEvents::default(),
            subscriptions: Mutex::new(Vec::new()),
            login_callback: Mutex::new(None),
            disposed: AtomicBool::new(false),
        });
        let weak = Arc::downgrade(&inner);
        mutex(&inner.subscriptions).push(client.network().subscribe_raw_packet(Arc::new(
            move |e| {
                if let Some(inner) = weak.upgrade() {
                    inner.handle_packet(e);
                }
            },
        )));
        let weak = Arc::downgrade(&inner);
        let mut callback_client = (*client).clone();
        mutex(&inner.subscriptions).push(callback_client.self_().subscribe_im(Arc::new(
            move |e| {
                if let Some(inner) = weak.upgrade() {
                    inner.handle_im(e);
                }
            },
        )));
        let weak = Arc::downgrade(&inner);
        let callback = crate::NetworkManagerLoginResponseCallback::from_handler(
            move |success, _, _, _, response| {
                if success && let Some(inner) = weak.upgrade() {
                    inner.load_login(response);
                }
            },
        );
        client.network().native_register_login_response_callback(
            callback.clone(),
            Some(vec!["buddy-list".into()]),
        )?;
        *mutex(&inner.login_callback) = Some(callback);
        Ok(Self {
            inner: client.install_friends_manager_inner(inner),
        })
    }
    fn ids(&self) -> (UUID, UUID) {
        let mut c = (*self.inner.client).clone();
        let a = c.self_();
        (a.agent_id(), a.session_id())
    }
    fn send<T: GeneratedPacket>(&self, p: &T, ty: PacketType) -> Result<(), Error> {
        if self.inner.disposed.load(Ordering::Acquire) {
            return Err(Error::InvalidOperation);
        }
        let sim = self
            .inner
            .client
            .network()
            .current_sim()
            .ok_or(Error::InvalidOperation)?;
        let bytes = p.encode_packet()?;
        let result = sim.native_send_packet_data(
            bytes.clone(),
            i32::try_from(bytes.len()).map_err(|_| Error::Argument)?,
            ty,
            false,
        );
        match result {
            Err(Error::InvalidOperation) if !sim.connected() => Ok(()),
            result => result,
        }
    }
    pub fn friend_list(&self) -> HashMap<UUID, FriendInfo> {
        read(&self.inner.friends).clone()
    }
    pub fn friend_requests(&self) -> HashMap<UUID, UUID> {
        read(&self.inner.requests).clone()
    }
    pub fn dispose(&self) -> Result<(), Error> {
        self.inner.dispose();
        Ok(())
    }
    pub fn subscribe_calling_card_offered(
        &self,
        h: EventHandler<CallingCardOfferedEventArgs>,
    ) -> Subscription {
        self.inner.events.calling_card.subscribe(h)
    }
    pub fn subscribe_friend_found_reply(
        &self,
        h: EventHandler<FriendFoundReplyEventArgs>,
    ) -> Subscription {
        self.inner.events.found.subscribe(h)
    }
    pub fn subscribe_friend_names(&self, h: EventHandler<FriendNamesEventArgs>) -> Subscription {
        self.inner.events.names.subscribe(h)
    }
    pub fn subscribe_friend_offline(&self, h: EventHandler<FriendInfoEventArgs>) -> Subscription {
        self.inner.events.offline.subscribe(h)
    }
    pub fn subscribe_friend_online(&self, h: EventHandler<FriendInfoEventArgs>) -> Subscription {
        self.inner.events.online.subscribe(h)
    }
    pub fn subscribe_friend_rights_update(
        &self,
        h: EventHandler<FriendInfoEventArgs>,
    ) -> Subscription {
        self.inner.events.rights.subscribe(h)
    }
    pub fn subscribe_friendship_offered(
        &self,
        h: EventHandler<FriendshipOfferedEventArgs>,
    ) -> Subscription {
        self.inner.events.offered.subscribe(h)
    }
    pub fn subscribe_friendship_response(
        &self,
        h: EventHandler<FriendshipResponseEventArgs>,
    ) -> Subscription {
        self.inner.events.response.subscribe(h)
    }
    pub fn subscribe_friendship_terminated(
        &self,
        h: EventHandler<FriendshipTerminatedEventArgs>,
    ) -> Subscription {
        self.inner.events.terminated.subscribe(h)
    }
    pub fn subscribe_friends_list_ready(
        &self,
        h: EventHandler<FriendsReadyEventArgs>,
    ) -> Subscription {
        self.inner.events.ready.subscribe(h)
    }
    pub fn accept_friendship(&self, from: UUID, transaction: UUID) -> Result<(), Error> {
        let (agent, session) = self.ids();
        let mut p = crate::packets::AcceptFriendshipPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.transaction_block.transaction_id = transaction;
        let mut folder =
            crate::packets::AcceptFriendshipPacketFolderDataBlock::new_with_constructor()?;
        folder.folder_id = self
            .inner
            .client
            .inventory()
            .find_folder_for_type_with_folder_type(FolderType::CallingCard)?;
        p.folder_data.push(folder);
        self.send(&p, PacketType::AcceptFriendship)?;
        self.inner.accept(from);
        Ok(())
    }
    pub fn decline_friendship(&self, from: UUID, transaction: UUID) -> Result<(), Error> {
        let (agent, session) = self.ids();
        let mut p = crate::packets::DeclineFriendshipPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.transaction_block.transaction_id = transaction;
        self.send(&p, PacketType::DeclineFriendship)?;
        write(&self.inner.requests).remove(&from);
        Ok(())
    }
    pub async fn accept_friendship_via_cap(
        &self,
        from: UUID,
        ct: Option<CancellationToken>,
    ) -> Result<(), Error> {
        self.friend_cap("AcceptFriendship", "POST", from, ct)
            .await?;
        self.inner.accept(from);
        Ok(())
    }
    pub async fn decline_friendship_via_cap(
        &self,
        from: UUID,
        ct: Option<CancellationToken>,
    ) -> Result<(), Error> {
        self.friend_cap("DeclineFriendship", "DELETE", from, ct)
            .await?;
        write(&self.inner.requests).remove(&from);
        Ok(())
    }
    async fn friend_cap(
        &self,
        cap: &str,
        method: &str,
        from: UUID,
        ct: Option<CancellationToken>,
    ) -> Result<(), Error> {
        let token = ct.unwrap_or_else(|| self.inner.client.cancellation_token());
        token.throw_if_cancellation_requested()?;
        let caps = self
            .inner
            .client
            .network()
            .current_sim()
            .and_then(|s| s.native_caps())
            .ok_or(Error::InvalidOperation)?;
        let uri = caps
            .capability_uri(cap.into())?
            .ok_or(Error::InvalidOperation)?;
        let mut url = reqwest::Url::parse(&uri.0).map_err(|_| Error::Argument)?;
        url.query_pairs_mut().append_pair("from", &from.to_string());
        let http = self.inner.client.native_http_caps_client();
        let (response, bytes) = if method == "POST" {
            http.post_with_uri_osd_format_osd_cancellation_token_i_progress(
                libremetaverse_types::compat::Uri(url.into()),
                OSDFormat::Xml,
                OSD::Undefined,
                token,
                None,
            )
            .await?
        } else {
            http.send_custom(
                "DELETE",
                libremetaverse_types::compat::Uri(url.into()),
                BTreeMap::new(),
                None,
                Vec::new(),
                token,
            )
            .await?
        };
        if !response.is_success_status_code() || bytes.len() > 1024 * 1024 {
            return Err(Error::InvalidOperation);
        }
        if !bytes.is_empty() {
            let OSD::Map(map) = OSDParser::deserialize_with_bytes(bytes)? else {
                return Err(Error::Argument);
            };
            if !map
                .get("success")
                .and_then(|v| v.as_boolean().ok())
                .unwrap_or(false)
            {
                return Err(Error::InvalidOperation);
            }
        }
        Ok(())
    }
    pub fn offer_friendship_with_uuid(&self, id: UUID) -> Result<(), Error> {
        self.offer_friendship_with_uuid_string(id, "Do ya wanna be my buddy?".into())
    }
    pub fn offer_friendship_with_uuid_string(
        &self,
        id: UUID,
        message: String,
    ) -> Result<(), Error> {
        if message.len() > 1024 {
            return Err(Error::Argument);
        }
        let mut client = (*self.inner.client).clone();
        let region = client
            .network()
            .current_sim()
            .map_or_else(UUID::zero, |s| s.id);
        let me = client.self_();
        me.native_instant_message_full(
            me.name(),
            id,
            message,
            UUID::random()?,
            InstantMessageDialog::FriendshipOffered,
            crate::InstantMessageOnline::Offline,
            me.sim_position(),
            region,
            Vec::new(),
        )
    }
    pub fn terminate_friendship(&self, id: UUID) -> Result<(), Error> {
        let (agent, session) = self.ids();
        let mut p = crate::packets::TerminateFriendshipPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.ex_block.other_id = id;
        self.send(&p, PacketType::TerminateFriendship)?;
        write(&self.inner.friends).remove(&id);
        Ok(())
    }
    pub fn grant_rights(&self, id: UUID, rights: FriendRights) -> Result<(), Error> {
        let (agent, session) = self.ids();
        let mut p = crate::packets::GrantUserRightsPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        let mut b = crate::packets::GrantUserRightsPacketRightsBlock::new_with_constructor()?;
        b.agent_related = id;
        b.related_rights = rights.0;
        p.rights.push(b);
        self.send(&p, PacketType::GrantUserRights)
    }
    pub fn map_friend(&self, id: UUID) -> Result<(), Error> {
        let (agent, _) = self.ids();
        let mut p = crate::packets::FindAgentPacket::new_with_constructor()?;
        p.agent_block.hunter = agent;
        p.agent_block.prey = id;
        let b = crate::packets::FindAgentPacketLocationBlockBlock::new_with_constructor()?;
        p.location_block.push(b);
        self.send(&p, PacketType::FindAgent)
    }
    pub fn track_friend(&self, id: UUID) -> Result<(), Error> {
        let (agent, session) = self.ids();
        let mut p = crate::packets::TrackAgentPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.target_data.prey_id = id;
        self.send(&p, PacketType::TrackAgent)
    }
    pub fn request_online_notification(&self, id: UUID) -> Result<(), Error> {
        let (agent, session) = self.ids();
        let mut p = crate::packets::GenericMessagePacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.method_data.method = b"requestonlinenotification".to_vec();
        let mut b = crate::packets::GenericMessagePacketParamListBlock::new_with_constructor()?;
        b.parameter = id.to_string().into_bytes();
        p.param_list.push(b);
        self.send(&p, PacketType::GenericMessage)
    }
    pub fn give_calling_card(&self, id: UUID) -> Result<(), Error> {
        let (agent, session) = self.ids();
        let mut p = crate::packets::OfferCallingCardPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.agent_block.dest_id = id;
        p.agent_block.transaction_id = UUID::random()?;
        self.send(&p, PacketType::OfferCallingCard)
    }
    pub fn accept_calling_card(&self, _from: UUID, transaction: UUID) -> Result<(), Error> {
        let (agent, session) = self.ids();
        let mut p = crate::packets::AcceptCallingCardPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.transaction_block.transaction_id = transaction;
        let mut b = crate::packets::AcceptCallingCardPacketFolderDataBlock::new_with_constructor()?;
        b.folder_id = self
            .inner
            .client
            .inventory()
            .find_folder_for_type_with_folder_type(FolderType::CallingCard)?;
        p.folder_data.push(b);
        self.send(&p, PacketType::AcceptCallingCard)
    }
    pub fn decline_calling_card(&self, _from: UUID, transaction: UUID) -> Result<(), Error> {
        let (agent, session) = self.ids();
        let mut p = crate::packets::DeclineCallingCardPacket::new_with_constructor()?;
        p.agent_data.agent_id = agent;
        p.agent_data.session_id = session;
        p.transaction_block.transaction_id = transaction;
        self.send(&p, PacketType::DeclineCallingCard)
    }
    pub fn on_find_agent_reply_handler(
        &self,
        _sender: Option<libremetaverse_types::compat::Object>,
        e: crate::PacketReceivedEventArgs,
    ) -> Result<(), Error> {
        // The native network pipeline dispatches the payload-bearing raw packet
        // before its metadata-only compatibility callback. `handle_packet`
        // performs the decode and event emission; this public callback retains
        // the upstream type guard without emitting a duplicate event.
        if e.packet().type_ == PacketType::FindAgent {
            Ok(())
        } else {
            Err(Error::Argument)
        }
    }
}

impl FriendsManagerInner {
    fn accept(&self, id: UUID) {
        let mut friends = write(&self.friends);
        if friends.len() < MAX_FRIENDS || friends.contains_key(&id) {
            friends.entry(id).or_insert_with(|| {
                FriendInfo::native_new(
                    id,
                    FriendRights::CAN_SEE_ONLINE,
                    FriendRights::CAN_SEE_ONLINE,
                )
            });
        }
        drop(friends);
        write(&self.requests).remove(&id);
    }
    fn dispose(&self) {
        if self.disposed.swap(true, Ordering::AcqRel) {
            return;
        }
        mutex(&self.subscriptions).clear();
        if let Some(cb) = mutex(&self.login_callback).take() {
            let _ = self
                .client
                .network()
                .native_unregister_login_response_callback(&cb);
        }
    }
    fn load_login(&self, response: Option<crate::LoginResponseData>) {
        let Some(response) = response else { return };
        let mut friends = write(&self.friends);
        for b in response.buddy_list().into_iter().take(MAX_FRIENDS) {
            if let Ok(id) = UUID::parse(b.buddy_id.chars().take(36).collect::<String>()) {
                friends.entry(id).or_insert_with(|| {
                    FriendInfo::native_new(
                        id,
                        FriendRights(b.buddy_rights_given),
                        FriendRights(b.buddy_rights_has),
                    )
                });
            }
        }
        let count = i32::try_from(friends.len()).unwrap_or(i32::MAX);
        drop(friends);
        self.events.ready.emit(FriendsReadyEventArgs { count });
    }
    fn handle_im(&self, e: crate::InstantMessageEventArgs) {
        let im = e.im();
        match im.dialog {
            InstantMessageDialog::FriendshipOffered => {
                let mut requests = write(&self.requests);
                if requests.len() < MAX_FRIENDS || requests.contains_key(&im.from_agent_id) {
                    requests.insert(im.from_agent_id, im.im_session_id);
                }
                drop(requests);
                self.events.offered.emit(FriendshipOfferedEventArgs {
                    agent_id: im.from_agent_id,
                    agent_name: im.from_agent_name,
                    im_session_id: im.im_session_id,
                });
            }
            InstantMessageDialog::FriendshipAccepted => {
                let mut f = FriendInfo::native_new(
                    im.from_agent_id,
                    FriendRights::CAN_SEE_ONLINE,
                    FriendRights::CAN_SEE_ONLINE,
                );
                f.name.clone_from(&im.from_agent_name);
                f.online = true;
                write(&self.friends).insert(f.id, f.clone());
                self.events.response.emit(FriendshipResponseEventArgs {
                    agent_id: f.id,
                    agent_name: f.name,
                    accepted: true,
                });
            }
            InstantMessageDialog::FriendshipDeclined => {
                self.events.response.emit(FriendshipResponseEventArgs {
                    agent_id: im.from_agent_id,
                    agent_name: im.from_agent_name,
                    accepted: false,
                });
            }
            _ => {}
        }
    }
    #[allow(
        clippy::too_many_lines,
        clippy::cast_possible_truncation,
        clippy::cast_sign_loss,
        clippy::cast_precision_loss
    )]
    fn handle_packet(&self, e: RawPacketReceivedEventArgs) {
        if self.disposed.load(Ordering::Acquire) || e.data.len() > 8 * 1024 * 1024 {
            return;
        }
        let mut pos = 0;
        match e.packet_type {
            PacketType::OnlineNotification | PacketType::OfflineNotification => {
                let online = e.packet_type == PacketType::OnlineNotification;
                let ids = if online {
                    crate::packets::OnlineNotificationPacket::new_from_bytes(&e.data, &mut pos).map(
                        |p| {
                            p.agent_block
                                .into_iter()
                                .map(|b| b.agent_id)
                                .collect::<Vec<_>>()
                        },
                    )
                } else {
                    crate::packets::OfflineNotificationPacket::new_from_bytes(&e.data, &mut pos)
                        .map(|p| {
                            p.agent_block
                                .into_iter()
                                .map(|b| b.agent_id)
                                .collect::<Vec<_>>()
                        })
                };
                if let Ok(ids) = ids {
                    for id in ids.into_iter().take(MAX_FRIENDS) {
                        let event = {
                            let mut fs = write(&self.friends);
                            let f = fs.entry(id).or_insert_with(|| {
                                FriendInfo::native_new(
                                    id,
                                    FriendRights::CAN_SEE_ONLINE,
                                    FriendRights::CAN_SEE_ONLINE,
                                )
                            });
                            let changed = f.online != online;
                            f.online = online;
                            changed.then(|| f.clone())
                        };
                        if let Some(friend) = event {
                            if online {
                                self.events.online.emit(FriendInfoEventArgs { friend });
                            } else {
                                self.events.offline.emit(FriendInfoEventArgs { friend });
                            }
                        }
                    }
                }
            }
            PacketType::TerminateFriendship => {
                if let Ok(p) =
                    crate::packets::TerminateFriendshipPacket::new_from_bytes(&e.data, &mut pos)
                {
                    let id = p.ex_block.other_id;
                    let name = write(&self.friends)
                        .remove(&id)
                        .map_or_else(String::new, |f| f.name);
                    self.events.terminated.emit(FriendshipTerminatedEventArgs {
                        agent_id: id,
                        agent_name: name,
                    });
                }
            }
            PacketType::ChangeUserRights => {
                if let Ok(p) =
                    crate::packets::ChangeUserRightsPacket::new_from_bytes(&e.data, &mut pos)
                {
                    let self_id = {
                        let mut client = (*self.client).clone();
                        client.self_().agent_id()
                    };
                    for b in p.rights.into_iter().take(MAX_FRIENDS) {
                        let id = if b.agent_related == self_id {
                            p.agent_data.agent_id
                        } else {
                            b.agent_related
                        };
                        let event = {
                            let mut fs = write(&self.friends);
                            fs.get_mut(&id).map(|f| {
                                if b.agent_related == id {
                                    f.their_rights = FriendRights(b.related_rights);
                                } else {
                                    f.my_rights = FriendRights(b.related_rights);
                                }
                                f.clone()
                            })
                        };
                        if let Some(friend) = event {
                            self.events.rights.emit(FriendInfoEventArgs { friend });
                        }
                    }
                }
            }
            PacketType::FindAgent => {
                if let Ok(p) = crate::packets::FindAgentPacket::new_from_bytes(&e.data, &mut pos)
                    && let Some(loc) = p.location_block.first()
                {
                    let gx = loc.global_x.max(0.0) as u32;
                    let gy = loc.global_y.max(0.0) as u32;
                    let handle = (u64::from(gx & !255) << 32) | u64::from(gy & !255);
                    self.events.found.emit(FriendFoundReplyEventArgs {
                        agent_id: p.agent_block.prey,
                        region_handle: handle,
                        location: Vector3 {
                            x: (gx & 255) as f32,
                            y: (gy & 255) as f32,
                            z: 0.0,
                        },
                    });
                }
            }
            PacketType::OfferCallingCard => {
                if let Ok(p) =
                    crate::packets::OfferCallingCardPacket::new_from_bytes(&e.data, &mut pos)
                {
                    let id = p.agent_data.agent_id;
                    let name = read(&self.friends).get(&id).map_or_else(
                        || id.to_string(),
                        |f| {
                            if f.name.is_empty() {
                                id.to_string()
                            } else {
                                f.name.clone()
                            }
                        },
                    );
                    self.events.calling_card.emit(CallingCardOfferedEventArgs {
                        agent_id: id,
                        agent_name: name,
                        transaction_id: p.agent_block.transaction_id,
                    });
                }
            }
            PacketType::UUIDNameReply => {
                if let Ok(p) =
                    crate::packets::UUIDNameReplyPacket::new_from_bytes(&e.data, &mut pos)
                {
                    let mut changed = HashMap::new();
                    {
                        let mut fs = write(&self.friends);
                        for b in p.uuid_name_block.into_iter().take(MAX_FRIENDS) {
                            if let Some(f) = fs.get_mut(&b.id) {
                                let name = format!(
                                    "{} {}",
                                    wire_string(&b.first_name),
                                    wire_string(&b.last_name)
                                )
                                .trim()
                                .to_owned();
                                if f.name != name {
                                    f.name.clone_from(&name);
                                    changed.insert(b.id, name);
                                }
                            }
                        }
                    }
                    if !changed.is_empty() {
                        self.events
                            .names
                            .emit(FriendNamesEventArgs { names: changed });
                    }
                }
            }
            _ => {}
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::net::{IpAddr, Ipv4Addr, SocketAddr};

    #[test]
    fn rights_enforce_online_before_map() {
        let mut f = FriendInfo::native_new(UUID::zero(), FriendRights::NONE, FriendRights::NONE);
        f.set_can_see_me_on_map(true);
        assert!(!f.can_see_me_on_map());
        f.set_can_see_me_online(true);
        f.set_can_see_me_on_map(true);
        assert!(f.can_see_me_on_map());
        f.set_can_see_me_online(false);
        assert!(!f.can_see_me_on_map());
    }

    #[test]
    fn presence_packet_updates_state_before_callback() {
        let client = GridClient::new().unwrap();
        let manager = FriendsManager::native_new(Arc::new(client.clone())).unwrap();
        let friend_id =
            UUID::new_with_string("77777777-7777-7777-7777-777777777777".into()).unwrap();
        write(&manager.inner.friends).insert(
            friend_id,
            FriendInfo::native_new(friend_id, FriendRights::NONE, FriendRights::NONE),
        );
        let callback_manager = manager.clone();
        let observed = Arc::new(AtomicBool::new(false));
        let observed_copy = Arc::clone(&observed);
        let _subscription = manager.subscribe_friend_online(Arc::new(move |event| {
            assert!(callback_manager.friend_list()[&event.friend().uuid()].is_online());
            observed_copy.store(true, Ordering::Release);
        }));
        let mut packet = crate::packets::OnlineNotificationPacket::new_with_constructor().unwrap();
        let mut row =
            crate::packets::OnlineNotificationPacketAgentBlockBlock::new_with_constructor()
                .unwrap();
        row.agent_id = friend_id;
        packet.agent_block = vec![row];
        let simulator = crate::Simulator::native_new(
            client,
            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 13973),
            0,
            Some(256),
            Some(256),
        )
        .unwrap();
        manager.inner.handle_packet(RawPacketReceivedEventArgs {
            packet_type: PacketType::OnlineNotification,
            data: packet.encode_packet().unwrap(),
            simulator,
        });
        assert!(observed.load(Ordering::Acquire));
    }
}
