using System;
using System.Net;
using System.Text;
using System.Threading;
using log4net;
using Orbiter.Interfaces;

namespace Orbiter.Multiplayer
{
    /// <summary>
    /// 
    /// </summary>
    public class Web
    {
        private Thread web;
        private Server server;
        private bool active;
        private HttpListener listener;
        private static readonly ILog log = LogManager.GetLogger(" WEB");

        /// <summary>
        /// Initializes a new instance of the <see cref="Web"/> class.
        /// </summary>
        /// <param name="server">The server.</param>
        public Web(Server server)
        {
            this.server = server;
        }

        /// <summary>
        /// Starts this instance.
        /// </summary>
        public void Start()
        {
            active = true;
            listener = new HttpListener
            {
                //AuthenticationSchemes = AuthenticationSchemes.Basic
            };
            listener.Prefixes.Add(@"http://*:" + server.Configuration.Network.Web + "/");
            try
            {
                listener.Start();
                web = new Thread(ListenerThread) {IsBackground = true, Priority = ThreadPriority.Lowest};
                web.Start();
            }
            catch (Exception ex)
            {
                log.Error("Unable to start web server: ", ex);
            }
        }

        /// <summary>
        /// Stops this instance.
        /// </summary>
        public void Stop()
        {
            active = false;
            try
            {
                listener.Stop();
                web.Abort();
                web.Join();
            }
            catch (Exception ex)
            {
                log.Error("Unable to stop web server: ", ex);
            }

        }

        /// <summary>
        /// Reconfigures this instance by means of stopping and restarting it.
        /// </summary>
        public void Reconfigure()
        {
            if (active) Stop();
            if (server.Configuration.Network.WebSpecified) Start();
        }

        private void ListenerThread()
        {
            const string doctype = "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">";
            const string header = "<meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\"><meta name=\"description\" content=\"Orbiter Multiplayer Project Server Instance\"><meta name=\"keywords\" content=\"OMP,Orbiter,multiplayer,project,server,status\"><html><head><title>";
            const string body = "</title></head><body style=\"";
            const string footer = "</body></html>";
            
            try
            {
                while (active)
                {
                    try
                    {
                        var context = listener.GetContext();
                        var content = GetContent(context.Request.RawUrl);
                        var output = context.Response.OutputStream;
                        var text = new StringBuilder();
                        text.Append(doctype);
                        text.Append(header);
                        if (content != null)
                        {
                            text.Append(server.Configuration.Name);
                            text.Append(body);
                            text.Append(server.Configuration.HTML.BodyStyle);
                            text.Append("\">");
                            text.Append(content);
                        }
                        else
                        {
                            text.Append("404 Not Found");
                            text.Append(body);
                            text.Append(server.Configuration.HTML.BodyStyle);
                            text.Append("\">");
                            text.Append("<h1>Not Found</h1><p>The requested URL ");
                            text.Append(context.Request.RawUrl);
                            text.Append(" was not found on this server.</p><hr><address>");
                            text.Append(new Version());
                            text.Append(" \"");
                            text.Append(server.Configuration.Name);
                            text.Append("\" at ");
                            text.Append(server.Configuration.Network.IP);
                            text.Append(" Port ");
                            text.Append(server.Configuration.Network.Web);
                            text.Append("</address>");
                            context.Response.StatusCode = (int)HttpStatusCode.NotFound;
                        }
                        text.Append(footer);
                        byte[] line = Encoding.UTF8.GetBytes(text.ToString());
                        output.Write(line, 0, line.Length);
                        output.Close();
                        context.Response.Close();
                    }
                    catch (Exception ex)
                    {
                        if (!(ex is ThreadAbortException))
                            log.Error("Error receiving on HTTP port.", ex);
                    }
                }
            }
            catch (ThreadAbortException){}
        }

