corrade-vassal – Rev 1

Subversion Repositories:
Rev:
/*
 * Copyright (c) 2006-2014, openmetaverse.org
 * All rights reserved.
 *
 * - Redistribution and use in source and binary forms, with or without 
 *   modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * - Neither the name of the openmetaverse.org nor the names 
 *   of its contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Text;
using System.Collections.Generic;
using OpenMetaverse.Packets;

namespace OpenMetaverse
{
    /// <summary>
    /// 
    /// </summary>
    [Flags]
    public enum FriendRights : int
    {
        /// <summary>The avatar has no rights</summary>
        None = 0,
        /// <summary>The avatar can see the online status of the target avatar</summary>
        CanSeeOnline = 1,
        /// <summary>The avatar can see the location of the target avatar on the map</summary>
        CanSeeOnMap = 2,
        /// <summary>The avatar can modify the ojects of the target avatar </summary>
        CanModifyObjects = 4
    }

    /// <summary>
    /// This class holds information about an avatar in the friends list.  There are two ways 
    /// to interface to this class.  The first is through the set of boolean properties.  This is the typical
    /// way clients of this class will use it.  The second interface is through two bitflag properties,
    /// TheirFriendsRights and MyFriendsRights
    /// </summary>
    public class FriendInfo
    {
        private UUID m_id;
        private string m_name;
        private bool m_isOnline;
        private bool m_canSeeMeOnline;
        private bool m_canSeeMeOnMap;
        private bool m_canModifyMyObjects;
        private bool m_canSeeThemOnline;
        private bool m_canSeeThemOnMap;
        private bool m_canModifyTheirObjects;

        #region Properties

        /// <summary>
        /// System ID of the avatar
        /// </summary>
        public UUID UUID { get { return m_id; } }

        /// <summary>
        /// full name of the avatar
        /// </summary>
        public string Name
        {
            get { return m_name; }
            set { m_name = value; }
        }

        /// <summary>
        /// True if the avatar is online
        /// </summary>
        public bool IsOnline
        {
            get { return m_isOnline; }
            set { m_isOnline = value; }
        }

        /// <summary>
        /// True if the friend can see if I am online
        /// </summary>
        public bool CanSeeMeOnline
        {
            get { return m_canSeeMeOnline; }
            set
            {
                m_canSeeMeOnline = value;

                // if I can't see them online, then I can't see them on the map
                if (!m_canSeeMeOnline)
                    m_canSeeMeOnMap = false;
            }
        }

        /// <summary>
        /// True if the friend can see me on the map 
        /// </summary>
        public bool CanSeeMeOnMap
        {
            get { return m_canSeeMeOnMap; }
            set
            {
                // if I can't see them online, then I can't see them on the map
                if (m_canSeeMeOnline)
                    m_canSeeMeOnMap = value;
            }
        }

        /// <summary>
        /// True if the freind can modify my objects
        /// </summary>
        public bool CanModifyMyObjects
        {
            get { return m_canModifyMyObjects; }
            set { m_canModifyMyObjects = value; }
        }

        /// <summary>
        /// True if I can see if my friend is online
        /// </summary>
        public bool CanSeeThemOnline { get { return m_canSeeThemOnline; } }

        /// <summary>
        /// True if I can see if my friend is on the map
        /// </summary>
        public bool CanSeeThemOnMap { get { return m_canSeeThemOnMap; } }

        /// <summary>
        /// True if I can modify my friend's objects
        /// </summary>
        public bool CanModifyTheirObjects { get { return m_canModifyTheirObjects; } }

        /// <summary>
        /// My friend's rights represented as bitmapped flags
        /// </summary>
        public FriendRights TheirFriendRights
        {
            get
            {
                FriendRights results = FriendRights.None;
                if (m_canSeeMeOnline)
                    results |= FriendRights.CanSeeOnline;
                if (m_canSeeMeOnMap)
                    results |= FriendRights.CanSeeOnMap;
                if (m_canModifyMyObjects)
                    results |= FriendRights.CanModifyObjects;

                return results;
            }
            set
            {
                m_canSeeMeOnline = (value & FriendRights.CanSeeOnline) != 0;
                m_canSeeMeOnMap = (value & FriendRights.CanSeeOnMap) != 0;
                m_canModifyMyObjects = (value & FriendRights.CanModifyObjects) != 0;
            }
        }

        /// <summary>
        /// My rights represented as bitmapped flags
        /// </summary>
        public FriendRights MyFriendRights
        {
            get
            {
                FriendRights results = FriendRights.None;
                if (m_canSeeThemOnline)
                    results |= FriendRights.CanSeeOnline;
                if (m_canSeeThemOnMap)
                    results |= FriendRights.CanSeeOnMap;
                if (m_canModifyTheirObjects)
                    results |= FriendRights.CanModifyObjects;

                return results;
            }
            set
            {
                m_canSeeThemOnline = (value & FriendRights.CanSeeOnline) != 0;
                m_canSeeThemOnMap = (value & FriendRights.CanSeeOnMap) != 0;
                m_canModifyTheirObjects = (value & FriendRights.CanModifyObjects) != 0;
            }
        }

        #endregion Properties

        /// <summary>
        /// Used internally when building the initial list of friends at login time
        /// </summary>
        /// <param name="id">System ID of the avatar being prepesented</param>
        /// <param name="theirRights">Rights the friend has to see you online and to modify your objects</param>
        /// <param name="myRights">Rights you have to see your friend online and to modify their objects</param>
        internal FriendInfo(UUID id, FriendRights theirRights, FriendRights myRights)
        {
            m_id = id;
            m_canSeeMeOnline = (theirRights & FriendRights.CanSeeOnline) != 0;
            m_canSeeMeOnMap = (theirRights & FriendRights.CanSeeOnMap) != 0;
            m_canModifyMyObjects = (theirRights & FriendRights.CanModifyObjects) != 0;

            m_canSeeThemOnline = (myRights & FriendRights.CanSeeOnline) != 0;
            m_canSeeThemOnMap = (myRights & FriendRights.CanSeeOnMap) != 0;
            m_canModifyTheirObjects = (myRights & FriendRights.CanModifyObjects) != 0;
        }

        /// <summary>
        /// FriendInfo represented as a string
        /// </summary>
        /// <returns>A string reprentation of both my rights and my friends rights</returns>
        public override string ToString()
        {
            if (!String.IsNullOrEmpty(m_name))
                return String.Format("{0} (Their Rights: {1}, My Rights: {2})", m_name, TheirFriendRights,
                    MyFriendRights);
            else
                return String.Format("{0} (Their Rights: {1}, My Rights: {2})", m_id, TheirFriendRights,
                    MyFriendRights);
        }
    }

    /// <summary>
    /// This class is used to add and remove avatars from your friends list and to manage their permission.  
    /// </summary>
    public class FriendsManager
    {
        #region Delegates

        /// <summary>The event subscribers. null if no subcribers</summary>
        private EventHandler<FriendInfoEventArgs> m_FriendOnline;

        /// <summary>Raises the FriendOnline event</summary>
        /// <param name="e">A FriendInfoEventArgs object containing the
        /// data returned from the data server</param>
        protected virtual void OnFriendOnline(FriendInfoEventArgs e)
        {
            EventHandler<FriendInfoEventArgs> handler = m_FriendOnline;
            if (handler != null)
                handler(this, e);
        }

        /// <summary>Thread sync lock object</summary>
        private readonly object m_FriendOnlineLock = new object();

        /// <summary>Raised when the simulator sends notification one of the members in our friends list comes online</summary>
        public event EventHandler<FriendInfoEventArgs> FriendOnline
        {
            add { lock (m_FriendOnlineLock) { m_FriendOnline += value; } }
            remove { lock (m_FriendOnlineLock) { m_FriendOnline -= value; } }
        }

        /// <summary>The event subscribers. null if no subcribers</summary>
        private EventHandler<FriendInfoEventArgs> m_FriendOffline;

        /// <summary>Raises the FriendOffline event</summary>
        /// <param name="e">A FriendInfoEventArgs object containing the
        /// data returned from the data server</param>
        protected virtual void OnFriendOffline(FriendInfoEventArgs e)
        {
            EventHandler<FriendInfoEventArgs> handler = m_FriendOffline;
            if (handler != null)
                handler(this, e);
        }

        /// <summary>Thread sync lock object</summary>
        private readonly object m_FriendOfflineLock = new object();

        /// <summary>Raised when the simulator sends notification one of the members in our friends list goes offline</summary>
        public event EventHandler<FriendInfoEventArgs> FriendOffline
        {
            add { lock (m_FriendOfflineLock) { m_FriendOffline += value; } }
            remove { lock (m_FriendOfflineLock) { m_FriendOffline -= value; } }
        }

        /// <summary>The event subscribers. null if no subcribers</summary>
        private EventHandler<FriendInfoEventArgs> m_FriendRights;

        /// <summary>Raises the FriendRightsUpdate event</summary>
        /// <param name="e">A FriendInfoEventArgs object containing the
        /// data returned from the data server</param>
        protected virtual void OnFriendRights(FriendInfoEventArgs e)
        {
            EventHandler<FriendInfoEventArgs> handler = m_FriendRights;
            if (handler != null)
                handler(this, e);
        }

        /// <summary>Thread sync lock object</summary>
        private readonly object m_FriendRightsLock = new object();

        /// <summary>Raised when the simulator sends notification one of the members in our friends list grants or revokes permissions</summary>
        public event EventHandler<FriendInfoEventArgs> FriendRightsUpdate
        {
            add { lock (m_FriendRightsLock) { m_FriendRights += value; } }
            remove { lock (m_FriendRightsLock) { m_FriendRights -= value; } }
        }

        /// <summary>The event subscribers. null if no subcribers</summary>
        private EventHandler<FriendNamesEventArgs> m_FriendNames;

        /// <summary>Raises the FriendNames event</summary>
        /// <param name="e">A FriendNamesEventArgs object containing the
        /// data returned from the data server</param>
        protected virtual void OnFriendNames(FriendNamesEventArgs e)
        {
            EventHandler<FriendNamesEventArgs> handler = m_FriendNames;
            if (handler != null)
                handler(this, e);
        }

        /// <summary>Thread sync lock object</summary>
        private readonly object m_FriendNamesLock = new object();

        /// <summary>Raised when the simulator sends us the names on our friends list</summary>
        public event EventHandler<FriendNamesEventArgs> FriendNames
        {
            add { lock (m_FriendNamesLock) { m_FriendNames += value; } }
            remove { lock (m_FriendNamesLock) { m_FriendNames -= value; } }
        }

        /// <summary>The event subscribers. null if no subcribers</summary>
        private EventHandler<FriendshipOfferedEventArgs> m_FriendshipOffered;

        /// <summary>Raises the FriendshipOffered event</summary>
        /// <param name="e">A FriendshipOfferedEventArgs object containing the
        /// data returned from the data server</param>
        protected virtual void OnFriendshipOffered(FriendshipOfferedEventArgs e)
        {
            EventHandler<FriendshipOfferedEventArgs> handler = m_FriendshipOffered;
            if (handler != null)
                handler(this, e);
        }

        /// <summary>Thread sync lock object</summary>
        private readonly object m_FriendshipOfferedLock = new object();

        /// <summary>Raised when the simulator sends notification another agent is offering us friendship</summary>
        public event EventHandler<FriendshipOfferedEventArgs> FriendshipOffered
        {
            add { lock (m_FriendshipOfferedLock) { m_FriendshipOffered += value; } }
            remove { lock (m_FriendshipOfferedLock) { m_FriendshipOffered -= value; } }
        }

        /// <summary>The event subscribers. null if no subcribers</summary>
        private EventHandler<FriendshipResponseEventArgs> m_FriendshipResponse;

        /// <summary>Raises the FriendshipResponse event</summary>
        /// <param name="e">A FriendshipResponseEventArgs object containing the
        /// data returned from the data server</param>
        protected virtual void OnFriendshipResponse(FriendshipResponseEventArgs e)
        {
            EventHandler<FriendshipResponseEventArgs> handler = m_FriendshipResponse;
            if (handler != null)
                handler(this, e);
        }

        /// <summary>Thread sync lock object</summary>
        private readonly object m_FriendshipResponseLock = new object();

        /// <summary>Raised when a request we sent to friend another agent is accepted or declined</summary>
        public event EventHandler<FriendshipResponseEventArgs> FriendshipResponse
        {
            add { lock (m_FriendshipResponseLock) { m_FriendshipResponse += value; } }
            remove { lock (m_FriendshipResponseLock) { m_FriendshipResponse -= value; } }
        }

        /// <summary>The event subscribers. null if no subcribers</summary>
        private EventHandler<FriendshipTerminatedEventArgs> m_FriendshipTerminated;

        /// <summary>Raises the FriendshipTerminated event</summary>
        /// <param name="e">A FriendshipTerminatedEventArgs object containing the
        /// data returned from the data server</param>
        protected virtual void OnFriendshipTerminated(FriendshipTerminatedEventArgs e)
        {
            EventHandler<FriendshipTerminatedEventArgs> handler = m_FriendshipTerminated;
            if (handler != null)
                handler(this, e);
        }

        /// <summary>Thread sync lock object</summary>
        private readonly object m_FriendshipTerminatedLock = new object();

        /// <summary>Raised when the simulator sends notification one of the members in our friends list has terminated 
        /// our friendship</summary>
        public event EventHandler<FriendshipTerminatedEventArgs> FriendshipTerminated
        {
            add { lock (m_FriendshipTerminatedLock) { m_FriendshipTerminated += value; } }
            remove { lock (m_FriendshipTerminatedLock) { m_FriendshipTerminated -= value; } }
        }

        /// <summary>The event subscribers. null if no subcribers</summary>
        private EventHandler<FriendFoundReplyEventArgs> m_FriendFound;

        /// <summary>Raises the FriendFoundReply event</summary>
        /// <param name="e">A FriendFoundReplyEventArgs object containing the
        /// data returned from the data server</param>
        protected virtual void OnFriendFoundReply(FriendFoundReplyEventArgs e)
        {
            EventHandler<FriendFoundReplyEventArgs> handler = m_FriendFound;
            if (handler != null)
                handler(this, e);
        }

        /// <summary>Thread sync lock object</summary>
        private readonly object m_FriendFoundLock = new object();

        /// <summary>Raised when the simulator sends the location of a friend we have 
        /// requested map location info for</summary>
        public event EventHandler<FriendFoundReplyEventArgs> FriendFoundReply
        {
            add { lock (m_FriendFoundLock) { m_FriendFound += value; } }
            remove { lock (m_FriendFoundLock) { m_FriendFound -= value; } }
        }

        #endregion Delegates

        #region Events

        #endregion Events

        private GridClient Client;
        /// <summary>
        /// A dictionary of key/value pairs containing known friends of this avatar. 
        /// 
        /// The Key is the <seealso cref="UUID"/> of the friend, the value is a <seealso cref="FriendInfo"/>
        /// object that contains detailed information including permissions you have and have given to the friend
        /// </summary>
        public InternalDictionary<UUID, FriendInfo> FriendList = new InternalDictionary<UUID, FriendInfo>();

        /// <summary>
        /// A Dictionary of key/value pairs containing current pending frienship offers.
        /// 
        /// The key is the <seealso cref="UUID"/> of the avatar making the request, 
        /// the value is the <seealso cref="UUID"/> of the request which is used to accept
        /// or decline the friendship offer
        /// </summary>
        public InternalDictionary<UUID, UUID> FriendRequests = new InternalDictionary<UUID, UUID>();

        /// <summary>
        /// Internal constructor
        /// </summary>
        /// <param name="client">A reference to the GridClient Object</param>
        internal FriendsManager(GridClient client)
        {
            Client = client;

            Client.Network.LoginProgress += Network_OnConnect;
            Client.Avatars.UUIDNameReply += new EventHandler<UUIDNameReplyEventArgs>(Avatars_OnAvatarNames);
            Client.Self.IM += Self_IM;

            Client.Network.RegisterCallback(PacketType.OnlineNotification, OnlineNotificationHandler);
            Client.Network.RegisterCallback(PacketType.OfflineNotification, OfflineNotificationHandler);
            Client.Network.RegisterCallback(PacketType.ChangeUserRights, ChangeUserRightsHandler);
            Client.Network.RegisterCallback(PacketType.TerminateFriendship, TerminateFriendshipHandler);
            Client.Network.RegisterCallback(PacketType.FindAgent, OnFindAgentReplyHandler);

            Client.Network.RegisterLoginResponseCallback(new NetworkManager.LoginResponseCallback(Network_OnLoginResponse),
                new string[] { "buddy-list" });
        }

        #region Public Methods

        /// <summary>
        /// Accept a friendship request
        /// </summary>
        /// <param name="fromAgentID">agentID of avatatar to form friendship with</param>
        /// <param name="imSessionID">imSessionID of the friendship request message</param>
        public void AcceptFriendship(UUID fromAgentID, UUID imSessionID)
        {
            UUID callingCardFolder = Client.Inventory.FindFolderForType(AssetType.CallingCard);

            AcceptFriendshipPacket request = new AcceptFriendshipPacket();
            request.AgentData.AgentID = Client.Self.AgentID;
            request.AgentData.SessionID = Client.Self.SessionID;
            request.TransactionBlock.TransactionID = imSessionID;
            request.FolderData = new AcceptFriendshipPacket.FolderDataBlock[1];
            request.FolderData[0] = new AcceptFriendshipPacket.FolderDataBlock();
            request.FolderData[0].FolderID = callingCardFolder;

            Client.Network.SendPacket(request);

            FriendInfo friend = new FriendInfo(fromAgentID, FriendRights.CanSeeOnline,
                FriendRights.CanSeeOnline);

            if (!FriendList.ContainsKey(fromAgentID))
                FriendList.Add(friend.UUID, friend);

            if (FriendRequests.ContainsKey(fromAgentID))
                FriendRequests.Remove(fromAgentID);

            Client.Avatars.RequestAvatarName(fromAgentID);
        }

        /// <summary>
        /// Decline a friendship request
        /// </summary>
        /// <param name="fromAgentID"><seealso cref="UUID"/> of friend</param>
        /// <param name="imSessionID">imSessionID of the friendship request message</param>
        public void DeclineFriendship(UUID fromAgentID, UUID imSessionID)
        {
            DeclineFriendshipPacket request = new DeclineFriendshipPacket();
            request.AgentData.AgentID = Client.Self.AgentID;
            request.AgentData.SessionID = Client.Self.SessionID;
            request.TransactionBlock.TransactionID = imSessionID;
            Client.Network.SendPacket(request);

            if (FriendRequests.ContainsKey(fromAgentID))
                FriendRequests.Remove(fromAgentID);
        }

        /// <summary>
        /// Overload: Offer friendship to an avatar.
        /// </summary>
        /// <param name="agentID">System ID of the avatar you are offering friendship to</param>
        public void OfferFriendship(UUID agentID)
        {
            OfferFriendship(agentID, "Do ya wanna be my buddy?");
        }

        /// <summary>
        /// Offer friendship to an avatar.
        /// </summary>
        /// <param name="agentID">System ID of the avatar you are offering friendship to</param>
        /// <param name="message">A message to send with the request</param>
        public void OfferFriendship(UUID agentID, string message)
        {
            Client.Self.InstantMessage(Client.Self.Name,
                agentID,
                message,
                UUID.Random(),
                InstantMessageDialog.FriendshipOffered,
                InstantMessageOnline.Offline,
                Client.Self.SimPosition,
                Client.Network.CurrentSim.ID,
                null);
        }


        /// <summary>
        /// Terminate a friendship with an avatar
        /// </summary>
        /// <param name="agentID">System ID of the avatar you are terminating the friendship with</param>
        public void TerminateFriendship(UUID agentID)
        {
            if (FriendList.ContainsKey(agentID))
            {
                TerminateFriendshipPacket request = new TerminateFriendshipPacket();
                request.AgentData.AgentID = Client.Self.AgentID;
                request.AgentData.SessionID = Client.Self.SessionID;
                request.ExBlock.OtherID = agentID;

                Client.Network.SendPacket(request);

                if (FriendList.ContainsKey(agentID))
                    FriendList.Remove(agentID);
            }
        }
        /// <summary>Process an incoming packet and raise the appropriate events</summary>
        /// <param name="sender">The sender</param>
        /// <param name="e">The EventArgs object containing the packet data</param>
        private void TerminateFriendshipHandler(object sender, PacketReceivedEventArgs e)
        {
            Packet packet = e.Packet;
            TerminateFriendshipPacket itsOver = (TerminateFriendshipPacket)packet;
            string name = String.Empty;

            if (FriendList.ContainsKey(itsOver.ExBlock.OtherID))
            {
                name = FriendList[itsOver.ExBlock.OtherID].Name;
                FriendList.Remove(itsOver.ExBlock.OtherID);
            }

            if (m_FriendshipTerminated != null)
            {
                OnFriendshipTerminated(new FriendshipTerminatedEventArgs(itsOver.ExBlock.OtherID, name));
            }
        }

        /// <summary>
        /// Change the rights of a friend avatar.
        /// </summary>
        /// <param name="friendID">the <seealso cref="UUID"/> of the friend</param>
        /// <param name="rights">the new rights to give the friend</param>
        /// <remarks>This method will implicitly set the rights to those passed in the rights parameter.</remarks>
        public void GrantRights(UUID friendID, FriendRights rights)
        {
            GrantUserRightsPacket request = new GrantUserRightsPacket();
            request.AgentData.AgentID = Client.Self.AgentID;
            request.AgentData.SessionID = Client.Self.SessionID;
            request.Rights = new GrantUserRightsPacket.RightsBlock[1];
            request.Rights[0] = new GrantUserRightsPacket.RightsBlock();
            request.Rights[0].AgentRelated = friendID;
            request.Rights[0].RelatedRights = (int)rights;

            Client.Network.SendPacket(request);
        }

        /// <summary>
        /// Use to map a friends location on the grid.
        /// </summary>
        /// <param name="friendID">Friends UUID to find</param>
        /// <remarks><seealso cref="E:OnFriendFound"/></remarks>
        public void MapFriend(UUID friendID)
        {
            FindAgentPacket stalk = new FindAgentPacket();
            stalk.AgentBlock.Hunter = Client.Self.AgentID;
            stalk.AgentBlock.Prey = friendID;
            stalk.AgentBlock.SpaceIP = 0; // Will be filled in by the simulator
            stalk.LocationBlock = new FindAgentPacket.LocationBlockBlock[1];
            stalk.LocationBlock[0] = new FindAgentPacket.LocationBlockBlock();
            stalk.LocationBlock[0].GlobalX = 0.0; // Filled in by the simulator
            stalk.LocationBlock[0].GlobalY = 0.0;

            Client.Network.SendPacket(stalk);
        }

        /// <summary>
        /// Use to track a friends movement on the grid
        /// </summary>
        /// <param name="friendID">Friends Key</param>
        public void TrackFriend(UUID friendID)
        {
            TrackAgentPacket stalk = new TrackAgentPacket();
            stalk.AgentData.AgentID = Client.Self.AgentID;
            stalk.AgentData.SessionID = Client.Self.SessionID;
            stalk.TargetData.PreyID = friendID;

            Client.Network.SendPacket(stalk);
        }

        /// <summary>
        /// Ask for a notification of friend's online status
        /// </summary>
        /// <param name="friendID">Friend's UUID</param>
        public void RequestOnlineNotification(UUID friendID)
        {
            GenericMessagePacket gmp = new GenericMessagePacket();

            gmp.AgentData.AgentID = Client.Self.AgentID;
            gmp.AgentData.SessionID = Client.Self.SessionID;
            gmp.AgentData.TransactionID = UUID.Zero;

            gmp.MethodData.Method = Utils.StringToBytes("requestonlinenotification");
            gmp.MethodData.Invoice = UUID.Zero;
            gmp.ParamList = new GenericMessagePacket.ParamListBlock[1];
            gmp.ParamList[0] = new GenericMessagePacket.ParamListBlock();
            gmp.ParamList[0].Parameter = Utils.StringToBytes(friendID.ToString());

            Client.Network.SendPacket(gmp);
        }

        #endregion

        #region Internal events

        private void Network_OnConnect(object sender, LoginProgressEventArgs e)
        {
            if (e.Status != LoginStatus.Success)
            {
                return;
            }

            List<UUID> names = new List<UUID>();

            if (FriendList.Count > 0)
            {
                FriendList.ForEach(
                    delegate(KeyValuePair<UUID, FriendInfo> kvp)
                    {
                        if (String.IsNullOrEmpty(kvp.Value.Name))
                            names.Add(kvp.Key);
                    }
                );

                Client.Avatars.RequestAvatarNames(names);
            }
        }


        /// <summary>
        /// This handles the asynchronous response of a RequestAvatarNames call.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e">names cooresponding to the the list of IDs sent the the RequestAvatarNames call.</param>
        private void Avatars_OnAvatarNames(object sender, UUIDNameReplyEventArgs e)
        {
            Dictionary<UUID, string> newNames = new Dictionary<UUID, string>();

            foreach (KeyValuePair<UUID, string> kvp in e.Names)
            {
                FriendInfo friend;
                lock (FriendList.Dictionary)
                {
                    if (FriendList.TryGetValue(kvp.Key, out friend))
                    {
                        if (friend.Name == null)
                            newNames.Add(kvp.Key, e.Names[kvp.Key]);

                        friend.Name = e.Names[kvp.Key];
                        FriendList[kvp.Key] = friend;
                    }
                }
            }

            if (newNames.Count > 0 && m_FriendNames != null)
            {
                OnFriendNames(new FriendNamesEventArgs(newNames));
            }
        }
        #endregion

        #region Packet Handlers

        /// <summary>Process an incoming packet and raise the appropriate events</summary>
        /// <param name="sender">The sender</param>
        /// <param name="e">The EventArgs object containing the packet data</param>
        protected void OnlineNotificationHandler(object sender, PacketReceivedEventArgs e)
        {
            Packet packet = e.Packet;
            if (packet.Type == PacketType.OnlineNotification)
            {
                OnlineNotificationPacket notification = ((OnlineNotificationPacket)packet);

                foreach (OnlineNotificationPacket.AgentBlockBlock block in notification.AgentBlock)
                {
                    FriendInfo friend;
                    lock (FriendList.Dictionary)
                    {
                        if (!FriendList.ContainsKey(block.AgentID))
                        {
                            friend = new FriendInfo(block.AgentID, FriendRights.CanSeeOnline,
                                FriendRights.CanSeeOnline);
                            FriendList.Add(block.AgentID, friend);
                        }
                        else
                        {
                            friend = FriendList[block.AgentID];
                        }
                    }

                    bool doNotify = !friend.IsOnline;
                    friend.IsOnline = true;

                    if (m_FriendOnline != null && doNotify)
                    {
                        OnFriendOnline(new FriendInfoEventArgs(friend));
                    }
                }
            }
        }

        /// <summary>Process an incoming packet and raise the appropriate events</summary>
        /// <param name="sender">The sender</param>
        /// <param name="e">The EventArgs object containing the packet data</param>
        protected void OfflineNotificationHandler(object sender, PacketReceivedEventArgs e)
        {
            Packet packet = e.Packet;
            if (packet.Type == PacketType.OfflineNotification)
            {
                OfflineNotificationPacket notification = (OfflineNotificationPacket)packet;

                foreach (OfflineNotificationPacket.AgentBlockBlock block in notification.AgentBlock)
                {
                    FriendInfo friend = new FriendInfo(block.AgentID, FriendRights.CanSeeOnline, FriendRights.CanSeeOnline);

                    lock (FriendList.Dictionary)
                    {
                        if (!FriendList.Dictionary.ContainsKey(block.AgentID))
                            FriendList.Dictionary[block.AgentID] = friend;

                        friend = FriendList.Dictionary[block.AgentID];
                    }

                    friend.IsOnline = false;

                    if (m_FriendOffline != null)
                    {
                        OnFriendOffline(new FriendInfoEventArgs(friend));
                    }
                }
            }
        }


        /// <summary>Process an incoming packet and raise the appropriate events</summary>
        /// <param name="sender">The sender</param>
        /// <param name="e">The EventArgs object containing the packet data</param>
        private void ChangeUserRightsHandler(object sender, PacketReceivedEventArgs e)
        {
            Packet packet = e.Packet;
            if (packet.Type == PacketType.ChangeUserRights)
            {
                FriendInfo friend;
                ChangeUserRightsPacket rights = (ChangeUserRightsPacket)packet;

                foreach (ChangeUserRightsPacket.RightsBlock block in rights.Rights)
                {
                    FriendRights newRights = (FriendRights)block.RelatedRights;
                    if (FriendList.TryGetValue(block.AgentRelated, out friend))
                    {
                        friend.TheirFriendRights = newRights;
                        if (m_FriendRights != null)
                        {
                            OnFriendRights(new FriendInfoEventArgs(friend));
                        }
                    }
                    else if (block.AgentRelated == Client.Self.AgentID)
                    {
                        if (FriendList.TryGetValue(rights.AgentData.AgentID, out friend))
                        {
                            friend.MyFriendRights = newRights;
                            if (m_FriendRights != null)
                            {
                                OnFriendRights(new FriendInfoEventArgs(friend));
                            }
                        }
                    }
                }
            }
        }

        /// <summary>Process an incoming packet and raise the appropriate events</summary>
        /// <param name="sender">The sender</param>
        /// <param name="e">The EventArgs object containing the packet data</param>
        public void OnFindAgentReplyHandler(object sender, PacketReceivedEventArgs e)
        {
            if (m_FriendFound != null)
            {
                Packet packet = e.Packet;
                FindAgentPacket reply = (FindAgentPacket)packet;

                float x, y;
                UUID prey = reply.AgentBlock.Prey;
                ulong regionHandle = Helpers.GlobalPosToRegionHandle((float)reply.LocationBlock[0].GlobalX,
                    (float)reply.LocationBlock[0].GlobalY, out x, out y);
                Vector3 xyz = new Vector3(x, y, 0f);

                OnFriendFoundReply(new FriendFoundReplyEventArgs(prey, regionHandle, xyz));
            }
        }

        #endregion

        private void Self_IM(object sender, InstantMessageEventArgs e)
        {
            if (e.IM.Dialog == InstantMessageDialog.FriendshipOffered)
            {
                if (m_FriendshipOffered != null)
                {
                    if (FriendRequests.ContainsKey(e.IM.FromAgentID))
                        FriendRequests[e.IM.FromAgentID] = e.IM.IMSessionID;
                    else
                        FriendRequests.Add(e.IM.FromAgentID, e.IM.IMSessionID);

                    OnFriendshipOffered(new FriendshipOfferedEventArgs(e.IM.FromAgentID, e.IM.FromAgentName, e.IM.IMSessionID));
                }
            }
            else if (e.IM.Dialog == InstantMessageDialog.FriendshipAccepted)
            {
                FriendInfo friend = new FriendInfo(e.IM.FromAgentID, FriendRights.CanSeeOnline,
                    FriendRights.CanSeeOnline);
                friend.Name = e.IM.FromAgentName;
                lock (FriendList.Dictionary) FriendList[friend.UUID] = friend;

                if (m_FriendshipResponse != null)
                {
                    OnFriendshipResponse(new FriendshipResponseEventArgs(e.IM.FromAgentID, e.IM.FromAgentName, true));
                }
                RequestOnlineNotification(e.IM.FromAgentID);
            }
            else if (e.IM.Dialog == InstantMessageDialog.FriendshipDeclined)
            {
                if (m_FriendshipResponse != null)
                {
                    OnFriendshipResponse(new FriendshipResponseEventArgs(e.IM.FromAgentID, e.IM.FromAgentName, false));
                }
            }
        }

        /// <summary>
        /// Populate FriendList <seealso cref="InternalDictionary"/> with data from the login reply
        /// </summary>
        /// <param name="loginSuccess">true if login was successful</param>
        /// <param name="redirect">true if login request is requiring a redirect</param>
        /// <param name="message">A string containing the response to the login request</param>
        /// <param name="reason">A string containing the reason for the request</param>
        /// <param name="replyData">A <seealso cref="LoginResponseData"/> object containing the decoded 
        /// reply from the login server</param>
        private void Network_OnLoginResponse(bool loginSuccess, bool redirect, string message, string reason,
            LoginResponseData replyData)
        {
            int uuidLength = UUID.Zero.ToString().Length;

            if (loginSuccess && replyData.BuddyList != null)
            {
                foreach (BuddyListEntry buddy in replyData.BuddyList)
                {
                    UUID bubid;
                    string id = buddy.buddy_id.Length > uuidLength ? buddy.buddy_id.Substring(0, uuidLength) : buddy.buddy_id;
                    if (UUID.TryParse(id, out bubid))
                    {
                        lock (FriendList.Dictionary)
                        {
                            if (!FriendList.ContainsKey(bubid))
                            {
                                FriendList[bubid] = new FriendInfo(bubid, 
                                    (FriendRights)buddy.buddy_rights_given,
                                    (FriendRights)buddy.buddy_rights_has);
                            }
                        }
                    }
                }
            }
        }
    }
    #region EventArgs

    /// <summary>Contains information on a member of our friends list</summary>
    public class FriendInfoEventArgs : EventArgs
    {
        private readonly FriendInfo m_Friend;

        /// <summary>Get the FriendInfo</summary>
        public FriendInfo Friend { get { return m_Friend; } }

        /// <summary>
        /// Construct a new instance of the FriendInfoEventArgs class
        /// </summary>
        /// <param name="friend">The FriendInfo</param>
        public FriendInfoEventArgs(FriendInfo friend)
        {
            this.m_Friend = friend;
        }
    }

    /// <summary>Contains Friend Names</summary>
    public class FriendNamesEventArgs : EventArgs
    {
        private readonly Dictionary<UUID, string> m_Names;

        /// <summary>A dictionary where the Key is the ID of the Agent, 
        /// and the Value is a string containing their name</summary>
        public Dictionary<UUID, string> Names { get { return m_Names; } }

        /// <summary>
        /// Construct a new instance of the FriendNamesEventArgs class
        /// </summary>
        /// <param name="names">A dictionary where the Key is the ID of the Agent, 
        /// and the Value is a string containing their name</param>
        public FriendNamesEventArgs(Dictionary<UUID, string> names)
        {
            this.m_Names = names;
        }
    }

    /// <summary>Sent when another agent requests a friendship with our agent</summary>
    public class FriendshipOfferedEventArgs : EventArgs
    {
        private readonly UUID m_AgentID;
        private readonly string m_AgentName;
        private readonly UUID m_SessionID;

        /// <summary>Get the ID of the agent requesting friendship</summary>
        public UUID AgentID { get { return m_AgentID; } }
        /// <summary>Get the name of the agent requesting friendship</summary>
        public string AgentName { get { return m_AgentName; } }
        /// <summary>Get the ID of the session, used in accepting or declining the 
        /// friendship offer</summary>
        public UUID SessionID { get { return m_SessionID; } }

        /// <summary>
        /// Construct a new instance of the FriendshipOfferedEventArgs class
        /// </summary>
        /// <param name="agentID">The ID of the agent requesting friendship</param>
        /// <param name="agentName">The name of the agent requesting friendship</param>
        /// <param name="imSessionID">The ID of the session, used in accepting or declining the 
        /// friendship offer</param>
        public FriendshipOfferedEventArgs(UUID agentID, string agentName, UUID imSessionID)
        {
            this.m_AgentID = agentID;
            this.m_AgentName = agentName;
            this.m_SessionID = imSessionID;
        }
    }

    /// <summary>A response containing the results of our request to form a friendship with another agent</summary>
    public class FriendshipResponseEventArgs : EventArgs
    {
        private readonly UUID m_AgentID;
        private readonly string m_AgentName;
        private readonly bool m_Accepted;

        /// <summary>Get the ID of the agent we requested a friendship with</summary>
        public UUID AgentID { get { return m_AgentID; } }
        /// <summary>Get the name of the agent we requested a friendship with</summary>
        public string AgentName { get { return m_AgentName; } }
        /// <summary>true if the agent accepted our friendship offer</summary>
        public bool Accepted { get { return m_Accepted; } }

        /// <summary>
        /// Construct a new instance of the FriendShipResponseEventArgs class
        /// </summary>
        /// <param name="agentID">The ID of the agent we requested a friendship with</param>
        /// <param name="agentName">The name of the agent we requested a friendship with</param>
        /// <param name="accepted">true if the agent accepted our friendship offer</param>
        public FriendshipResponseEventArgs(UUID agentID, string agentName, bool accepted)
        {
            this.m_AgentID = agentID;
            this.m_AgentName = agentName;
            this.m_Accepted = accepted;
        }
    }

    /// <summary>Contains data sent when a friend terminates a friendship with us</summary>
    public class FriendshipTerminatedEventArgs : EventArgs
    {
        private readonly UUID m_AgentID;
        private readonly string m_AgentName;

        /// <summary>Get the ID of the agent that terminated the friendship with us</summary>
        public UUID AgentID { get { return m_AgentID; } }
        /// <summary>Get the name of the agent that terminated the friendship with us</summary>
        public string AgentName { get { return m_AgentName; } }

        /// <summary>
        /// Construct a new instance of the FrindshipTerminatedEventArgs class
        /// </summary>
        /// <param name="agentID">The ID of the friend who terminated the friendship with us</param>
        /// <param name="agentName">The name of the friend who terminated the friendship with us</param>
        public FriendshipTerminatedEventArgs(UUID agentID, string agentName)
        {
            this.m_AgentID = agentID;
            this.m_AgentName = agentName;
        }
    }

    /// <summary>
    /// Data sent in response to a <see cref="FindFriend"/> request which contains the information to allow us to map the friends location
    /// </summary>
    public class FriendFoundReplyEventArgs : EventArgs
    {
        private readonly UUID m_AgentID;
        private readonly ulong m_RegionHandle;
        private readonly Vector3 m_Location;

        /// <summary>Get the ID of the agent we have received location information for</summary>
        public UUID AgentID { get { return m_AgentID; } }
        /// <summary>Get the region handle where our mapped friend is located</summary>
        public ulong RegionHandle { get { return m_RegionHandle; } }
        /// <summary>Get the simulator local position where our friend is located</summary>
        public Vector3 Location { get { return m_Location; } }

        /// <summary>
        /// Construct a new instance of the FriendFoundReplyEventArgs class
        /// </summary>
        /// <param name="agentID">The ID of the agent we have requested location information for</param>
        /// <param name="regionHandle">The region handle where our friend is located</param>
        /// <param name="location">The simulator local position our friend is located</param>
        public FriendFoundReplyEventArgs(UUID agentID, ulong regionHandle, Vector3 location)
        {
            this.m_AgentID = agentID;
            this.m_RegionHandle = regionHandle;
            this.m_Location = location;
        }
    }
    #endregion
}