/*
 * 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 OpenSim 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.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Reflection;
using System.Threading;
using System.Web;

using OpenMetaverse;
using OpenMetaverse.StructuredData;
using OpenMetaverse.Packets;

using log4net;
using Nini.Config;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Region.Environment.Interfaces;
using OpenSim.Region.Environment.Scenes;

using LLSD = OpenMetaverse.StructuredData.LLSD;
using LLSDMap = OpenMetaverse.StructuredData.LLSDMap;
using LLSDArray = OpenMetaverse.StructuredData.LLSDArray;

namespace OpenSim.Region.Environment.Modules.InterGrid
{
    public struct OGPState
    {
        public string first_name;
        public string last_name;
        public UUID agent_id;
        public UUID local_agent_id;
        public UUID region_id;
        public uint circuit_code;
        public UUID secure_session_id;
        public UUID session_id;
        public bool agent_access;
        public string sim_access;
        public uint god_level;
        public bool god_overide;
        public bool identified;
        public bool transacted;
        public bool age_verified;
        public bool allow_redirect;
        public int limited_to_estate;
        public string inventory_host;
        public bool src_can_see_mainland;
        public int src_estate_id;
        public int src_version;
        public int src_parent_estate_id;
        public bool visible_to_parent;
        public string teleported_into_region;
    }
    
    public class OpenGridProtocolModule : IRegionModule
    {
        private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        private List<Scene> m_scene = new List<Scene>();

        private Dictionary<string, AgentCircuitData> CapsLoginID = new Dictionary<string, AgentCircuitData>();
        private Dictionary<UUID, OGPState> m_OGPState = new Dictionary<UUID, OGPState>();
        private Dictionary<string, string> m_loginToRegionState = new Dictionary<string, string>();
        

        private string LastNameSuffix = "_EXTERNAL";
        private string FirstNamePrefix = "";
        private string httpsCN = "";
        private bool httpSSL = false;
        private uint httpsslport = 0;
        private bool GridMode = false;

        #region IRegionModule Members

        public void Initialise(Scene scene, IConfigSource config)
        {
            bool enabled = false;
            IConfig cfg = null;
            IConfig httpcfg = null;
            IConfig startupcfg = null;
            try
            {
                cfg = config.Configs["OpenGridProtocol"];
            } catch (NullReferenceException)
            {
                enabled = false;
            }

            try
            {
                httpcfg = config.Configs["Network"];
            }
            catch (NullReferenceException)
            {
               
            }
            try
            {
                startupcfg = config.Configs["Startup"];
            }
            catch (NullReferenceException)
            {

            }

            if (startupcfg != null)
            {
                GridMode = enabled = startupcfg.GetBoolean("gridmode", false);
            }

            if (cfg != null)
            {
                enabled = cfg.GetBoolean("ogp_enabled", false);
                LastNameSuffix = cfg.GetString("ogp_lastname_suffix", "_EXTERNAL");
                FirstNamePrefix = cfg.GetString("ogp_firstname_prefix", "");
                if (enabled)
                {
                    m_log.Warn("[OGP]: Open Grid Protocol is on, Listening for Clients on /agent/");
                    lock (m_scene)
                    {
                        if (m_scene.Count == 0)
                        {
                            scene.AddLLSDHandler("/agent/", ProcessAgentDomainMessage);
                            scene.AddLLSDHandler("/", ProcessRegionDomainSeed);
                            try
                            {
                                ServicePointManager.ServerCertificateValidationCallback += customXertificateValidation;
                            }
                            catch (NotImplementedException)
                            {
                                try
                                {
#pragma warning disable 0612, 0618
                                    // Mono does not implement the ServicePointManager.ServerCertificateValidationCallback yet!  Don't remove this!
                                    ServicePointManager.CertificatePolicy = new MonoCert();
#pragma warning restore 0612, 0618
                                }
                                catch (Exception)
                                {
                                    m_log.Error("[OGP]: Certificate validation handler change not supported.  You may get ssl certificate validation errors teleporting from your region to some SSL regions.");
                                }
                            }

                        }
                        // can't pick the region 'agent' because it would conflict with our agent domain handler
                        // a zero length region name would conflict with are base region seed cap
                        if (!SceneListDuplicateCheck(scene.RegionInfo.RegionName) && scene.RegionInfo.RegionName.ToLower() != "agent" && scene.RegionInfo.RegionName.Length > 0)
                        {
                            scene.AddLLSDHandler("/" + HttpUtility.UrlPathEncode(scene.RegionInfo.RegionName.ToLower()),ProcessRegionDomainSeed);
                        }

                        if (!m_scene.Contains(scene))
                            m_scene.Add(scene);
                    }
                }
            }
            lock (m_scene)
            {
                if (m_scene.Count == 1)
                {
                    if (httpcfg != null)
                    {
                        httpSSL = httpcfg.GetBoolean("http_listener_ssl", false);
                        httpsCN = httpcfg.GetString("http_listener_cn", scene.RegionInfo.ExternalHostName);
                        if (httpsCN.Length == 0)
                            httpsCN = scene.RegionInfo.ExternalHostName;
                        httpsslport = (uint)httpcfg.GetInt("http_listener_sslport",((int)scene.RegionInfo.HttpPort + 1));
                    }
                }
            }
        }
        
        public void PostInitialise()
        {
        }

        public void Close()
        {
            //scene.EventManager.OnAvatarEnteringNewParcel -= AvatarEnteringParcel;
        }

        public string Name
        {
            get { return "OpenGridProtocolModule"; }
        }

        public bool IsSharedModule
        {
            get { return true; }
        }

        #endregion

        public LLSD ProcessRegionDomainSeed(string path, LLSD request, string endpoint)
        {
            string[] pathSegments = path.Split('/');
            
            if (pathSegments.Length <= 1)
            {
                return GenerateNoHandlerMessage();

            }
            
            return GenerateRezAvatarRequestMessage(pathSegments[1]);
            
            

            //m_log.InfoFormat("[OGP]: path {0}, segments {1} segment[1] {2} Last segment {3}",
            //                 path, pathSegments.Length, pathSegments[1], pathSegments[pathSegments.Length - 1]);
            //return new LLSDMap();

        }

        public LLSD ProcessAgentDomainMessage(string path, LLSD request, string endpoint)
        {
            // /agent/*

            string[] pathSegments = path.Split('/');
            if (pathSegments.Length <= 1)
            {
                return GenerateNoHandlerMessage();
                
            }
            if (pathSegments[0].Length == 0 && pathSegments[1].Length == 0)
            {
                return GenerateRezAvatarRequestMessage("");
            }
            m_log.InfoFormat("[OGP]: path {0}, segments {1} segment[1] {2} Last segment {3}",
                             path, pathSegments.Length, pathSegments[1], pathSegments[pathSegments.Length - 1]);

            switch (pathSegments[pathSegments.Length - 1])
            {
                case "rez_avatar":
                    return RezAvatarMethod(path, request);
                    //break;
                case "derez_avatar":
                    return DerezAvatarMethod(path, request);
                    //break;

            }
            if (path.Length < 2)
            {
                return GenerateNoHandlerMessage();
            }

            switch (pathSegments[pathSegments.Length - 2] + "/" + pathSegments[pathSegments.Length - 1])
            {
                case "rez_avatar/rez":
                    return RezAvatarMethod(path, request);
                    //break;
                case "rez_avatar/request":
                    return RequestRezAvatarMethod(path, request);
                case "rez_avatar/place":
                    return RequestRezAvatarMethod(path, request);
                case "rez_avatar/derez":
                    return DerezAvatarMethod(path, request);
                    //break;
                default:
                    return GenerateNoHandlerMessage();
            }
            //return null;
        }

        private LLSD GenerateRezAvatarRequestMessage(string regionname)
        {
            Scene region = null;
            bool usedroot = false;

            if (regionname.Length == 0)
            {
                region = GetRootScene();
                usedroot = true;
            }
            else
            {
                region = GetScene(HttpUtility.UrlDecode(regionname).ToLower());
            }

            // this shouldn't happen since we don't listen for a region that is down..   but 
            // it might if the region was taken down or is in the middle of restarting

            if (region == null)
            {
                region = GetRootScene();
                usedroot = true;
            }
            
            UUID statekeeper = UUID.Random();

            
            

            RegionInfo reg = region.RegionInfo;

            LLSDMap responseMap = new LLSDMap();
            string rezHttpProtocol = "http://";
            //string regionCapsHttpProtocol = "http://";
            string httpaddr = reg.ExternalHostName;
            string urlport = reg.HttpPort.ToString();
            string requestpath = "/agent/" + statekeeper + "/rez_avatar/request";

            if (!usedroot)
            {
                lock (m_loginToRegionState)
                {
                    if (!m_loginToRegionState.ContainsKey(requestpath))
                    {
                        m_loginToRegionState.Add(requestpath, region.RegionInfo.RegionName.ToLower());
                    }
                }
            }

            if (httpSSL)
            {
                rezHttpProtocol = "https://";
                //regionCapsHttpProtocol = "https://";
                urlport = httpsslport.ToString();

                if (httpsCN.Length > 0)
                    httpaddr = httpsCN;
            }

            responseMap["connect"] = LLSD.FromBoolean(true);
            LLSDMap capabilitiesMap = new LLSDMap();
            capabilitiesMap["rez_avatar/request"] = LLSD.FromString(rezHttpProtocol + httpaddr + ":" + urlport + requestpath);
            responseMap["capabilities"] = capabilitiesMap;
            
            return responseMap;
        }

        // Using OpenSim.Framework.Communications.Capabilities.Caps here one time..   
        // so the long name is probably better then a using statement
        public void OnRegisterCaps(UUID agentID, OpenSim.Framework.Communications.Capabilities.Caps caps)
        {
            /* If we ever want to register our own caps here....    
             * 
            string capsBase = "/CAPS/" + caps.CapsObjectPath;
            caps.RegisterHandler("CAPNAME",
                                 new RestStreamHandler("POST", capsBase + CAPSPOSTFIX!,
                                                       delegate(string request, string path, string param,
                                                                OSHttpRequest httpRequest, OSHttpResponse httpResponse)
                                                       {
                                                           return METHODHANDLER(request, path, param,
                                                                                         agentID, caps);
                                         }));
            
             *
             */
        }


        public LLSD RequestRezAvatarMethod(string path, LLSD request)
        {
            //System.Console.WriteLine("[REQUESTREZAVATAR]: " + request.ToString());

            LLSDMap requestMap = (LLSDMap)request;


            Scene homeScene = null;

            lock (m_loginToRegionState)
            {
                if (m_loginToRegionState.ContainsKey(path))
                {
                    homeScene = GetScene(m_loginToRegionState[path]);
                    m_loginToRegionState.Remove(path);

                    if (homeScene == null)
                        homeScene = GetRootScene();
                }
                else
                {
                    homeScene = GetRootScene();
                }
            }

            // Homescene is still null, we must have no regions that are up
            if (homeScene == null)
                return GenerateNoHandlerMessage();

            RegionInfo reg = homeScene.RegionInfo;
            ulong regionhandle = GetOSCompatibleRegionHandle(reg);
            //string RegionURI = reg.ServerURI;
            //int RegionPort = (int)reg.HttpPort;

            UUID RemoteAgentID = requestMap["agent_id"].AsUUID();
            
            // will be used in the future.  The client always connects with the aditi agentid currently
            UUID LocalAgentID = RemoteAgentID;

            string FirstName = requestMap["first_name"].AsString();
            string LastName = requestMap["last_name"].AsString();

            FirstName = FirstNamePrefix + FirstName;
            LastName = LastName + LastNameSuffix;

            OGPState userState = GetOGPState(LocalAgentID);

            userState.first_name = requestMap["first_name"].AsString();
            userState.last_name = requestMap["last_name"].AsString();
            userState.age_verified = requestMap["age_verified"].AsBoolean();
            userState.transacted = requestMap["transacted"].AsBoolean();
            userState.agent_access = requestMap["agent_access"].AsBoolean();
            userState.allow_redirect = requestMap["allow_redirect"].AsBoolean();
            userState.identified = requestMap["identified"].AsBoolean();
            userState.god_level = (uint)requestMap["god_level"].AsInteger();
            userState.sim_access = requestMap["sim_access"].AsString();
            userState.agent_id = RemoteAgentID;
            userState.limited_to_estate = requestMap["limited_to_estate"].AsInteger();
            userState.src_can_see_mainland = requestMap["src_can_see_mainland"].AsBoolean();
            userState.src_estate_id = requestMap["src_estate_id"].AsInteger();
            userState.local_agent_id = LocalAgentID;
            userState.teleported_into_region = reg.RegionName.ToLower();

            UpdateOGPState(LocalAgentID, userState);

            LLSDMap responseMap = new LLSDMap();

            if (RemoteAgentID == UUID.Zero)
            {
                responseMap["connect"] = LLSD.FromBoolean(false);
                responseMap["message"] = LLSD.FromString("No agent ID was specified in rez_avatar/request");
                m_log.Error("[OGP]: rez_avatar/request failed because no avatar UUID was provided in the request body");
                return responseMap;
            }

            responseMap["sim_host"] = LLSD.FromString(reg.ExternalHostName);
            
            // DEPRECIATED
            responseMap["sim_ip"] = LLSD.FromString(Util.GetHostFromDNS(reg.ExternalHostName).ToString());
            
            responseMap["connect"] = LLSD.FromBoolean(true);
            responseMap["sim_port"] = LLSD.FromInteger(reg.InternalEndPoint.Port);
            responseMap["region_x"] = LLSD.FromInteger(reg.RegionLocX * (uint)Constants.RegionSize); // LLX
            responseMap["region_y"] = LLSD.FromInteger(reg.RegionLocY * (uint)Constants.RegionSize); // LLY
            responseMap["region_id"] = LLSD.FromUUID(reg.originRegionID);
            responseMap["sim_access"] = LLSD.FromString((reg.RegionSettings.Maturity == 1) ? "Mature" : "PG");

            // Generate a dummy agent for the user so we can get back a CAPS path
            AgentCircuitData agentData = new AgentCircuitData();
            agentData.AgentID = LocalAgentID;
            agentData.BaseFolder=UUID.Zero;
            agentData.CapsPath=Util.GetRandomCapsPath();
            agentData.child = false;
            agentData.circuitcode = (uint)(Util.RandomClass.Next());
            agentData.firstname = FirstName;
            agentData.lastname = LastName;
            agentData.SecureSessionID=UUID.Random();
            agentData.SessionID=UUID.Random();
            agentData.startpos = new Vector3(128f, 128f, 100f);

            // Pre-Fill our region cache with information on the agent.
            UserAgentData useragent = new UserAgentData();
            useragent.AgentIP="unknown";
            useragent.AgentOnline=true;
            useragent.AgentPort = (uint)0;
            useragent.Handle = regionhandle;
            useragent.InitialRegion = reg.originRegionID;
            useragent.LoginTime=Util.UnixTimeSinceEpoch();
            useragent.LogoutTime = 0;
            useragent.Position=agentData.startpos;
            useragent.Region=reg.originRegionID;
            useragent.SecureSessionID=agentData.SecureSessionID;
            useragent.SessionID = agentData.SessionID;

            UserProfileData userProfile = new UserProfileData();
            userProfile.AboutText = "OGP User";
            userProfile.CanDoMask = (uint)0;
            userProfile.Created = Util.UnixTimeSinceEpoch();
            userProfile.CurrentAgent = useragent;
            userProfile.CustomType = "OGP";
            userProfile.FirstLifeAboutText = "I'm testing OpenGrid Protocol";
            userProfile.FirstLifeImage = UUID.Zero;
            userProfile.FirstName = agentData.firstname;
            userProfile.GodLevel = 0;
            userProfile.HomeLocation = agentData.startpos;
            userProfile.HomeLocationX = agentData.startpos.X;
            userProfile.HomeLocationY = agentData.startpos.Y;
            userProfile.HomeLocationZ = agentData.startpos.Z;
            userProfile.HomeLookAt = Vector3.Zero;
            userProfile.HomeLookAtX = userProfile.HomeLookAt.X;
            userProfile.HomeLookAtY = userProfile.HomeLookAt.Y;
            userProfile.HomeLookAtZ = userProfile.HomeLookAt.Z;
            userProfile.HomeRegion = reg.RegionHandle;
            userProfile.HomeRegionID = reg.originRegionID;
            userProfile.HomeRegionX = reg.RegionLocX;
            userProfile.HomeRegionY = reg.RegionLocY;
            userProfile.ID = agentData.AgentID;
            userProfile.Image = UUID.Zero;
            userProfile.LastLogin = Util.UnixTimeSinceEpoch();
            userProfile.Partner = UUID.Zero;
            userProfile.PasswordHash = "$1$";
            userProfile.PasswordSalt = "";
            userProfile.RootInventoryFolderID = UUID.Zero;
            userProfile.SurName = agentData.lastname;
            userProfile.UserAssetURI = homeScene.CommsManager.NetworkServersInfo.AssetURL;
            userProfile.UserFlags = 0;
            userProfile.UserInventoryURI = homeScene.CommsManager.NetworkServersInfo.InventoryURL;
            userProfile.WantDoMask = 0;
            userProfile.WebLoginKey = UUID.Random();

            // Do caps registration
            // get seed capagentData.firstname = FirstName;agentData.lastname = LastName;
            if (homeScene.CommsManager.UserService.GetUserProfile(agentData.AgentID) == null && !GridMode)
            {
                homeScene.CommsManager.AddUser(agentData.firstname, agentData.lastname, CreateRandomStr(7), homeScene.RegionInfo.RegionLocX, homeScene.RegionInfo.RegionLocY, agentData.AgentID);
                UserProfileData userProfile2 = homeScene.CommsManager.UserService.GetUserProfile(agentData.AgentID);
                if (userProfile2 != null)
                {
                    userProfile = userProfile2;
                    userProfile.AboutText = "OGP USER";
                    userProfile.FirstLifeAboutText = "OGP USER";
                    homeScene.CommsManager.UserService.UpdateUserProfile(userProfile);
                }

            }
            
            // Stick our data in the cache so the region will know something about us
            homeScene.CommsManager.UserProfileCacheService.PreloadUserCache(agentData.AgentID, userProfile);

            // Call 'new user' event handler
            homeScene.NewUserConnection(reg.RegionHandle, agentData);

            //string raCap = string.Empty;

            UUID AvatarRezCapUUID = LocalAgentID;
            string rezAvatarPath = "/agent/" + AvatarRezCapUUID + "/rez_avatar/rez";
            string derezAvatarPath = "/agent/" + AvatarRezCapUUID + "/rez_avatar/derez";
            // Get a reference to the user's cap so we can pull out the Caps Object Path
            OpenSim.Framework.Communications.Capabilities.Caps userCap = homeScene.GetCapsHandlerForUser(agentData.AgentID);

            string rezHttpProtocol = "http://";
            string regionCapsHttpProtocol = "http://";
            string httpaddr = reg.ExternalHostName;
            string urlport = reg.HttpPort.ToString();


            if (httpSSL)
            {
                rezHttpProtocol = "https://";
                regionCapsHttpProtocol = "https://";
                urlport = httpsslport.ToString();

                if (httpsCN.Length > 0)
                    httpaddr = httpsCN;
            }
            


            // DEPRECIATED
            responseMap["seed_capability"] = LLSD.FromString(regionCapsHttpProtocol + httpaddr + ":" + reg.HttpPort + "/CAPS/" + userCap.CapsObjectPath + "0000/");
            
            // REPLACEMENT
            responseMap["region_seed_capability"] = LLSD.FromString(regionCapsHttpProtocol + httpaddr + ":" + reg.HttpPort + "/CAPS/" + userCap.CapsObjectPath + "0000/");

            responseMap["rez_avatar"] = LLSD.FromString(rezHttpProtocol + httpaddr + ":" + urlport + rezAvatarPath);
            responseMap["rez_avatar/rez"] = LLSD.FromString(rezHttpProtocol + httpaddr + ":" + urlport + rezAvatarPath);
            responseMap["rez_avatar/derez"] = LLSD.FromString(rezHttpProtocol + httpaddr + ":" + urlport + derezAvatarPath);

            // Add the user to the list of CAPS that are outstanding.
            // well allow the caps hosts in this dictionary
            lock (CapsLoginID)
            {
                if (CapsLoginID.ContainsKey(rezAvatarPath))
                {
                    CapsLoginID[rezAvatarPath] = agentData;
                    
                    // This is a joke, if you didn't notice...  It's so unlikely to happen, that I'll print this message if it does occur!
                    m_log.Error("[OGP]: Holy anomoly batman! Caps path already existed!  All the UUID Duplication worries were founded!");
                }
                else
                {
                    CapsLoginID.Add(rezAvatarPath, agentData);
                }
            }
            
            //System.Console.WriteLine("Response:" + responseMap.ToString());
            return responseMap;
        }

        public LLSD RezAvatarMethod(string path, LLSD request)
        {
            m_log.WarnFormat("[REZAVATAR]: {0}", request.ToString());

            LLSDMap responseMap = new LLSDMap();

            AgentCircuitData userData = null;

            // Only people we've issued a cap can go further
            if (TryGetAgentCircuitData(path,out userData))
            {
                LLSDMap requestMap = (LLSDMap)request;

                // take these values to start.  There's a few more
                UUID SecureSessionID=requestMap["secure_session_id"].AsUUID();
                UUID SessionID = requestMap["session_id"].AsUUID();
                int circuitcode = requestMap["circuit_code"].AsInteger();
                LLSDArray Parameter = new LLSDArray();
                if (requestMap.ContainsKey("parameter"))
                {
                   Parameter = (LLSDArray)((LLSD)requestMap["parameter"]);
                }

                //int version = 1;
                int estateID = 1;
                int parentEstateID = 1;
                UUID regionID = UUID.Zero;
                bool visibleToParent = true;

                for (int i = 0; i < Parameter.Count; i++)
                {
                    LLSDMap item = (LLSDMap)Parameter[i];
//                    if (item.ContainsKey("version"))
//                    {
//                        version = item["version"].AsInteger();
//                    }
                    if (item.ContainsKey("estate_id"))
                    {
                        estateID = item["estate_id"].AsInteger();
                    }
                    if (item.ContainsKey("parent_estate_id"))
                    {
                        parentEstateID = item["parent_estate_id"].AsInteger();

                    }
                    if (item.ContainsKey("region_id"))
                    {
                        regionID = item["region_id"].AsUUID();

                    }
                    if (item.ContainsKey("visible_to_parent"))
                    {
                        visibleToParent = item["visible_to_parent"].AsBoolean();
                    }
                }
                //Update our Circuit data with the real values
                userData.SecureSessionID = SecureSessionID;
                userData.SessionID = SessionID;

                OGPState userState = GetOGPState(userData.AgentID);

                // Locate a home scene suitable for the user.
                Scene homeScene = null;

                homeScene = GetScene(userState.teleported_into_region);
                
                if (homeScene == null)
                    homeScene = GetRootScene();

                if (homeScene != null)
                {
                    // Get a reference to their Cap object so we can pull out the capobjectroot
                    OpenSim.Framework.Communications.Capabilities.Caps userCap =
                                                homeScene.GetCapsHandlerForUser(userData.AgentID);

                    //Update the circuit data in the region so this user is authorized
                    homeScene.UpdateCircuitData(userData);
                    homeScene.ChangeCircuitCode(userData.circuitcode,(uint)circuitcode);

                    // Load state
                    

                    // Keep state changes
                    userState.first_name = requestMap["first_name"].AsString();
                    userState.secure_session_id = requestMap["secure_session_id"].AsUUID();
                    userState.age_verified = requestMap["age_verified"].AsBoolean();
                    userState.region_id = homeScene.RegionInfo.originRegionID; // replace 0000000 with our regionid
                    userState.transacted = requestMap["transacted"].AsBoolean();
                    userState.agent_access = requestMap["agent_access"].AsBoolean();
                    userState.inventory_host = requestMap["inventory_host"].AsString();
                    userState.identified = requestMap["identified"].AsBoolean();
                    userState.session_id = requestMap["session_id"].AsUUID();
                    userState.god_level = (uint)requestMap["god_level"].AsInteger();
                    userState.last_name = requestMap["last_name"].AsString();
                    userState.god_overide = requestMap["god_override"].AsBoolean();
                    userState.circuit_code = (uint)requestMap["circuit_code"].AsInteger();
                    userState.limited_to_estate = requestMap["limited_to_estate"].AsInteger();
                    userState.src_estate_id = estateID;
                    userState.region_id = regionID;
                    userState.src_parent_estate_id = parentEstateID;
                    userState.visible_to_parent = visibleToParent;

                    // Save state changes
                    UpdateOGPState(userData.AgentID, userState);

                    // Get the region information for the home region.
                    RegionInfo reg = homeScene.RegionInfo;

                    // Dummy positional and look at info..  we don't have it.
                    LLSDArray PositionArray = new LLSDArray();
                    PositionArray.Add(LLSD.FromInteger(128));
                    PositionArray.Add(LLSD.FromInteger(128));
                    PositionArray.Add(LLSD.FromInteger(40));

                    LLSDArray LookAtArray = new LLSDArray();
                    LookAtArray.Add(LLSD.FromInteger(1));
                    LookAtArray.Add(LLSD.FromInteger(1));
                    LookAtArray.Add(LLSD.FromInteger(1));

                    // Our region's X and Y position in OpenSimulator space.
                    uint fooX = reg.RegionLocX;
                    uint fooY = reg.RegionLocY;
                    m_log.InfoFormat("[OGP]: region x({0}) region y({1})", fooX, fooY);
                    m_log.InfoFormat("[OGP]: region http {0} {1}", reg.ServerURI, reg.HttpPort);
                    m_log.InfoFormat("[OGO]: region UUID {0} ", reg.RegionID);

                    // Convert the X and Y position to LL space
                    responseMap["region_x"] = LLSD.FromInteger(fooX * (uint)Constants.RegionSize); // convert it to LL X
                    responseMap["region_y"] = LLSD.FromInteger(fooY * (uint)Constants.RegionSize); // convert it to LL Y

                    // Give em a new seed capability
                    responseMap["seed_capability"] = LLSD.FromString("http://" + reg.ExternalHostName + ":" + reg.HttpPort + "/CAPS/" + userCap.CapsObjectPath + "0000/");
                    responseMap["region"] = LLSD.FromUUID(reg.originRegionID);
                    responseMap["look_at"] = LookAtArray;

                    responseMap["sim_port"] = LLSD.FromInteger(reg.InternalEndPoint.Port);
                    responseMap["sim_host"] = LLSD.FromString(reg.ExternalHostName);// + ":" + reg.InternalEndPoint.Port.ToString());
                    
                    // DEPRECIATED
                    responseMap["sim_ip"] = LLSD.FromString(Util.GetHostFromDNS(reg.ExternalHostName).ToString());

                    responseMap["session_id"] = LLSD.FromUUID(SessionID);
                    responseMap["secure_session_id"] = LLSD.FromUUID(SecureSessionID);
                    responseMap["circuit_code"] = LLSD.FromInteger(circuitcode);

                    responseMap["position"] = PositionArray;

                    responseMap["region_id"] = LLSD.FromUUID(reg.originRegionID);

                    responseMap["sim_access"] = LLSD.FromString("Mature");

                    responseMap["connect"] = LLSD.FromBoolean(true);

                   

                    m_log.InfoFormat("[OGP]: host: {0}, IP {1}", responseMap["sim_host"].ToString(), responseMap["sim_ip"].ToString());
                }
            }

            return responseMap;
        }

        public LLSD DerezAvatarMethod(string path, LLSD request)
        {
            m_log.ErrorFormat("DerezPath: {0}, Request: {1}", path, request.ToString());

            //LLSD llsdResponse = null;
            LLSDMap responseMap = new LLSDMap();

            string[] PathArray = path.Split('/');
            m_log.InfoFormat("[OGP]: prefix {0}, uuid {1}, suffix {2}", PathArray[1], PathArray[2], PathArray[3]);
            string uuidString = PathArray[2];
            m_log.InfoFormat("[OGP]: Request to Derez avatar with UUID {0}", uuidString);
            UUID userUUID = UUID.Zero;
            if (UUID.TryParse(uuidString, out userUUID))
            {
                UUID RemoteID = (UUID)uuidString;
                UUID LocalID = RemoteID;
                // FIXME: TODO: Routine to map RemoteUUIDs to LocalUUIds
                //         would be done already..  but the client connects with the Aditi UUID
                //         regardless over the UDP stack

                OGPState userState = GetOGPState(LocalID);
                if (userState.agent_id != UUID.Zero)
                {
                    //LLSDMap outboundRequestMap = new LLSDMap();
                    LLSDMap inboundRequestMap = (LLSDMap)request;
                    string rezAvatarString = inboundRequestMap["rez_avatar"].AsString();
                    if (rezAvatarString.Length == 0)
                    {
                        rezAvatarString = inboundRequestMap["rez_avatar/rez"].AsString();
                    }
                    LLSDArray LookAtArray = new LLSDArray();
                    LookAtArray.Add(LLSD.FromInteger(1));
                    LookAtArray.Add(LLSD.FromInteger(1));
                    LookAtArray.Add(LLSD.FromInteger(1));

                    LLSDArray PositionArray = new LLSDArray();
                    PositionArray.Add(LLSD.FromInteger(128));
                    PositionArray.Add(LLSD.FromInteger(128));
                    PositionArray.Add(LLSD.FromInteger(40));

                    LLSDArray lookArray = new LLSDArray();
                    lookArray.Add(LLSD.FromInteger(128));
                    lookArray.Add(LLSD.FromInteger(128));
                    lookArray.Add(LLSD.FromInteger(40));

                    responseMap["connect"] = LLSD.FromBoolean(true);// it's okay to give this user up
                    responseMap["look_at"] = LookAtArray;

                    m_log.WarnFormat("[OGP]: Invoking rez_avatar on host:{0} for avatar: {1} {2}", rezAvatarString, userState.first_name, userState.last_name);

                    LLSDMap rezResponseMap = invokeRezAvatarCap(responseMap, rezAvatarString,userState);

                    // If invoking it returned an error, parse and end
                    if (rezResponseMap.ContainsKey("connect"))
                    {
                        if (rezResponseMap["connect"].AsBoolean() == false)
                        {
                            return responseMap;
                        }
                    }

                    string rezRespSeedCap = "";

                    // DEPRECIATED
                    if (rezResponseMap.ContainsKey("seed_capability"))
                        rezRespSeedCap = rezResponseMap["seed_capability"].AsString();
                    
                    // REPLACEMENT
                    if (rezResponseMap.ContainsKey("region_seed_capability"))
                        rezRespSeedCap = rezResponseMap["region_seed_capability"].AsString();

                    // REPLACEMENT
                    if (rezResponseMap.ContainsKey("rez_avatar/rez"))
                        rezRespSeedCap = rezResponseMap["rez_avatar/rez"].AsString();

                    // DEPRECIATED
                    string rezRespSim_ip = rezResponseMap["sim_ip"].AsString();
                    
                    string rezRespSim_host = rezResponseMap["sim_host"].AsString();

                    int rrPort = rezResponseMap["sim_port"].AsInteger();
                    int rrX = rezResponseMap["region_x"].AsInteger();
                    int rrY = rezResponseMap["region_y"].AsInteger();
                    m_log.ErrorFormat("X:{0}, Y:{1}", rrX, rrY);
                    UUID rrRID = rezResponseMap["region_id"].AsUUID();
                    LLSDArray RezResponsePositionArray = null;
                    string rrAccess = rezResponseMap["sim_access"].AsString();
                    if (rezResponseMap.ContainsKey("position"))
                    {
                        RezResponsePositionArray = (LLSDArray)rezResponseMap["position"];
                    }
                    // DEPRECIATED
                    responseMap["seed_capability"] = LLSD.FromString(rezRespSeedCap);
                    
                    // REPLACEMENT r3
                    responseMap["region_seed_capability"] = LLSD.FromString(rezRespSeedCap);

                    // DEPRECIATED
                    responseMap["sim_ip"] = LLSD.FromString(Util.GetHostFromDNS(rezRespSim_ip).ToString());
                    
                    responseMap["sim_host"] = LLSD.FromString(rezRespSim_host);
                    responseMap["sim_port"] = LLSD.FromInteger(rrPort);
                    responseMap["region_x"] = LLSD.FromInteger(rrX );
                    responseMap["region_y"] = LLSD.FromInteger(rrY );
                    responseMap["region_id"] = LLSD.FromUUID(rrRID);
                    responseMap["sim_access"] = LLSD.FromString(rrAccess);

                    if (RezResponsePositionArray != null)
                    {
                        responseMap["position"] = RezResponsePositionArray;
                    }
                    responseMap["look_at"] = lookArray;
                    responseMap["connect"] = LLSD.FromBoolean(true);

                    ShutdownConnection(LocalID,this);
                    // PLEASE STOP CHANGING THIS TO an M_LOG, M_LOG DOESN'T WORK ON MULTILINE .TOSTRINGS
                    System.Console.WriteLine("RESPONSEDEREZ: " + responseMap.ToString());
                    return responseMap;
                }
                else
                {
                    return GenerateNoStateMessage(LocalID);
                }
            }
            else
            {
                return GenerateNoHandlerMessage();
            }

            //return responseMap;
        }

        private LLSDMap invokeRezAvatarCap(LLSDMap responseMap, string CapAddress, OGPState userState)
        {
            Scene reg = GetRootScene();

            WebRequest DeRezRequest = WebRequest.Create(CapAddress);
            DeRezRequest.Method = "POST";
            DeRezRequest.ContentType = "application/xml+llsd";

            LLSDMap RAMap = new LLSDMap();
            LLSDMap AgentParms = new LLSDMap();
            LLSDMap RegionParms = new LLSDMap();

            LLSDArray Parameter = new LLSDArray(2);

            LLSDMap version = new LLSDMap();
            version["version"] = LLSD.FromInteger(userState.src_version);
            Parameter.Add((LLSD)version);

            LLSDMap SrcData = new LLSDMap();
            SrcData["estate_id"] = LLSD.FromInteger(reg.RegionInfo.EstateSettings.EstateID);
            SrcData["parent_estate_id"] = LLSD.FromInteger((reg.RegionInfo.EstateSettings.ParentEstateID == 100 ? 1 : reg.RegionInfo.EstateSettings.ParentEstateID));
            SrcData["region_id"] = LLSD.FromUUID(reg.RegionInfo.originRegionID);
            SrcData["visible_to_parent"] = LLSD.FromBoolean(userState.visible_to_parent);
            Parameter.Add((LLSD)SrcData);

            AgentParms["first_name"] = LLSD.FromString(userState.first_name);
            AgentParms["last_name"] = LLSD.FromString(userState.last_name);
            AgentParms["agent_id"] = LLSD.FromUUID(userState.agent_id);
            RegionParms["region_id"] = LLSD.FromUUID(userState.region_id);
            AgentParms["circuit_code"] = LLSD.FromInteger(userState.circuit_code);
            AgentParms["secure_session_id"] = LLSD.FromUUID(userState.secure_session_id);
            AgentParms["session_id"] = LLSD.FromUUID(userState.session_id);
            AgentParms["agent_access"] = LLSD.FromBoolean(userState.agent_access);
            AgentParms["god_level"] = LLSD.FromInteger(userState.god_level);
            AgentParms["god_overide"] = LLSD.FromBoolean(userState.god_overide);
            AgentParms["identified"] = LLSD.FromBoolean(userState.identified);
            AgentParms["transacted"] = LLSD.FromBoolean(userState.transacted);
            AgentParms["age_verified"] = LLSD.FromBoolean(userState.age_verified);
            AgentParms["limited_to_estate"] = LLSD.FromInteger(userState.limited_to_estate);
            AgentParms["inventory_host"] = LLSD.FromString(userState.inventory_host);

            // version 1
            RAMap = AgentParms;

            // Planned for version 2
            // RAMap["agent_params"] = AgentParms;

            RAMap["region_params"] = RegionParms;
            RAMap["parameter"] = Parameter;

            string RAMapString = RAMap.ToString();
            m_log.InfoFormat("[OGP] RAMap string {0}", RAMapString);
            LLSD LLSDofRAMap = RAMap; // RENAME if this works

            m_log.InfoFormat("[OGP]: LLSD of map as string  was {0}", LLSDofRAMap.ToString());
            //m_log.InfoFormat("[OGP]: LLSD+XML: {0}", LLSDParser.SerializeXmlString(LLSDofRAMap));
            byte[] buffer = LLSDParser.SerializeXmlBytes(LLSDofRAMap);

            //string bufferDump = System.Text.Encoding.ASCII.GetString(buffer);
            //m_log.InfoFormat("[OGP]: buffer form is {0}",bufferDump);
            //m_log.InfoFormat("[OGP]: LLSD of map was {0}",buffer.Length);

            Stream os = null;
            try
            { // send the Post
                DeRezRequest.ContentLength = buffer.Length;   //Count bytes to send
                os = DeRezRequest.GetRequestStream();
                os.Write(buffer, 0, buffer.Length);         //Send it
                os.Close();
                m_log.InfoFormat("[OGP]: Derez Avatar Posted Rez Avatar request to remote sim {0}", CapAddress);
            }
            catch (WebException ex)
            {
                m_log.InfoFormat("[OGP] Bad send on de_rez_avatar {0}", ex.Message);
                responseMap["connect"] = LLSD.FromBoolean(false);

                return responseMap;
            }

            m_log.Info("[OGP] waiting for a reply after rez avatar send");
            string rez_avatar_reply = null;
            { // get the response
                try
                {
                    WebResponse webResponse = DeRezRequest.GetResponse();
                    if (webResponse == null)
                    {
                        m_log.Info("[OGP:] Null reply on rez_avatar post");
                    }

                    StreamReader sr = new StreamReader(webResponse.GetResponseStream());
                    rez_avatar_reply = sr.ReadToEnd().Trim();
                    m_log.InfoFormat("[OGP]: rez_avatar reply was {0} ", rez_avatar_reply);

                }
                catch (WebException ex)
                {
                    m_log.InfoFormat("[OGP]: exception on read after send of rez avatar {0}", ex.Message);
                    responseMap["connect"] = LLSD.FromBoolean(false);

                    return responseMap;
                }
                LLSD rezResponse = null;
                try
                {
                    rezResponse = LLSDParser.DeserializeXml(rez_avatar_reply);

                    responseMap = (LLSDMap)rezResponse;
                }
                catch (Exception ex)
                {
                    m_log.InfoFormat("[OGP]: exception on parse of rez reply {0}", ex.Message);
                    responseMap["connect"] = LLSD.FromBoolean(false);

                    return responseMap;
                }
            }
            return responseMap;
        }

        public LLSD GenerateNoHandlerMessage()
        {
            LLSDMap map = new LLSDMap();
            map["reason"] = LLSD.FromString("LLSDRequest");
            map["message"] = LLSD.FromString("No handler registered for LLSD Requests");
            map["login"] = LLSD.FromString("false");
            map["connect"] = LLSD.FromString("false");
            return map;
        }
        public LLSD GenerateNoStateMessage(UUID passedAvatar)
        {
            LLSDMap map = new LLSDMap();
            map["reason"] = LLSD.FromString("derez failed");
            map["message"] = LLSD.FromString("Unable to locate OGP state for avatar " + passedAvatar.ToString());
            map["login"] = LLSD.FromString("false");
            map["connect"] = LLSD.FromString("false");
            return map;
        }
        private bool TryGetAgentCircuitData(string path, out AgentCircuitData userdata)
        {
            userdata = null;
            lock (CapsLoginID)
            {
                if (CapsLoginID.ContainsKey(path))
                {
                    userdata = CapsLoginID[path];
                    DiscardUsedCap(path);
                    return true;
                }
            }
            return false;
        }

        private void DiscardUsedCap(string path)
        {
            CapsLoginID.Remove(path);
        }

        private Scene GetRootScene()
        {
            Scene ReturnScene = null;
            lock (m_scene)
            {
                if (m_scene.Count > 0)
                {
                    ReturnScene = m_scene[0];
                }
            }

            return ReturnScene;
        }

        private Scene GetScene(string scenename)
        {
            Scene ReturnScene = null;
            lock (m_scene)
            {
                foreach (Scene s in m_scene)
                {
                    if (s.RegionInfo.RegionName.ToLower() == scenename)
                    {
                        ReturnScene = s;
                        break;
                    }
                }
            }

            return ReturnScene;
        }

        private ulong GetOSCompatibleRegionHandle(RegionInfo reg)
        {
            return Util.UIntsToLong(reg.RegionLocX, reg.RegionLocY);
        }

        private OGPState InitializeNewState()
        {
            OGPState returnState = new OGPState();
            returnState.first_name = "";
            returnState.last_name = "";
            returnState.agent_id = UUID.Zero;
            returnState.local_agent_id = UUID.Zero;
            returnState.region_id = UUID.Zero;
            returnState.circuit_code = 0;
            returnState.secure_session_id = UUID.Zero;
            returnState.session_id = UUID.Zero;
            returnState.agent_access = true;
            returnState.god_level = 0;
            returnState.god_overide = false;
            returnState.identified = false;
            returnState.transacted = false;
            returnState.age_verified = false;
            returnState.limited_to_estate = 1;
            returnState.inventory_host = "http://inv4.mysql.aditi.lindenlab.com";
            returnState.allow_redirect = true;
            returnState.sim_access = "";
            returnState.src_can_see_mainland = true;
            returnState.src_estate_id = 1;
            returnState.src_version = 1;
            returnState.src_parent_estate_id = 1;
            returnState.visible_to_parent = true;
            returnState.teleported_into_region = "";

            return returnState;
        }

        private OGPState GetOGPState(UUID agentId)
        {
            lock (m_OGPState)
            {
                if (m_OGPState.ContainsKey(agentId))
                {
                    return m_OGPState[agentId];
                }
                else
                {
                    return InitializeNewState();
                }
            }
        }

        public void DeleteOGPState(UUID agentId)
        {
            lock (m_OGPState)
            {
                if (m_OGPState.ContainsKey(agentId))
                    m_OGPState.Remove(agentId);
            }
        }

        private void UpdateOGPState(UUID agentId, OGPState state)
        {
            lock (m_OGPState)
            {
                if (m_OGPState.ContainsKey(agentId))
                {
                    m_OGPState[agentId] = state;
                }
                else
                {
                    m_OGPState.Add(agentId,state);
                }
            }
        }
        private bool SceneListDuplicateCheck(string str)
        {
            // no lock, called from locked space!
            bool found = false;
            
            foreach (Scene s in m_scene)
            {
                if (s.RegionInfo.RegionName == str)
                {
                    found = true;
                    break;
                }
            }

            return found;
        }

        public void ShutdownConnection(UUID avatarId, OpenGridProtocolModule mod)
        {
            Scene homeScene = GetRootScene();
            ScenePresence avatar = null;
            if (homeScene.TryGetAvatar(avatarId,out avatar))
            {
                KillAUser ku = new KillAUser(avatar,mod);
                Thread ta = new Thread(ku.ShutdownNoLogout);
                ta.IsBackground = true;
                ta.Name = "ShutdownThread";
                ta.Start();
            }
        }

        private string CreateRandomStr(int len)
        {
            Random rnd = new Random(System.Environment.TickCount);
            string returnstring = "";
            string chars = "abcdefghijklmnopqrstuvwxyz0123456789";

            for (int i = 0; i < len; i++)
            {
                returnstring += chars.Substring(rnd.Next(chars.Length), 1);
            }
            return returnstring;
        }
        // Temporary hack to allow teleporting to and from Vaak
        private static bool customXertificateValidation(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error)
        {
            //if (cert.Subject == "E=root@lindenlab.com, CN=*.vaak.lindenlab.com, O=\"Linden Lab, Inc.\", L=San Francisco, S=California, C=US")
            //{
                return true;
            //}

            //return false;
        }
    }

    public class KillAUser
    {
        private ScenePresence avToBeKilled = null;
        private OpenGridProtocolModule m_mod = null;

        public KillAUser(ScenePresence avatar, OpenGridProtocolModule mod)
        {
            avToBeKilled = avatar;
            m_mod = mod;
        }

        public void ShutdownNoLogout()
        {
            UUID avUUID = UUID.Zero;

            if (avToBeKilled != null)
            {
                avUUID = avToBeKilled.UUID;
                avToBeKilled.MakeChildAgent();

                avToBeKilled.ControllingClient.SendLogoutPacketWhenClosing = false;

                Thread.Sleep(30000);

                // test for child agent because they might have come back
                if (avToBeKilled.IsChildAgent)
                {
                    m_mod.DeleteOGPState(avUUID);
                    avToBeKilled.ControllingClient.Close(true);
                }
            }
        }
        
    }
    
    public class MonoCert : ICertificatePolicy
    {
        #region ICertificatePolicy Members

        public bool CheckValidationResult(ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem)
        {
            return true;
        }

        #endregion
    }
}