        private string GetContent(string url)
        {
            var paths = url.Split(new []{'/'}, StringSplitOptions.RemoveEmptyEntries);
            switch (paths.Length)
            {
                case 0: return GetSummaryPage();
                case 1:
                    switch (paths[0])
                    {
                        case "connections": return GetConnectionsPage();
                        case "users": return GetUsersPage();
                        case "objects": return GetObjectsPage();
                        case "topic": return GetTopicPage();
                    }
                    break;
                case 2:
                    switch (paths[0])
                    {
                        case "users":
                            lock(server.Map.UsersByName)
                            {
                                var user=Uri.UnescapeDataString(paths[1]);
                                if (!server.Map.UsersByName.ContainsKey(user)) return null;
                                return GetUserPage(server.Map.UsersByName[user]);
                            }
                        case "objects":
                            lock(server.Map.GlobalsById)
                            {
                                ushort id;
                                try {id = Convert.ToUInt16(paths[1]);}
                                catch (FormatException){return null;}
                                catch (OverflowException){return null;}
                                if (!server.Map.GlobalsById.ContainsKey(id)) return null;
                                return GetObjectPage(server.Map.GlobalsById[id]);
                            }
                    }
                    break;
            }
            return null;
        }

        private string GetObjectPage(GlobalObject o)
        {
            var result = new StringBuilder();
            const string rowbreak = "</td></tr><tr><td align=\"right\">";
            const string columnbreak = "</td><td>";
            const string header1 = "<a href=\"/\">Summary</a>-><a href=\"/objects\">Objects</a>->";
            const string header2 = "<br><br><table style=\"text-align: center; ";
            const string header3 = "\" border=\"0\" cellpadding=\"10\" cellspacing=\"2\"><tbody><tr><td align=\"right\">";
            const string footer = "</td></tr></tbody></table>";

            result.Append(header1);
            result.Append(o.GlobalId);
            result.Append(header2);
            result.Append(server.Configuration.HTML.TableStyle);
            result.Append(header3);
            result.Append("ID:");
            result.Append(columnbreak);
            result.Append(o.GlobalId);
            result.Append(rowbreak);
            result.Append("Object:");
            result.Append(columnbreak);
            result.Append(o.Name);
            result.Append(rowbreak);
            result.Append("Type:");
            result.Append(columnbreak);
            var body = o as CelestialBody;
            if (body!=null)
            {
                result.Append(body.Type);
                result.Append(rowbreak);
                result.Append("Mass [kg]:");
                result.Append(columnbreak);
                result.Append(body.Mass);
                var parent = body.Parent;
                if (parent != null)
                {
                    result.Append(rowbreak);
                    result.Append("Parent:");
                    result.Append(columnbreak);
                    result.Append("<a href=\"/objects/");
                    result.Append(parent.GlobalId);
                    result.Append("\">");
                    result.Append(parent.GlobalId);
                    result.Append("</a>");
                }
            }
            else
            {
                var vessel = o as Vessel;
                if (vessel!=null)
                {
                    result.Append(vessel.ClassName);
                    result.Append(rowbreak);
                    var user = vessel.User;
                    if (user != null)
                    {
                        result.Append("User:");
                        result.Append(columnbreak);
                        result.Append("<a href=\"/users/");
                        result.Append(Uri.EscapeUriString(user.Name));
                        result.Append("\">");
                        result.Append(user.Name);
                        result.Append("</a>");
                        result.Append(rowbreak);
                    }
                    var state = vessel.State;
                    result.Append("Reference:");
                    result.Append(columnbreak);
                    result.Append("<a href=\"/objects/");
                    result.Append(state.SpaceReference);
                    result.Append("\">");
                    result.Append(state.SpaceReference);
                    result.Append("</a>");
                    result.Append(rowbreak);
                    var position = new Vector3(state.Position.X, state.Position.Y, state.Position.Z);
                    result.Append("Position [m]:");
                    result.Append(columnbreak);
                    result.Append(position.X);
                    result.Append(columnbreak);
                    result.Append(position.Y);
                    result.Append(columnbreak);
                    result.Append(position.Z);
                    result.Append(columnbreak);
                    result.Append(position.Length);
                    result.Append(rowbreak);
                    var velocity = new Vector3(state.Velocity.X, state.Velocity.Y, state.Velocity.Z);
                    result.Append("Velocity [m/s]:");
                    result.Append(columnbreak);
                    result.Append(velocity.X);
                    result.Append(columnbreak);
                    result.Append(velocity.Y);
                    result.Append(columnbreak);
                    result.Append(velocity.Z);
                    result.Append(columnbreak);
                    result.Append(velocity.Length);
                    result.Append(rowbreak);
                    var acceleration = new Vector3(state.Acceleration.X, state.Acceleration.Y, state.Acceleration.Z);
                    result.Append("Acceleration [m/s²]:");
                    result.Append(columnbreak);
                    result.Append(acceleration.X);
                    result.Append(columnbreak);
                    result.Append(acceleration.Y);
                    result.Append(columnbreak);
                    result.Append(acceleration.Z);
                    result.Append(columnbreak);
                    result.Append(acceleration.Length);
                    result.Append(rowbreak);
                    var rotation = new Vector3(state.RotationalVelocity.X, state.RotationalVelocity.Y, state.RotationalVelocity.Z);
                    result.Append("Rotational Velocity [°/s]:");
                    result.Append(columnbreak);
                    result.Append(rotation.X / Constants.Radiant);
                    result.Append(columnbreak);
                    result.Append(rotation.Y / Constants.Radiant);
                    result.Append(columnbreak);
                    result.Append(rotation.Z / Constants.Radiant);
                    result.Append(columnbreak);
                    result.Append(rotation.Length / Constants.Radiant);
                    result.Append(rowbreak);
                    var attitude = new Vector3(state.RotationalAttitude.X, state.RotationalAttitude.Y, state.RotationalAttitude.Z);
                    result.Append("Euler attitude [°]:");
                    result.Append(columnbreak);
                    result.Append(attitude.X / Constants.Radiant);
                    result.Append(columnbreak);
                    result.Append(attitude.Y / Constants.Radiant);
                    result.Append(columnbreak);
                    result.Append(attitude.Z / Constants.Radiant);
                }
                else
                {
                    result.Append("unknown");
                }
            }
            result.Append(footer);
            return result.ToString();
        }

