using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Reflection;
using GoldParser;
using log4net;

namespace Orbiter.Multiplayer
{
    /// <summary>
    /// 
    /// </summary>
    public enum AddObjectResult
    {
        /// <summary>
        /// 
        /// </summary>
        OK,
        /// <summary>
        /// 
        /// </summary>
        TooMuch,
        /// <summary>
        /// 
        /// </summary>
        Collision,
    }

    /// <summary>
    /// 
    /// </summary>
    public enum RemoveObjectResult
    {
        /// <summary>
        /// 
        /// </summary>
        OK,
        /// <summary>
        /// 
        /// </summary>
        OwnUser,
        /// <summary>
        /// 
        /// </summary>
        NotFound,
        /// <summary>
        /// 
        /// </summary>
        WrongPassword,
        /// <summary>
        /// 
        /// </summary>
        Hosted,
    }

    /// <summary>
    /// 
    /// </summary>
    public class Map
    {
        private List<Host> hosts;
        private readonly Dictionary<String, User> usersByName;
        private Dictionary<UInt16,Time> spaceTimesByTimeReference;
        private readonly Dictionary<User, List<Vessel>> vesselsByUser;
        private readonly SortedDictionary<UInt16, GlobalObject> globalsById;
        private readonly Dictionary<string, string> redirectionsByClass;
        private readonly Queue<int> usedPorts;
        private readonly Queue<UInt16> usedGlobalIds;
        private UInt16 currentGlobalIds;
        private int currentPort;
        private int maxPort;
        private int basePort;
        private readonly Server server;
        private VisibilityCluster<Vessel> cluster;
        private static readonly Grammar systemConfigurationGrammar;
        private static readonly Grammar celestialBodyConfigurationGrammar;
        private string configDirectory;
        private string orbiterDirectory;
        private const string configExtension = ".cfg";
        private const string configSubDirectory = "Config";
        private static readonly ILog log = LogManager.GetLogger("    ");
        private string lastLine;
        private readonly Dictionary<UInt16, List<User>> observersById;
        private readonly Dictionary<User, List<UInt16>> observesByUser;
        private readonly Dictionary<UInt16, int> hashById;
        private Dictionary<User, List<UInt16>> offersByUser;
        private Dictionary<UInt16, List<User>> offeredToById;
        private Dictionary<User, List<UInt16>> offersForUser; 

