/*
 * 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;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml;
using log4net;
using OpenMetaverse;
using OpenSim.Data;
using OpenSim.Framework;
using OpenSim.Framework.Communications;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Grid.Framework;

namespace OpenSim.Grid.GridServer.Modules
{
    public class GridRestModule
    {
         private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        private GridDBService m_gridDBService;
        private IGridServiceCore m_gridCore;

        protected GridConfig m_config;

        /// <value>
        /// Used to notify old regions as to which OpenSim version to upgrade to
        /// </value>
        //private string m_opensimVersion;

        protected BaseHttpServer m_httpServer;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="opensimVersion">
        /// Used to notify old regions as to which OpenSim version to upgrade to
        /// </param>
        public GridRestModule()
        {
        }

        public void Initialise(string opensimVersion, GridDBService gridDBService, IGridServiceCore gridCore, GridConfig config)
        {
            //m_opensimVersion = opensimVersion;
            m_gridDBService = gridDBService;
            m_gridCore = gridCore;
            m_config = config;
            RegisterHandlers();
        }

        public void PostInitialise()
        {

        }

        public void RegisterHandlers()
        {
            //have these in separate method as some servers restart the http server and reregister all the handlers.
            m_httpServer = m_gridCore.GetHttpServer();

            m_httpServer.AddStreamHandler(new RestStreamHandler("GET", "/sims/", RestGetSimMethod));
            m_httpServer.AddStreamHandler(new RestStreamHandler("POST", "/sims/", RestSetSimMethod));

            m_httpServer.AddStreamHandler(new RestStreamHandler("GET", "/regions/", RestGetRegionMethod));
            m_httpServer.AddStreamHandler(new RestStreamHandler("POST", "/regions/", RestSetRegionMethod));
        }

        /// <summary>
        /// Performs a REST Get Operation
        /// </summary>
        /// <param name="request"></param>
        /// <param name="path"></param>
        /// <param name="param"></param>
        /// <param name="httpRequest">HTTP request header object</param>
        /// <param name="httpResponse">HTTP response header object</param>
        /// <returns></returns>
        public string RestGetRegionMethod(string request, string path, string param,
                                          OSHttpRequest httpRequest, OSHttpResponse httpResponse)
        {
            return RestGetSimMethod(String.Empty, "/sims/", param, httpRequest, httpResponse);
        }

        /// <summary>
        /// Performs a REST Set Operation
        /// </summary>
        /// <param name="request"></param>
        /// <param name="path"></param>
        /// <param name="param"></param>
        /// <param name="httpRequest">HTTP request header object</param>
        /// <param name="httpResponse">HTTP response header object</param>
        /// <returns></returns>
        public string RestSetRegionMethod(string request, string path, string param,
                                          OSHttpRequest httpRequest, OSHttpResponse httpResponse)
        {
            return RestSetSimMethod(String.Empty, "/sims/", param, httpRequest, httpResponse);
        }

        /// <summary>
        /// Returns information about a sim via a REST Request
        /// </summary>
        /// <param name="request"></param>
        /// <param name="path"></param>
        /// <param name="param">A string representing the sim's UUID</param>
        /// <param name="httpRequest">HTTP request header object</param>
        /// <param name="httpResponse">HTTP response header object</param>
        /// <returns>Information about the sim in XML</returns>
        public string RestGetSimMethod(string request, string path, string param,
                                       OSHttpRequest httpRequest, OSHttpResponse httpResponse)
        {
            string respstring = String.Empty;

            RegionProfileData TheSim;

            UUID UUID;
            if (UUID.TryParse(param, out UUID))
            {
                TheSim = m_gridDBService.GetRegion(UUID);

                if (!(TheSim == null))
                {
                    respstring = "<Root>";
                    respstring += "<authkey>" + TheSim.regionSendKey + "</authkey>";
                    respstring += "<sim>";
                    respstring += "<uuid>" + TheSim.UUID.ToString() + "</uuid>";
                    respstring += "<regionname>" + TheSim.regionName + "</regionname>";
                    respstring += "<sim_ip>" + TheSim.serverIP + "</sim_ip>";
                    respstring += "<sim_port>" + TheSim.serverPort.ToString() + "</sim_port>";
                    respstring += "<region_locx>" + TheSim.regionLocX.ToString() + "</region_locx>";
                    respstring += "<region_locy>" + TheSim.regionLocY.ToString() + "</region_locy>";
                    respstring += "<estate_id>1</estate_id>";
                    respstring += "</sim>";
                    respstring += "</Root>";
                }
            }
            else
            {
                respstring = "<Root>";
                respstring += "<error>Param must be a UUID</error>";
                respstring += "</Root>";
            }

            return respstring;
        }

        /// <summary>
        /// Creates or updates a sim via a REST Method Request
        /// BROKEN with SQL Update
        /// </summary>
        /// <param name="request"></param>
        /// <param name="path"></param>
        /// <param name="param"></param>
        /// <param name="httpRequest">HTTP request header object</param>
        /// <param name="httpResponse">HTTP response header object</param>
        /// <returns>"OK" or an error</returns>
        public string RestSetSimMethod(string request, string path, string param,
                                       OSHttpRequest httpRequest, OSHttpResponse httpResponse)
        {
            m_log.Info("Processing region update via REST method");
            RegionProfileData theSim;
            theSim = m_gridDBService.GetRegion(new UUID(param));
            if (theSim == null)
            {
                theSim = new RegionProfileData();
                UUID UUID = new UUID(param);
                theSim.UUID = UUID;
                theSim.regionRecvKey = m_config.SimRecvKey;
            }

            XmlDocument doc = new XmlDocument();
            doc.LoadXml(request);
            XmlNode rootnode = doc.FirstChild;
            XmlNode authkeynode = rootnode.ChildNodes[0];
            if (authkeynode.Name != "authkey")
            {
                return "ERROR! bad XML - expected authkey tag";
            }

            XmlNode simnode = rootnode.ChildNodes[1];
            if (simnode.Name != "sim")
            {
                return "ERROR! bad XML - expected sim tag";
            }

            //theSim.regionSendKey = Cfg;
            theSim.regionRecvKey = m_config.SimRecvKey;
            theSim.regionSendKey = m_config.SimSendKey;
            theSim.regionSecret = m_config.SimRecvKey;
            theSim.regionDataURI = String.Empty;
            theSim.regionAssetURI = m_config.DefaultAssetServer;
            theSim.regionAssetRecvKey = m_config.AssetRecvKey;
            theSim.regionAssetSendKey = m_config.AssetSendKey;
            theSim.regionUserURI = m_config.DefaultUserServer;
            theSim.regionUserSendKey = m_config.UserSendKey;
            theSim.regionUserRecvKey = m_config.UserRecvKey;

            for (int i = 0; i < simnode.ChildNodes.Count; i++)
            {
                switch (simnode.ChildNodes[i].Name)
                {
                    case "regionname":
                        theSim.regionName = simnode.ChildNodes[i].InnerText;
                        break;

                    case "sim_ip":
                        theSim.serverIP = simnode.ChildNodes[i].InnerText;
                        break;

                    case "sim_port":
                        theSim.serverPort = Convert.ToUInt32(simnode.ChildNodes[i].InnerText);
                        break;

                    case "region_locx":
                        theSim.regionLocX = Convert.ToUInt32((string)simnode.ChildNodes[i].InnerText);
                        theSim.regionHandle = Utils.UIntsToLong((theSim.regionLocX * Constants.RegionSize), (theSim.regionLocY * Constants.RegionSize));
                        break;

                    case "region_locy":
                        theSim.regionLocY = Convert.ToUInt32((string)simnode.ChildNodes[i].InnerText);
                        theSim.regionHandle = Utils.UIntsToLong((theSim.regionLocX * Constants.RegionSize), (theSim.regionLocY * Constants.RegionSize));
                        break;
                }
            }

            theSim.serverURI = "http://" + theSim.serverIP + ":" + theSim.serverPort + "/";
            bool requirePublic = false;
            bool requireValid = true;

            if (requirePublic &&
                (theSim.serverIP.StartsWith("172.16") || theSim.serverIP.StartsWith("192.168") ||
                 theSim.serverIP.StartsWith("10.") || theSim.serverIP.StartsWith("0.") ||
                 theSim.serverIP.StartsWith("255.")))
            {
                return "ERROR! Servers must register with public addresses.";
            }

            if (requireValid && (theSim.serverIP.StartsWith("0.") || theSim.serverIP.StartsWith("255.")))
            {
                return "ERROR! 0.*.*.* / 255.*.*.* Addresses are invalid, please check your server config and try again";
            }

            try
            {
                m_log.Info("[DATA]: " +
                           "Updating / adding via " + m_gridDBService.GetNumberOfPlugins() + " storage provider(s) registered.");

                return m_gridDBService.CheckReservations(theSim, authkeynode);
            }
            catch (Exception e)
            {
                return "ERROR! Could not save to database! (" + e.ToString() + ")";
            }
        }
    }
}