        private string GetUserPage(User user)
        {
            var result = new StringBuilder();
            const string rowbreak = "</td></tr><tr><td align=\"right\">";
            const string columnbreak = "</td><td>";
            const string header1 = "<a href=\"/\">Summary</a>-><a href=\"/users\">Users</a>->";
            const string header2 = "<br><br><table style=\"text-align: center; ";
            const string header3 = "\" border=\"0\" cellpadding=\"10\" cellspacing=\"2\"><tbody><tr><td align=\"right\">";
            const string footer = "</td></tr></tbody></table>";
            const string vheader = "<table style=\"text-align: left;\" border=\"1\" cellpadding=\"10\" cellspacing=\"0\"><thead><tr><th colspan=\"1\" align=\"center\">";
            const string headerbreak = "</th><th colspan=\"1\" align=\"center\">";
            const string headerend = "</th></tr></thead><tbody><tr><td align=\"left\">";

            result.Append(header1);
            result.Append(user.Name);
            result.Append(header2);
            result.Append(server.Configuration.HTML.TableStyle);
            result.Append(header3);
            result.Append("User:");
            result.Append(columnbreak);
            result.Append(user.Name);
            result.Append(columnbreak);
            result.Append('"');
            result.Append(user.Name);
            result.Append('"');
            result.Append(rowbreak);
            result.Append("Status:");
            result.Append(columnbreak);
            var host = user.Host;
            if (host != null)
            {
                if (host.IsContainer) result.Append("container");
                else if (host.IsAdmin) result.Append("admin");
                else result.Append("busy");
                result.Append(columnbreak);
                result.Append(host.Address);
                result.Append(':');
                result.Append(host.Port);
            }
            else result.Append("idle");
            result.Append(rowbreak);
            result.Append("Ports:");
            result.Append(columnbreak);
            result.Append("Transmit to ");
            if (host !=null)
            {
                result.Append(host.IP);
                result.Append(':');
            }
            result.Append(user.SendToPort != 0 ? user.SendToPort.ToString() : "pending");
            if (user.NAT) result.Append(" STUN");
            result.Append(columnbreak);
            result.Append("Receive on ");
            result.Append(user.ReceiverPort);
            result.Append(rowbreak);
            result.Append("Time:");
            result.Append(columnbreak);
            result.Append(HighResolutionDateTime.FromModifiedJulianDay(user.Time.Now));
            result.Append(columnbreak);
            try
            {
                result.Append(HighResolutionDateTime.FromModifiedJulianDay(user.LocalTime.Now).DateTime -
                             HighResolutionDateTime.FromModifiedJulianDay(user.Time.Now).DateTime);
                result.Append(" off");
            }
            catch (ArgumentOutOfRangeException)
            {
                result.Append("out of sync");
            }
            result.Append(rowbreak);
            result.Append("Version:");
            result.Append(columnbreak);
            result.Append(host!=null?host.Version.ToString():"unknown");
            result.Append(columnbreak);
            result.Append(columnbreak);
            result.Append(rowbreak);
            result.Append(footer);

            lock (server.Map.VesselsByUser)
            {
                if (server.Map.VesselsByUser.ContainsKey(user))
                {
                    var list = server.Map.VesselsByUser[user];
                    if (list.Count > 0)
                    {
                        result.Append(vheader);
                        result.Append("ID");
                        result.Append(headerbreak);
                        result.Append("Name");
                        result.Append(headerbreak);
                        result.Append("Class");
                        result.Append(headerend);
                        var started = false;
                        foreach (var vessel in list)
                        {
                            if (started) result.Append(rowbreak);
                            else started = true;
                            result.Append("<a href=\"/objects/");
                            result.Append(vessel.GlobalId);
                            result.Append("\">");
                            result.Append(vessel.GlobalId);
                            result.Append("</a>");
                            result.Append(columnbreak);
                            result.Append(vessel.Name);
                            result.Append(columnbreak);
                            result.Append(vessel.ClassName);
                        }
                        result.Append(footer);
                    }
                }
            }

            return result.ToString();
        }

