/*
 * Copyright (c) Contributors, http://opensimulator.org/
 * See CONTRIBUTORS.TXT for a full list of copyright holders.
 *
 * 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.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSimulator Project 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 DEVELOPERS ``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 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.Reflection;
using System.Collections.Generic;
using log4net;
using OpenMetaverse;
using OpenMetaverse.StructuredData;

namespace OpenSim.Framework
{
    /// <summary>
    /// Circuit data for an agent.  Connection information shared between
    /// regions that accept UDP connections from a client
    /// </summary>
    public class AgentCircuitData
    {
        private static readonly ILog m_log = LogManager.GetLogger( MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Avatar Unique Agent Identifier
        /// </summary>
        public UUID AgentID;

        /// <summary>
        /// Avatar's Appearance
        /// </summary>
        public AvatarAppearance Appearance;

        /// <summary>
        /// Agent's root inventory folder
        /// </summary>
        public UUID BaseFolder;

        /// <summary>
        /// Base Caps path for user
        /// </summary>
        public string CapsPath = String.Empty;

        /// <summary>
        /// Seed caps for neighbor regions that the user can see into
        /// </summary>
        public Dictionary<ulong, string> ChildrenCapSeeds;

        /// <summary>
        /// Root agent, or Child agent
        /// </summary>
        public bool child;

        /// <summary>
        /// Number given to the client when they log-in that they provide
        /// as credentials to the UDP server
        /// </summary>
        public uint circuitcode;

        /// <summary>
        /// How this agent got here
        /// </summary>
        public uint teleportFlags;

        /// <summary>
        /// Agent's account first name
        /// </summary>
        public string firstname;
        public UUID InventoryFolder;

        /// <summary>
        /// Agent's account last name
        /// </summary>
        public string lastname;

        /// <summary>
        /// Agent's full name.
        /// </summary>
        public string Name { get { return string.Format("{0} {1}", firstname, lastname); } }

        /// <summary>
        /// Random Unique GUID for this session.  Client gets this at login and it's
        /// only supposed to be disclosed over secure channels
        /// </summary>
        public UUID SecureSessionID;

        /// <summary>
        /// Non secure Session ID
        /// </summary>
        public UUID SessionID;

        /// <summary>
        /// Hypergrid service token; generated by the user domain, consumed by the receiving grid.
        /// There is one such unique token for each grid visited.
        /// </summary>
        public string ServiceSessionID = string.Empty;

        /// <summary>
        /// The client's IP address, as captured by the login service
        /// </summary>
        public string IPAddress;

        /// <summary>
        /// Viewer's version string as reported by the viewer at login
        /// </summary>
        private string m_viewerInternal;

        /// <summary>
        /// Viewer's version string
        /// </summary>
        public string Viewer
        {
            set { m_viewerInternal = value; }

            // Try to return consistent viewer string taking into account
            // that viewers have chaagned how version is reported
            // See http://opensimulator.org/mantis/view.php?id=6851
            get
            {
                // Old style version string contains viewer name followed by a space followed by a version number
                if (m_viewerInternal == null || m_viewerInternal.Contains(" "))
                {
                    return m_viewerInternal;
                }
                else // New style version contains no spaces, just version number
                {
                    return Channel + " " + m_viewerInternal;
                }
            }
        }

        /// <summary>
        /// The channel strinf sent by the viewer at login
        /// </summary>
        public string Channel;

        /// <summary>
        /// The Mac address as reported by the viewer at login
        /// </summary>
        public string Mac;

        /// <summary>
        /// The id0 as reported by the viewer at login
        /// </summary>
        public string Id0;

        /// <summary>
        /// Position the Agent's Avatar starts in the region
        /// </summary>
        public Vector3 startpos;
        public float startfar = -1.0f;

        public Dictionary<string, object> ServiceURLs;

        public AgentCircuitData()
        {
        }

        /// <summary>
        /// Pack AgentCircuitData into an OSDMap for transmission over LLSD XML or LLSD json
        /// </summary>
        /// <returns>map of the agent circuit data</returns>
        public OSDMap PackAgentCircuitData(EntityTransferContext ctx)
        {
            OSDMap args = new OSDMap();
            args["agent_id"] = OSD.FromUUID(AgentID);
            args["base_folder"] = OSD.FromUUID(BaseFolder);
            args["caps_path"] = OSD.FromString(CapsPath);

            if (ChildrenCapSeeds != null)
            {
                OSDArray childrenSeeds = new OSDArray(ChildrenCapSeeds.Count);
                foreach (KeyValuePair<ulong, string> kvp in ChildrenCapSeeds)
                {
                    OSDMap pair = new OSDMap();
                    pair["handle"] = OSD.FromString(kvp.Key.ToString());
                    pair["seed"] = OSD.FromString(kvp.Value);
                    childrenSeeds.Add(pair);
                }
                if (ChildrenCapSeeds.Count > 0)
                    args["children_seeds"] = childrenSeeds;
            }
            args["child"] = OSD.FromBoolean(child);
            args["circuit_code"] = OSD.FromString(circuitcode.ToString());
            args["first_name"] = OSD.FromString(firstname);
            args["last_name"] = OSD.FromString(lastname);
            args["inventory_folder"] = OSD.FromUUID(InventoryFolder);
            args["secure_session_id"] = OSD.FromUUID(SecureSessionID);
            args["session_id"] = OSD.FromUUID(SessionID);

            args["service_session_id"] = OSD.FromString(ServiceSessionID);
            args["start_pos"] = OSD.FromString(startpos.ToString());
            args["client_ip"] = OSD.FromString(IPAddress);
            args["viewer"] = OSD.FromString(Viewer);
            args["channel"] = OSD.FromString(Channel);
            args["mac"] = OSD.FromString(Mac);
            args["id0"] = OSD.FromString(Id0);
            if(startfar > 0)
                args["far"] = OSD.FromReal(startfar);

            if (Appearance != null)
            {
                args["appearance_serial"] = OSD.FromInteger(Appearance.Serial);

                OSDMap appmap = Appearance.Pack(ctx);
                args["packed_appearance"] = appmap;
            }

            // Old, bad  way. Keeping it fow now for backwards compatibility
            // OBSOLETE -- soon to be deleted
            if (ServiceURLs != null && ServiceURLs.Count > 0)
            {
                OSDArray urls = new OSDArray(ServiceURLs.Count * 2);
                foreach (KeyValuePair<string, object> kvp in ServiceURLs)
                {
                    //System.Console.WriteLine("XXX " + kvp.Key + "=" + kvp.Value);
                    urls.Add(OSD.FromString(kvp.Key));
                    urls.Add(OSD.FromString((kvp.Value == null) ? string.Empty : kvp.Value.ToString()));
                }
                args["service_urls"] = urls;
            }

            // again, this time the right way
            if (ServiceURLs != null && ServiceURLs.Count > 0)
            {
                OSDMap urls = new OSDMap();
                foreach (KeyValuePair<string, object> kvp in ServiceURLs)
                {
                    //System.Console.WriteLine("XXX " + kvp.Key + "=" + kvp.Value);
                    urls[kvp.Key] = OSD.FromString((kvp.Value == null) ? string.Empty : kvp.Value.ToString());
                }
                args["serviceurls"] = urls;
            }


            return args;
        }

        /// <summary>
        /// Unpack agent circuit data map into an AgentCiruitData object
        /// </summary>
        /// <param name="args"></param>
        public void UnpackAgentCircuitData(OSDMap args)
        {
            OSD tmpOSD;
            if (args.TryGetValue("agent_id", out tmpOSD))
                AgentID = tmpOSD.AsUUID();
            if (args.TryGetValue("base_folder", out tmpOSD))
                BaseFolder =tmpOSD.AsUUID();
            if (args.TryGetValue("caps_path", out tmpOSD))
                CapsPath = tmpOSD.AsString();

            if (args.TryGetValue("children_seeds", out tmpOSD) && tmpOSD is OSDArray)
            {
                OSDArray childrenSeeds = (OSDArray)tmpOSD;
                ChildrenCapSeeds = new Dictionary<ulong, string>();
                foreach (OSD o in childrenSeeds)
                {
                    if (o.Type == OSDType.Map)
                    {
                        ulong handle = 0;
                        string seed = "";
                        OSDMap pair = (OSDMap)o;
                        if (pair.TryGetValue("handle", out tmpOSD))
                        {
                            if (!UInt64.TryParse(tmpOSD.AsString(), out handle))
                                continue;
                        }
                        if (!ChildrenCapSeeds.ContainsKey(handle))
                        {
                            if (pair.TryGetValue("seed", out tmpOSD))
                            {
                                seed = tmpOSD.AsString();
                                ChildrenCapSeeds.Add(handle, seed);
                            }
                        }
                    }
                }
            }
            else
                ChildrenCapSeeds = new Dictionary<ulong, string>();

            if (args.TryGetValue("child", out tmpOSD))
                child = tmpOSD.AsBoolean();
            if (args.TryGetValue("circuit_code", out tmpOSD))
                UInt32.TryParse(tmpOSD.AsString(), out circuitcode);
            if (args.TryGetValue("first_name", out tmpOSD))
                firstname = tmpOSD.AsString();
            if (args.TryGetValue("last_name", out tmpOSD))
                lastname = tmpOSD.AsString();
            if (args.TryGetValue("inventory_folder", out tmpOSD))
                InventoryFolder = tmpOSD.AsUUID();
            if (args.TryGetValue("secure_session_id", out tmpOSD))
                SecureSessionID = tmpOSD.AsUUID();
            if (args.TryGetValue("session_id", out tmpOSD))
                SessionID = tmpOSD.AsUUID();
            if (args.TryGetValue("service_session_id", out tmpOSD))
                ServiceSessionID = tmpOSD.AsString();
            if (args.TryGetValue("client_ip", out tmpOSD))
                IPAddress = tmpOSD.AsString();
            if (args.TryGetValue("viewer", out tmpOSD))
                Viewer = tmpOSD.AsString();
            if (args.TryGetValue("channel", out tmpOSD))
                Channel = tmpOSD.AsString();
            if (args.TryGetValue("mac", out tmpOSD))
                Mac = tmpOSD.AsString();
            if (args.TryGetValue("id0", out tmpOSD))
                Id0 = tmpOSD.AsString();
            if (args.TryGetValue("teleport_flags", out tmpOSD))
                teleportFlags = tmpOSD.AsUInteger();

            if (args.TryGetValue("start_pos", out tmpOSD))
                Vector3.TryParse(tmpOSD.AsString(), out startpos);

            if(args.TryGetValue("far", out tmpOSD))
                startfar = (float)tmpOSD.AsReal();

            //m_log.InfoFormat("[AGENTCIRCUITDATA]: agentid={0}, child={1}, startpos={2}", AgentID, child, startpos);

            try
            {
                // Unpack various appearance elements
                Appearance = new AvatarAppearance();

                // Eventually this code should be deprecated, use full appearance
                // packing in packed_appearance
                if (args.TryGetValue("appearance_serial", out tmpOSD))
                    Appearance.Serial = tmpOSD.AsInteger();

                if (args.TryGetValue("packed_appearance", out tmpOSD) && (tmpOSD is OSDMap))
                {
                    Appearance.Unpack((OSDMap)tmpOSD);
//                    m_log.InfoFormat("[AGENTCIRCUITDATA] unpacked appearance");
                }
                else
                {
                    m_log.Warn("[AGENTCIRCUITDATA]: failed to find a valid packed_appearance");
                }
            }
            catch (Exception e)
            {
                m_log.ErrorFormat("[AGENTCIRCUITDATA] failed to unpack appearance; {0}",e.Message);
            }

            ServiceURLs = new Dictionary<string, object>();
            // Try parse the new way, OSDMap
            if (args.TryGetValue("serviceurls", out tmpOSD) && (tmpOSD is OSDMap))
            {
                OSDMap urls = (OSDMap)tmpOSD;
                foreach (KeyValuePair<String, OSD> kvp in urls)
                {
                    ServiceURLs[kvp.Key] = kvp.Value;
                    //System.Console.WriteLine("XXX " + kvp.Key + "=" + ServiceURLs[kvp.Key]);
                }
            }
            // else try the old way, OSDArray
            // OBSOLETE -- soon to be deleted
            else if (args.TryGetValue("service_urls", out tmpOSD) && (tmpOSD is OSDArray))
            {
                OSDArray urls = (OSDArray)tmpOSD;
                OSD tmpOSDb;
                for (int i = 0; i < urls.Count - 1; i += 2)
                {
                    tmpOSD = urls[i];
                    tmpOSDb = urls[i + 1];
                    ServiceURLs[tmpOSD.AsString()] = tmpOSDb.AsString();
                    //System.Console.WriteLine("XXX " + urls[i * 2].AsString() + "=" + urls[(i * 2) + 1].AsString());
                }
            }
        }
    }
}