        static Map()
        {
            //Searching names for .NET/Mono compatibility
            Assembly me = typeof (Host).Assembly;
            string[] names = me.GetManifestResourceNames();
            foreach (string name in names)
            if (name.EndsWith("SystemConfiguration.cgt"))
            {
                systemConfigurationGrammar = new Grammar(new BinaryReader(me.GetManifestResourceStream(name)));
                break;
            }
            foreach (string name in names)
            if (name.EndsWith("CelestialBodyConfiguration.cgt"))
            {
                celestialBodyConfigurationGrammar = new Grammar(new BinaryReader(me.GetManifestResourceStream(name)));
                break;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Map"/> class.
        /// </summary>
        /// <param name="server">The server.</param>
        public Map(Server server)
        {
            hosts = new List<Host>();
            usersByName = new Dictionary<string, User>();
            spaceTimesByTimeReference=new Dictionary<UInt16, Time>();
            vesselsByUser=new Dictionary<User, List<Vessel>>();
            globalsById=new SortedDictionary<ushort, GlobalObject>();
            observersById=new Dictionary<ushort, List<User>>();
            observesByUser=new Dictionary<User, List<UInt16>>();
            redirectionsByClass=new Dictionary<string, string>();
            hashById=new Dictionary<ushort, int>();
            this.server = server;
            usedPorts=new Queue<int>();
            usedGlobalIds=new Queue<UInt16>();
            offersByUser=new Dictionary<User, List<ushort>>();
            offeredToById=new Dictionary<ushort, List<User>>();
            offersForUser=new Dictionary<User, List<ushort>>();
        }

        private void InitMisc()
        {
            if (cluster!=null) lock (cluster){CreateCluster();}
            else CreateCluster();
            lock (usedPorts)
            {
                var newBasePort = server.Configuration.Network.UDP;
                var newMaxPort = newBasePort + server.Configuration.Network.Count;
                var portsInUse = new List<int>();
                for(var i=currentPort;--i>=basePort;) if (i>=newBasePort && i<newMaxPort) portsInUse.Add(i);
                foreach (var port in usedPorts) if (portsInUse.Contains(port)) portsInUse.Remove(port);
                usedPorts.Clear();
                currentPort = portsInUse.Count > 0 ? portsInUse[0] + 1 : newBasePort;
                for (var i = newBasePort; i < currentPort; i++) if (!portsInUse.Contains(i)) usedPorts.Enqueue(i);
                basePort = newBasePort;
                maxPort = newMaxPort;
            }
            orbiterDirectory =
                server.Configuration.OrbiterRoot != null
                    ? Path.Combine(Directory.GetCurrentDirectory(), server.Configuration.OrbiterRoot)
                    : Directory.GetCurrentDirectory();
            configDirectory = Path.Combine(orbiterDirectory, configSubDirectory);
            if (server.Configuration.PromptFormat == null) server.Configuration.PromptFormat = "{0}";
        }

        private void CreateCluster()
        {
            if (server.Configuration.FieldOfViewSpecified && server.Configuration.VerticalPixelsSpecified)
                cluster = new VisibilityCluster<Vessel>((uint) server.Configuration.VerticalPixels,
                                                        server.Configuration.FieldOfView);
            else cluster = new VisibilityCluster<Vessel>(server.Configuration.SubvisualDistance);
        }

        /// <summary>
        /// Gets the free port.
        /// </summary>
        /// <returns></returns>
        public int GetFreePort()
        {
            lock (usedPorts)
            {
                if (usedPorts.Count > 0) return usedPorts.Dequeue();
                if (currentPort < maxPort) return currentPort++;
                return -1;
            }
        }

        /// <summary>
        /// Releases the port.
        /// </summary>
        /// <param name="port">The port.</param>
        public void ReleasePort(int port)
        {
            lock (usedPorts)
            {
                if (port < basePort || port >= maxPort) return;
                usedPorts.Enqueue(port);
                if (usedPorts.Count < currentPort - basePort) return;
                usedPorts.Clear();
                currentPort = basePort;
            }
        }

        /// <summary>
        /// Gets the global id.
        /// </summary>
        /// <returns></returns>
        private UInt16 GetGlobalId()
        {
            lock (usedGlobalIds)
            {
                if (usedGlobalIds.Count > 0) return usedGlobalIds.Dequeue();
                return currentGlobalIds < UInt16.MaxValue ? currentGlobalIds++ : UInt16.MaxValue;
            }
        }

        /// <summary>
        /// Releases the global id.
        /// </summary>
        /// <param name="id">The id.</param>
        private void ReleaseGlobalId(UInt16 id)
        {
            lock (usedGlobalIds)
            {
                if (id != UInt16.MaxValue)
                {
                    usedGlobalIds.Enqueue(id);
                    if (usedGlobalIds.Count >= currentGlobalIds)
                    {
                        usedGlobalIds.Clear();
                        currentGlobalIds = 0;
                    }
                }
                if (hashById.ContainsKey(id))
                {
                    log.Info("Global object " + hashById[id] + " with ID " + id + " released");
                    hashById.Remove(id);
                }
                else
                {
                    log.Warn("Unknown global object ID " + id + " released!");
                }
            }
        }

        /// <summary>
        /// Releases the global id associated with the global object hashcode.
        /// </summary>
        /// <param name="id">The id to be removed.</param>
        /// <param name="hash">The hash-code of the calling object</param>
        public void ReleaseGlobalId(UInt16 id, int hash)
        {
            lock (hashById)
            {
                if (!hashById.ContainsKey(id))
                {
                    log.Debug("Ignoring object " + hash + " with ID " + id + ": already released");
                    return;
                }
                if (hashById[id]!=hash)
                {
                    log.Debug("Ignoring object " + hash + " with ID " + id + ": occupied by object " + hashById[id]);
                    return;
                }
                ReleaseGlobalId(id);
            }
        }


        /// <summary>
        /// Gets the hosts.
        /// </summary>
        /// <value>The hosts.</value>
        public List<Host> Hosts
        {
            get
            {
                return hosts;
            }
        }

        /// <summary>
        /// Gets the name of the users by.
        /// </summary>
        /// <value>The name of the users by.</value>
        public Dictionary<string, User> UsersByName
        {
            get
            {
                return usersByName;
            }
        }

        /// <summary>
        /// Gets the globals by id.
        /// </summary>
        /// <value>The globals by id.</value>
        public SortedDictionary<ushort, GlobalObject> GlobalsById
        {
            get { return globalsById; }
        }

        /// <summary>
        /// Gets the vessels by user.
        /// </summary>
        /// <value>The vessels by user.</value>
        public Dictionary<User, List<Vessel>> VesselsByUser
        {
            get { return vesselsByUser; }
        }

        /// <summary>
        /// Gets the observers by id.
        /// </summary>
        /// <value>The observers by id.</value>
        public Dictionary<ushort, List<User>> ObserversById
        {
            get { return observersById; }
        }

        /// <summary>
        /// Gets the observes by user.
        /// </summary>
        /// <value>The observes by user.</value>
        public Dictionary<User, List<ushort>> ObservesByUser
        {
            get { return observesByUser; }
        }

        /// <summary>
        /// Gets the list of users that the identifier is offered to.
        /// </summary>
        public Dictionary<ushort, List<User>> OfferedToById
        {
            get { return offeredToById; }
        }

        /// <summary>
        /// Gets the offers by user.
        /// </summary>
        public Dictionary<User, List<ushort>> OffersByUser
        {
            get { return offersByUser; }
        }

        /// <summary>
        /// Gets the offers for a user.
        /// </summary>
        public Dictionary<User, List<ushort>> OffersForUser
        {
            get { return offersForUser; }
        }

        /// <summary>
        /// Gets the cluster.
        /// </summary>
        /// <value>The cluster.</value>
        public VisibilityCluster<Vessel> Cluster
        {
            get { return cluster; }
        }

        /// <summary>
        /// Gets the user.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <returns></returns>
        public User GetUser(string user)
        {
            lock (usersByName)
            {
                if (usersByName.ContainsKey(user)) return usersByName[user];
                return null;
            }
        }

        /// <summary>
        /// Gets the vessel class.
        /// </summary>
        /// <param name="vesselClass">The vessel class.</param>
        /// <returns></returns>
        public string GetVesselClass(string vesselClass)
        {
            lock(redirectionsByClass)
            {
                string result=null;
                vesselClass = vesselClass.ToLower();
                if (redirectionsByClass.ContainsKey(vesselClass)) result = redirectionsByClass[vesselClass];
                if (result == null && redirectionsByClass.ContainsKey("*")) result = redirectionsByClass["*"];
                return result == "*" ? vesselClass : result;
            }
        }

        /// <summary>
        /// Inits the redirections.
        /// </summary>
        private void InitRedirections()
        {
            lock(redirectionsByClass)
            {
                redirectionsByClass.Clear();
                foreach (var o in server.Configuration.Support[0].Items)
                {
                    var vesselClass=o as OMPServerConfigurationSystemClass;
                    if (vesselClass != null)
                        redirectionsByClass[vesselClass.Name.ToLower()] = vesselClass.Redirect ?? "*";
                }
            }
        }

        /// <summary>
        /// Inits the globals.
        /// </summary>
        private void InitGlobals()
        {
            lock (globalsById)
            {
                //Remove old static globals
                List<UInt16> list=new List<ushort>(globalsById.Count);
                foreach (GlobalObject o in globalsById.Values)
                {
                    if (!(o is Vessel)) list.Add(o.GlobalId);
                }
                foreach (UInt16 id in list)
                {
                    globalsById.Remove(id);
                    ReleaseGlobalId(id);
                }
                GC.Collect();
                //Build new static globals
                foreach (OMPServerConfigurationSystem system in server.Configuration.Support)
                {
                    string file = Path.Combine(configDirectory, system.Name + configExtension);
                    DecodeSystemConfiguration(file, system.Name);
                }
                foreach (GlobalObject o in globalsById.Values)
                {
                    CelestialBody body = o as CelestialBody;
                    if (body!=null)
                    {
                        string file = Path.Combine(configDirectory, body.Name + configExtension);
                        DecodeCelestialBodyConfiguration(file, body);
                    }
                }
            }
        }

        /// <summary>
        /// Reads the call back.
        /// </summary>
        /// <param name="parser">The parser.</param>
        /// <param name="lineStart">The line start.</param>
        /// <param name="lineLength">Length of the line.</param>
        private void ReadCallBack(Parser parser, int lineStart, int lineLength)
        {
            lastLine=parser.LineText;
        }

        /// <summary>
        /// Decodes the celestial body configuration.
        /// </summary>
        /// <param name="file">The file.</param>
        /// <param name="body">The body.</param>
        private void DecodeCelestialBodyConfiguration(string file, CelestialBody body)
        {
            var cbc = new Parser(File.OpenText(file), celestialBodyConfigurationGrammar)
            {
                SourceLineReadCallback = ReadCallBack,
                TrimReductions = false
            };
            while (true)
            {
                switch (cbc.Parse())
                {
                    case ParseMessage.Accept:
                        var node = (CelestialBodyConfigurationNode) cbc.TokenSyntaxNode;
                        foreach (CelestialBodyConfigurationNode n in node)
                        {
                            switch (n.SymbolIndex)
                            {
                                case CelestialBodyConfigurationSymbols.S_Lines:
                                    var n2 = n;
                                    while (n2.RuleIndex == CelestialBodyConfigurationRules.Lines)
                                    {
                                        DecodeCelestialBodyConfigurationLine(n2[2], body);
                                        n2 = n2[0];
                                    }
                                    DecodeCelestialBodyConfigurationLine(n2[0], body);
                                    break;
                            }
                        }
                        return;
                    case ParseMessage.InternalError:
                        log.Error("Error while parsing file '" + file + "' at line " + cbc.LineNumber + ":\n\r" +
                                  lastLine + "\n\r" + /*new string(' ', cbc.LinePosition - 1) + */"^ Internal error\n\r");
                        return;
                    case ParseMessage.LexicalError:
                        log.Error("Error while parsing file '" + file + "' at line " + cbc.LineNumber + ":\n\r" +
                                  lastLine + "\n\r" + /*new string(' ', cbc.LinePosition - 1) +*/
                                  "^ Lexical error in token '" + " " + "'\n\r");
                        return;
                    case ParseMessage.SyntaxError:
                        if (cbc.LinePosition != 1 || cbc.TokenText.Length != 0)
                        {
                            log.Error("Error while parsing file '" + file + "' at line " + cbc.LineNumber + ":\n\r" +
                                      lastLine + "\n\r" + /*new string(' ', cbc.LinePosition - cbc.TokenText.Length - 1) +*/
                                      "^ Syntax error\n\r");
                        }
                        return;
                    case ParseMessage.NotLoadedError:
                        throw new SystemException("Grammar not loaded!");
                    case ParseMessage.Empty:
                        return;
                    case ParseMessage.TokenRead:
                        cbc.TokenSyntaxNode = new CelestialBodyConfigurationNode(cbc.TokenSymbol, cbc.TokenText);
                        break;
                    case ParseMessage.Reduction:
                        var childs = new CelestialBodyConfigurationNode[cbc.ReductionCount];
                        for (var i = 0; i < childs.Length; i++) childs[i] = cbc.GetReductionSyntaxNode(i) as CelestialBodyConfigurationNode;
                        cbc.TokenSyntaxNode = new CelestialBodyConfigurationNode(cbc.ReductionRule, childs);
                        break;
                }
            }
        }

        /// <summary>
        /// Decodes the celestial body configuration line.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="body">The body.</param>
        private void DecodeCelestialBodyConfigurationLine(CelestialBodyConfigurationNode node, CelestialBody body)
        {
            switch(node.RuleIndex)
            {
                case CelestialBodyConfigurationRules.Line:
                    node = node[0];
                    switch (node.Trimmed.Content)
                    {
                        case "Name":
                            body.Name = node[2].Trimmed.Content;
                            log.Info("["+body.GlobalId+"] named '"+body.Name+"'");
                            break;
                        case "Mass":
                            body.Mass = Convert.ToDouble(node[2].Trimmed.Content, CultureInfo.InvariantCulture.NumberFormat);
                            log.Info("["+body.GlobalId+"] has mass of "+body.Mass+"kg");
                            break;
                    }
                    break;
            }
        }

        /// <summary>
        /// Decodes the system configuration.
        /// </summary>
        /// <param name="file">The file.</param>
        /// <param name="system">The system.</param>
        private void DecodeSystemConfiguration(string file, string system)
        {
            Parser sc = new Parser(File.OpenText(file), systemConfigurationGrammar);
            sc.TrimReductions = false;
            while (true)
            {
                switch (sc.Parse())
                {
                    case ParseMessage.Accept:
                        var node = (SystemConfigurationNode) sc.TokenSyntaxNode;
                        foreach (SystemConfigurationNode n in node)
                        {
                            switch (n.SymbolIndex)
                            {
                                case SystemConfigurationSymbols.S_Name2:
                                    string systemName = n[4].Trimmed.Content;
                                    if (systemName != system)
                                        log.Warn("Detected system's name '" + systemName + "' isn't equal to file name '" + system +
                                                 "'!");
                                    break;
                                case SystemConfigurationSymbols.S_Lines:
                                    var n2 = n;
                                    while (n2.RuleIndex == SystemConfigurationRules.Lines)
                                    {
                                        DecodeSystemConfigurationLine(n2[2]);
                                        n2 = n2[0];
                                    }
                                    DecodeSystemConfigurationLine(n2[0]);
                                    break;
                            }
                        }
                        return;
                    case ParseMessage.InternalError:
                        log.Error("Error while parsing file '" + file + "' at line " + sc.LineNumber + ":\n\r" +
                                  sc.LineText + "\n\r" + new string(' ', sc.LinePosition - 1) + "^ Internal error\n\r");
                        return;
                    case ParseMessage.LexicalError:
                        log.Error("Error while parsing file '" + file + "' at line " + sc.LineNumber + ":\n\r" +
                                  sc.LineText + "\n\r" + new string(' ', sc.LinePosition - 1) +
                                  "^ Lexical error in token '" + sc.TokenText + "'\n\r");
                        return;
                    case ParseMessage.SyntaxError:
                        if (sc.LinePosition != 1 || sc.TokenText.Length != 0)
                        {
                            log.Error("Error while parsing file '" + file + "' at line " + sc.LineNumber + ":\n\r" +
                                      sc.LineText + "\n\r" + new string(' ', sc.LinePosition - sc.TokenText.Length - 1) +
                                      "^ Syntax error\n\r");
                        }
                        return;
                    case ParseMessage.NotLoadedError:
                        throw new SystemException("Grammar not loaded!");
                    case ParseMessage.Empty:
                        return;
                    case ParseMessage.TokenRead:
                        sc.TokenSyntaxNode = new SystemConfigurationNode(sc.TokenSymbol, sc.TokenText);
                        break;
                    case ParseMessage.Reduction:
                        SystemConfigurationNode[] childs = new SystemConfigurationNode[sc.ReductionCount];
                        for (int i = 0; i < childs.Length; i++) childs[i] = sc.GetReductionSyntaxNode(i) as SystemConfigurationNode;
                        sc.TokenSyntaxNode = new SystemConfigurationNode(sc.ReductionRule, childs);
                        break;
                }
            }
        }

        /// <summary>
        /// Decodes the system configuration line.
        /// </summary>
        /// <param name="node">The node.</param>
        private void DecodeSystemConfigurationLine(SystemConfigurationNode node)
        {
            var id = GetGlobalId();
            var name = node[0][node[0].Count - 1].Trimmed.Content;
            CelestialBody body = null;
            if (id < UInt16.MaxValue)
            {
                if (!globalsById.ContainsKey(id))
                {
                    int index;
                    switch (node.RuleIndex)
                    {
                        case SystemConfigurationRules.Line:
                            index = Convert.ToInt32(node[0][1].Trimmed.Content);
                            body = new CelestialBody(server, CelestialBodyType.Star, index, null){Name = name, GlobalId = id};
                            log.Info("Added star "+index+" '"+name+"' as "+id);
                            break;
                        case SystemConfigurationRules.Line2:
                            index = Convert.ToInt32(node[0][1].Trimmed.Content);
                            body = new CelestialBody(server, CelestialBodyType.Planet, index, null){Name = name, GlobalId = id};
                            log.Info("Added planet " + index + " '" + name + "' as " + id);
                            break;
                        case SystemConfigurationRules.Line3:
                            index = Convert.ToInt32(node[0][5].Trimmed.Content);
                            body = new CelestialBody(server, CelestialBodyType.Moon, index, null){Name = name, GlobalId = id};
                            log.Info("Added moon " + index + " '" + name + "' with parent planet '"+node[0][0].Trimmed.Content+"' as " + id);
                            break;
                    }
                    if (body!=null)
                    {
                        globalsById.Add(id, body);
                        hashById.Add(id, body.GetHashCode());
                    }
                }
                else log.Error("Can't add celestial object '" + name + "'! Table inconsistencies: Id " + id + " already exists.");
            }
            else log.Error("Can't add celestial object '" + name + "'! Too much global objects.");
        }

        /// <summary>
        /// Adds the specified host.
        /// </summary>
        /// <param name="host">The host.</param>
        public void Add(Host host)
        {
            lock (hosts)
            {
                if (hosts.Contains(host))
                    throw new DuplicateNameException("Host already connected!");
                hosts.Add(host);
                host.HostDisconnected += new EventHandler<HostDisconnectedEventArgs>(host_HostDisconnected);
                host.Start();
            }
        }

        /// <summary>
        /// Adds the specified observation to the user.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="observe">The observed object.</param>
        /// <returns></returns>
        public AddObjectResult Add(User user, UInt16 observe)
        {
            lock(observersById)
            {
                List<UInt16> ids;
                List<User> users;
                if (observesByUser.ContainsKey(user)) ids=observesByUser[user];
                else
                {
                    ids=new List<UInt16>();
                    observesByUser.Add(user, ids);
                }
                if (observersById.ContainsKey(observe)) users = observersById[observe];
                else
                {
                    users=new List<User>();
                    observersById.Add(observe, users);
                }
                if (!users.Contains(user)) users.Add(user);
                if (ids.Contains(observe)) return AddObjectResult.Collision;
                ids.Add(observe);
            }
            return AddObjectResult.OK;
        }

        public AddObjectResult Add(UInt16 offer, User sender, User receiver)
        {
            lock (offeredToById)
            {
                var users=new List<User>();
                if (offeredToById.ContainsKey(offer)) users = offeredToById[offer];
                else offeredToById.Add(offer, users);
                if (users.Contains(receiver)) return AddObjectResult.Collision;
                users.Add(receiver);

                var offers = new List<UInt16>();
                if (offersByUser.ContainsKey(sender)) offers = offersByUser[sender];
                else offersByUser.Add(sender, offers);
                if (!offers.Contains(offer)) offers.Add(offer); //Note: a sender could offer to several receivers

                offers = new List<UInt16>();
                if (offersForUser.ContainsKey(receiver)) offers = offersForUser[receiver];
                else offersForUser.Add(receiver, offers);
                offers.Add(offer); //Note: since ID key is global and collision detection above should filter double-offers of one sender, a collision here should never occur!
            }
            return AddObjectResult.OK;
        }

        public RemoveObjectResult Remove(UInt16 offer, User sender)
        {
            lock (offeredToById)
            {
                if (!offersByUser.ContainsKey(sender)) return RemoveObjectResult.NotFound;
                if (!offeredToById.ContainsKey(offer)) return RemoveObjectResult.NotFound;
                //Note: if any indexer from here throws an exception, the database got corrupted due to a systematical error
                offersByUser[sender].Remove(offer); 
                if (offersByUser[sender].Count == 0) offersByUser.Remove(sender);
                var receivers = offeredToById[offer];
                offeredToById.Remove(offer);
                foreach (var receiver in receivers)
                {
                    offersForUser[receiver].Remove(offer);
                    if (offersForUser[receiver].Count == 0) offersForUser.Remove(receiver);
                }
            }
            return RemoveObjectResult.OK;
        }

        /// <summary>
        /// Removes the specified user.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="observe">The observe.</param>
        /// <returns></returns>
        public RemoveObjectResult Remove(User user, UInt16 observe)
        {
            RemoveObjectResult result=RemoveObjectResult.OK;
            lock (observersById)
            {
                if (!observesByUser.ContainsKey(user)) result=RemoveObjectResult.NotFound;
                else
                {
                    List<UInt16> ids = observesByUser[user];
                    if (!ids.Contains(observe)) result = RemoveObjectResult.NotFound;
                    else ids.Remove(observe);
                    if (ids.Count == 0) observesByUser.Remove(user);
                }
                if (!observersById.ContainsKey(observe)) result = RemoveObjectResult.NotFound;
                else
                {
                    List<User> users = observersById[observe];
                    if (!users.Contains(user)) result = RemoveObjectResult.NotFound;
                    else users.Remove(user);
                    if (users.Count == 0) observersById.Remove(observe);
                }
            }
            return result;
        }

        /// <summary>
        /// Adds the specified user.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="password">The password.</param>
        /// <returns></returns>
        public AddObjectResult Add(string user, string password)
        {
            lock (usersByName)
            {
                if (usersByName.ContainsKey(user)) return AddObjectResult.Collision;
                int nextPort = GetFreePort();
                if (nextPort<0) return AddObjectResult.TooMuch;
                User newUser=new User(user, nextPort, password, server);
                usersByName.Add(user, newUser);
                newUser.Start();
                return AddObjectResult.OK;
            }
        }

        /// <summary>
        /// Adds the specified vessel.
        /// </summary>
        /// <param name="vessel">The vessel.</param>
        /// <returns></returns>
        public AddObjectResult Add(Vessel vessel)
        {
            lock (globalsById)
            {
                List<Vessel> vessels;
                UInt16 globalId;
                if (vesselsByUser.ContainsKey(vessel.User))
                {
                    vessels = vesselsByUser[vessel.User];
                    if (vessels.Contains(vessel)) return AddObjectResult.Collision;
                    globalId = GetGlobalId();
                    if (globalsById.ContainsKey(globalId)) return AddObjectResult.Collision;
                    if (globalId == UInt16.MaxValue) return AddObjectResult.TooMuch;
                    vessels.Add(vessel);
                }
                else
                {
                    globalId = GetGlobalId();
                    if (globalsById.ContainsKey(globalId)) return AddObjectResult.Collision;
                    if (globalId == UInt16.MaxValue) return AddObjectResult.TooMuch;
                    vessels = new List<Vessel> {vessel};
                    vesselsByUser[vessel.User] = vessels;
                }
                vessel.GlobalId = globalId;
                globalsById.Add(globalId, vessel);
                hashById.Add(globalId, vessel.GetHashCode());
                return AddObjectResult.OK;
            }
        }

        /// <summary>
        /// Handles the HostDisconnected event of the host control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Orbiter.Multiplayer.HostDisconnectedEventArgs"/> instance containing the event data.</param>
        void host_HostDisconnected(object sender, HostDisconnectedEventArgs e)
        {
            Remove(sender as Host);
        }

        /// <summary>
        /// Removes the specified host.
        /// </summary>
        /// <param name="host">The host.</param>
        public void Remove(Host host)
        {
            lock (hosts)
            {
                if (!hosts.Contains(host))
                    throw new ArgumentException("Host not connected!");
                RemoveInternal(host);
                hosts.Remove(host);
            }
        }

        /// <summary>
        /// Removes the specified user.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <param name="password">The password.</param>
        /// <param name="host">The host.</param>
        /// <returns></returns>
        public RemoveObjectResult Remove(String user, string password, Host host)
        {
            lock (usersByName)
            {
                if (!usersByName.ContainsKey(user)) return RemoveObjectResult.NotFound;
                User u = usersByName[user];
                if (password!=server.Configuration.Password.Admin)
                    if (!u.IsValidPassword(password))
                        return RemoveObjectResult.WrongPassword;
                if (u.Host != host && u.Host != null) return RemoveObjectResult.Hosted;
                RemoveInternal(usersByName[user]);
                usersByName.Remove(user);
                if (u.Host == host) return RemoveObjectResult.OwnUser;
                return RemoveObjectResult.OK;
            }
        }

        /// <summary>
        /// Removes the specified vessel.
        /// </summary>
        /// <param name="vessel">The vessel.</param>
        /// <returns></returns>
        public RemoveObjectResult HandOver(Vessel vessel, User user, UInt16 localId)
        {
            lock (usersByName)
            {
                if (!vesselsByUser.ContainsKey(vessel.User)) return RemoveObjectResult.NotFound;
                var vessels = vesselsByUser[vessel.User];
                if (!vessels.Contains(vessel)) return RemoveObjectResult.NotFound;
                vessels.Remove(vessel);
                if (vessels.Count == 0) vesselsByUser.Remove(vessel.User);

                vessel.User.HandOver(vessel);
                user.HandOver(vessel, localId);

                if (vesselsByUser.ContainsKey(vessel.User))
                {
                    vessels = vesselsByUser[vessel.User];
                    if (vessels.Contains(vessel)) return RemoveObjectResult.Hosted;
                    vessels.Add(vessel);
                }
                else
                {
                    vessels = new List<Vessel> {vessel};
                    vesselsByUser[vessel.User] = vessels;
                }
            }
            return RemoveObjectResult.OK;
        }

        /// <summary>
        /// Removes the specified vessel.
        /// </summary>
        /// <param name="vessel">The vessel.</param>
        /// <returns></returns>
        public RemoveObjectResult Remove(Vessel vessel)
        {
            lock(cluster) cluster.Remove(vessel);
            lock (observersById)
            {
                if (observersById.ContainsKey(vessel.GlobalId))
                {
                    List<User> users = observersById[vessel.GlobalId];
                    foreach (User user in users)
                    {
                        if (observesByUser.ContainsKey(user))
                        {
                            List<UInt16> ids=observesByUser[user];
                            ids.Remove(vessel.GlobalId);
                            if (ids.Count == 0) observesByUser.Remove(user);
                        }
                    }
                    observersById.Remove(vessel.GlobalId);
                }
            }
            lock (offeredToById)
            {
                if (offeredToById.ContainsKey(vessel.GlobalId))
                {
                    offersByUser[vessel.User].Remove(vessel.GlobalId);
                    if (offersByUser[vessel.User].Count == 0) offersByUser.Remove(vessel.User);
                    foreach (var user in offeredToById[vessel.GlobalId])
                    {
                        offersForUser[user].Remove(vessel.GlobalId);
                        if (offersForUser[user].Count == 0) offersForUser.Remove(user);
                    }
                    offeredToById.Remove(vessel.GlobalId);
                }
            }
            lock(globalsById)
            {
                if (!vesselsByUser.ContainsKey(vessel.User)) return RemoveObjectResult.NotFound;
                var vessels = vesselsByUser[vessel.User];
                if (!vessels.Contains(vessel)) return RemoveObjectResult.NotFound;
                vessels.Remove(vessel);
                if (vessels.Count == 0) vesselsByUser.Remove(vessel.User);
                if (!globalsById.ContainsKey(vessel.GlobalId)) return RemoveObjectResult.NotFound;
                if (globalsById[vessel.GlobalId]!=vessel) return RemoveObjectResult.NotFound;
                globalsById.Remove(vessel.GlobalId);
                return RemoveObjectResult.OK;
            }
        }

        /// <summary>
        /// Removes the internal.
        /// </summary>
        /// <param name="host">The host.</param>
        private void RemoveInternal(Host host)
        {
            host.HostDisconnected -= new EventHandler<HostDisconnectedEventArgs>(host_HostDisconnected);
            host.Stop();
            //move user to container
        }

        /// <summary>
        /// Removes the internal.
        /// </summary>
        /// <param name="user">The user.</param>
        private void RemoveInternal(User user)
        {
            user.Stop();
            ReleasePort(user.ReceiverPort);
            List<Vessel> vessels = null;
            lock (observersById)
            {
                if (observesByUser.ContainsKey(user))
                {
                    List<UInt16> ids = observesByUser[user];
                    foreach (UInt16 id in ids)
                    {
                        if (observersById.ContainsKey(id))
                        {
                            List<User> users = observersById[id];
                            users.Remove(user);
                            if (users.Count == 0) observersById.Remove(id);
                        }
                    }
                    observesByUser.Remove(user);
                }
            }
            lock (offeredToById)
            {
                if (offersByUser.ContainsKey(user))
                {
                    var offers = offersByUser[user];
                    offersByUser.Remove(user);
                    foreach (var offer in offers)
                    {
                        var users = offeredToById[offer];
                        offeredToById.Remove(offer);
                        foreach (var usr in users)
                        {
                            offersForUser[usr].Remove(offer);
                            if (offersForUser[usr].Count == 0) offersForUser.Remove(usr);
                        }
                    }
                }
                if (offersForUser.ContainsKey(user))
                {
                    var offers = offersForUser[user];
                    offersForUser.Remove(user);
                    lock (globalsById) foreach (var offer in offers)
                    {
                        offeredToById[offer].Remove(user);
                        if (offeredToById[offer].Count != 0) continue;
                        offeredToById.Remove(offer);
                        var usr = ((Vessel) globalsById[offer]).User;
                        offersByUser[usr].Remove(offer);
                        if (offersByUser[usr].Count == 0) offersByUser.Remove(usr);
                    }
                }
            }
            lock (globalsById)
            {
                if (vesselsByUser.ContainsKey(user)) vessels = new List<Vessel>(vesselsByUser[user]);
            }
            if (vessels != null) foreach (Vessel v in vessels) Remove(v);
        }

        /// <summary>
        /// Clears this instance.
        /// </summary>
        public void Clear()
        {
            List<Host> toBeCleared = hosts;
            hosts=new List<Host>();
            foreach (Host host in toBeCleared) RemoveInternal(host);
            lock(usersByName)
            {
                foreach (User user in usersByName.Values) RemoveInternal(user);
                usersByName.Clear();
            }
        }

        /// <summary>
        /// Reconfigures this instance.
        /// </summary>
        public void Reconfigure()
        {
            InitMisc();
            InitRedirections();
            InitGlobals();
        }
    }
}