        private string GetTopicPage()
        {
            var result = new StringBuilder();
            result.Append("<a href=\"/\">Summary</a>->Topic<br><br><pre style=\"");
            result.Append(server.Configuration.HTML.TableStyle);
            result.Append("\">");
            foreach (var line in server.Configuration.Messages.Welcome.Line)
            {
                result.Append(line);
                result.Append('\n');
            }
            result.Append("</pre>");
            return result.ToString();
        }

        private string GetObjectsPage()
        {
            const string rowbreak = "</td></tr><tr><td align=\"left\">";
            const string columnbreak = "</td><td>";
            const string header1 = "<a href=\"/\">Summary</a>->Objects<br><br><table style=\"text-align: left; ";
            const string header2 = "\" border=\"1\" cellpadding=\"10\" cellspacing=\"0\"><thead><tr><th colspan=\"1\" align=\"center\">";
            const string headerbreak = "</th><th colspan=\"1\" align=\"center\">";
            const string headerend = "</th></tr></thead><tbody><tr><td align=\"left\">";
            const string footer = "</td></tr></tbody></table>";

            var line = new StringBuilder();
            line.Append(header1);
            line.Append(server.Configuration.HTML.TableStyle);
            line.Append(header2);
            line.Append("ID");
            line.Append(headerbreak);
            line.Append("Type");
            line.Append(headerbreak);
            line.Append("Name");
            line.Append(headerbreak);
            line.Append("Data");
            line.Append(headerend);

            var started = false;
            lock (server.Map.GlobalsById)
            {
                foreach (var o in server.Map.GlobalsById.Values)
                {
                    if (started) line.Append(rowbreak);
                    else started = true;
                    line.Append("<a href=\"/objects/");
                    line.Append(o.GlobalId.ToString());
                    line.Append("\">");
                    line.Append(o.GlobalId.ToString());
                    line.Append("</a>");
                    line.Append(columnbreak);
                    var vessel = o as Vessel;
                    if (vessel != null)
                    {
                        line.Append("Vessel");
                        line.Append(columnbreak);
                        line.Append(vessel.Name);
                        line.Append(columnbreak);
                        line.Append("Class ");
                        line.Append(vessel.ClassName);
                    }
                    else
                    {
                        var body = o as CelestialBody;
                        if (body != null)
                        {
                            line.Append(body.Type);
                            line.Append(columnbreak);
                            line.Append(body.Name);
                            line.Append(columnbreak);
                            line.Append("Mass ");
                            line.Append(body.Mass.ToString());
                            line.Append("kg");
                        }
                    }
                }
            }
            line.Append(footer);
            return line.ToString();
        }

        private string GetUsersPage()
        {
            const string rowbreak = "</td></tr><tr><td align=\"left\">";
            const string columnbreak = "</td><td>";
            const string header1 = "<a href=\"/\">Summary</a>->Users<br><br><table style=\"text-align: left; ";
            const string header2 = "\" border=\"1\" cellpadding=\"10\" cellspacing=\"0\"><thead><tr><th colspan=\"1\" align=\"center\">";
            const string headerbreak = "</th><th colspan=\"1\" align=\"center\">";
            const string headerend = "</th></tr></thead><tbody><tr><td align=\"left\">";
            const string footer = "</td></tr></tbody></table>";
            
            var line = new StringBuilder();
            line.Append(header1);
            line.Append(server.Configuration.HTML.TableStyle);
            line.Append(header2);
            line.Append("Username");
            line.Append(headerbreak);
            line.Append("Nick");
            line.Append(headerbreak);
            line.Append("Vessels");
            line.Append(headerbreak);
            line.Append("Status");
            line.Append(headerend);

            var started = false;
            lock (server.Map.UsersByName) lock (server.Map.VesselsByUser)
            {
                foreach (var user in server.Map.UsersByName.Values)
                {
                    if (started) line.Append(rowbreak);
                    else started = true;
                    line.Append("<a href=\"/users/");
                    line.Append(Uri.EscapeUriString(user.Name));
                    line.Append("\">");
                    line.Append(user.Name);
                    line.Append("</a>");
                    line.Append(columnbreak);
                    line.Append(user.Nick);
                    line.Append(columnbreak);
                    if (server.Map.VesselsByUser.ContainsKey(user)) line.Append(server.Map.VesselsByUser[user].Count.ToString());
                    else line.Append("non");
                    line.Append(columnbreak);
                    var host = user.Host;
                    if (host != null)
                    {
                        if (host.IsContainer) line.Append("container");
                        else if (host.IsAdmin) line.Append("admin");
                        else line.Append("busy");
                    }
                    else line.Append("idle");
                }
            }
            line.Append(footer);
            return line.ToString();
        }

        private string GetConnectionsPage()
        {
            const string rowbreak = "</td></tr><tr><td align=\"left\">";
            const string columnbreak = "</td><td>";
            const string header1 = "<a href=\"/\">Summary</a>->Connections<br><br><table style=\"text-align: left; ";
            const string header2 = "\" border=\"1\" cellpadding=\"10\" cellspacing=\"0\"><thead><tr><th colspan=\"1\" align=\"center\">";
            const string headerbreak2 = "</th><th colspan=\"2\" align=\"center\">";
            const string headerbreak = "</th><th colspan=\"1\" align=\"center\">";
            const string headerend = "</th></tr></thead><tbody><tr><td align=\"left\">";
            const string footer = "</td></tr></tbody></table>";

            var line = new StringBuilder();
            line.Append(header1);
            line.Append(server.Configuration.HTML.TableStyle);
            line.Append(header2);
            line.Append("Type");
            line.Append(headerbreak2);
            line.Append("Data");
            line.Append(headerbreak);
            line.Append("Name");
            line.Append(headerend);

            var started = false;
            lock (server.Map.Hosts)
            {
                foreach (var host in server.Map.Hosts)
                {
                    if (started) line.Append(rowbreak);
                    else started = true;
                    var user = host.User;
                    if (user != null)
                    {
                        line.Append("UDP +<BR>TCP");
                        line.Append(columnbreak);
                        line.Append("Transmit to ");
                        line.Append(user.SendToPort != 0 ? user.SendToPort.ToString() : "pending");
                        if (user.NAT) line.Append(" STUN");
                        line.Append("<BR>Source ");
                        line.Append(host.Address);
                        line.Append(columnbreak);
                        line.Append("Receive on ");
                        line.Append(user.ReceiverPort);
                        line.Append("<BR>Target ");
                        line.Append(host.IP);
                        line.Append(columnbreak);
                        line.Append("<a href=\"/users/");
                        line.Append(Uri.EscapeUriString(user.Name));
                        line.Append("\">");
                        line.Append(user.Name);
                        line.Append("</a>");
                        line.Append("<BR>");
                        line.Append(host.Nick);
                    }
                    else
                    {
                        line.Append("TCP");
                        line.Append(columnbreak);
                        line.Append("IP ");
                        line.Append(host.Address);
                        line.Append(columnbreak);
                        line.Append("Port ");
                        line.Append(host.Port);
                        line.Append(columnbreak);
                        line.Append(host.Nick);
                    }
                }
            }
            lock (server.Map.UsersByName)
            {
                foreach (var user in server.Map.UsersByName.Values)
                {
                    if (user.Host!=null) continue;
                    if (started) line.Append(rowbreak);
                    else started = true;
                    line.Append("UDP");
                    line.Append(columnbreak);
                    line.Append("Transmit to ");
                    line.Append(user.SendToPort != 0 ? user.SendToPort.ToString() : "pending");
                    if (user.NAT) line.Append(" STUN");
                    line.Append(columnbreak);
                    line.Append("Receive on ");
                    line.Append(user.ReceiverPort);
                    line.Append(columnbreak);
                    line.Append("<a href=\"/users/");
                    line.Append(Uri.EscapeUriString(user.Name));
                    line.Append("\">");
                    line.Append(user.Name);
                    line.Append("</a>");
                }
            }
            line.Append(footer);
            return line.ToString();
        }

        private string GetSummaryPage()
        {
            var result = new StringBuilder();
            const string rowbreak = "</td></tr><tr><td align=\"right\">";
            const string columnbreak = "</td><td>";
            const string header1 = "Summary<br><br><table style=\"text-align: center; ";
            const string header2 = "\" border=\"0\" cellpadding=\"10\" cellspacing=\"2\"><tbody><tr><td align=\"right\">";
            const string footer = "</td></tr></tbody></table>";

            result.Append(header1);
            result.Append(server.Configuration.HTML.TableStyle);
            result.Append(header2);
            result.Append("<a href=\"topic\">Server</a>:");
            result.Append(columnbreak);
            result.Append(server.Configuration.Name);
            result.Append(rowbreak);
            result.Append("Address:");
            result.Append(columnbreak);
            result.Append(server.Configuration.Network.IP);
            result.Append(rowbreak);
            result.Append("Ports:");
            result.Append(columnbreak);
            result.Append("TCP ");
            result.Append(server.Configuration.Network.TCP);
            result.Append(columnbreak);
            result.Append("UDP ");
            result.Append(server.Configuration.Network.UDP);
            result.Append("-");
            result.Append(server.Configuration.Network.UDP+server.Configuration.Network.Count-1);
            result.Append(rowbreak);
            result.Append("NTP Skew:");
            result.Append(columnbreak);
            result.Append((server.NTP.Skew*1e6).ToString("F2"));
            result.Append(" µs/s");
            result.Append(columnbreak);
            result.Append("min. ");
            result.Append((server.NTP.SkewMinimum*1e6).ToString("F2"));
            result.Append(" µs/s");
            result.Append(columnbreak);
            result.Append("max. ");
            result.Append((server.NTP.SkewMaximum*1e6).ToString("F2"));
            result.Append(" µs/s");
            result.Append(rowbreak);
            result.Append("<a href=\"connections\">Connections</a>:");
            result.Append(columnbreak);
            result.Append(server.Map.Hosts.Count+server.Map.UsersByName.Count);
            result.Append(rowbreak);
            result.Append("<a href=\"users\">Users</a>:");
            result.Append(columnbreak);
            result.Append(server.Map.UsersByName.Count);
            result.Append(rowbreak);
            result.Append("<a href=\"objects\">Objects</a>:");
            result.Append(columnbreak);
            result.Append(server.Map.GlobalsById.Count);
            result.Append(footer);
            return result.ToString();
        }
    }
}
