using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using GoldParser;
using log4net;

namespace Orbiter.Multiplayer
{
    /// <summary>
    /// 
    /// </summary>
    public enum HostDisconnectedReason
    {
        /// <summary>
        /// 
        /// </summary>
        ByServer,
        /// <summary>
        /// 
        /// </summary>
        ByClient,
        /// <summary>
        /// 
        /// </summary>
        ByError,
        /// <summary>
        /// 
        /// </summary>
        ByException,
    }

    /// <summary>
    /// 
    /// </summary>
    public class HostDisconnectedEventArgs:EventArgs
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="HostDisconnectedEventArgs"/> class.
        /// </summary>
        /// <param name="reason">The reason.</param>
        /// <param name="error">The error.</param>
        /// <param name="exception">The exception.</param>
        public HostDisconnectedEventArgs(HostDisconnectedReason reason, SocketError error, Exception exception)
        {
            Reason = reason;
            Error = error;
            Exception = exception;
        }

        /// <summary>
        /// Gets or sets the reason.
        /// </summary>
        /// <value>The reason.</value>
        public HostDisconnectedReason Reason { get; private set; }

        /// <summary>
        /// Gets or sets the error.
        /// </summary>
        /// <value>The error.</value>
        public SocketError Error { get; private set; }

        /// <summary>
        /// Gets or sets the exception.
        /// </summary>
        /// <value>The exception.</value>
        public Exception Exception { get; private set; }
    }

    /// <summary>
    /// 
    /// </summary>
    public class Host
    {
        /// <summary>
        /// Gets or sets the user.
        /// </summary>
        /// <value>The user.</value>
        public User User { get; private set; }
        private readonly Socket socket;
        private readonly Server server;
        private Thread commandInterpreter;
        private bool active;
        private bool localEcho;
        private readonly ASCIIEncoding encoder;
        private static readonly Grammar commandGrammar;
        private static readonly List<String> helpPage;
        private static readonly List<String> header;
        private string nick;
        private static readonly ILog log = LogManager.GetLogger(" TCP");
        private static readonly ILog chat = LogManager.GetLogger("Chat");
        private ListCommand listCommand;
        private readonly List<UInt16> tracks;
        private UInt16 ping;
        private string prompt;
        private IPAddress lastKnownAddress;
        private const string IFTAG = "IF ";
        private const string FITAG = "FI";
        private const string DESCTAG = "DESC";

        /// <summary>
        /// Gets or sets a value indicating whether this instance is admin.
        /// </summary>
        /// <value><c>true</c> if this instance is admin; otherwise, <c>false</c>.</value>
        public bool IsAdmin { get; private set; }

        /// <summary>
        /// Gets a value indicating whether this instance is a container.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is a container; otherwise, <c>false</c>.
        /// </value>
        public bool IsContainer { get; private set; }

        [Flags]
        private enum ListCommand
        {
            AllUsers=0,
            OwnUsers,
            CurrentUsers,
            TargetUsers,
            AllObjects,
            OwnObjects,
            CurrentObjects,
            TargetObjects,
            AllUsersDetailed,
            OwnUsersDetailed,
            CurrentUsersDetailed,
            TargetUsersDetailed,
            AllObjectsDetailed,
            OwnObjectsDetailed,
            CurrentObjectsDetailed,
            TargetObjectsDetailed,
        }

        /// <summary>
        /// Gets the ping.
        /// </summary>
        /// <value>The ping.</value>
        public UInt16 Ping
        {
            get { return ping; }
        }

        /// <summary>
        /// Gets the tracks.
        /// </summary>
        /// <value>The tracks.</value>
        public List<UInt16> Tracks
        {
            get { return tracks; }
        }

        /// <summary>
        /// Gets or sets the nick.
        /// </summary>
        /// <value>The nick.</value>
        public string Nick
        {
            get
            {
                if (User!=null) return User.Nick;
                return nick ?? Address.ToString();
            }
            set
            {
                if (User != null)
                {
                    if (string.IsNullOrEmpty(value)) value = nick;
                    User.Nick = value;
                }
                nick = value;
            }
        }

        /// <summary>
        /// Gets or sets the prompt.
        /// </summary>
        /// <value>The prompt.</value>
        public string Prompt
        {
            get
            {
                return prompt;
            }
            set
            {
                prompt = string.IsNullOrEmpty(value) ? server.Configuration.PromptFormat : value;
                if (prompt.StartsWith("{0")) prompt = ">" + prompt;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Host"/> class.
        /// </summary>
        /// <param name="socket">The socket.</param>
        /// <param name="server">The server.</param>
        public Host(Socket socket, Server server)
        {
            this.socket = socket;
            this.server = server;
            encoder = new ASCIIEncoding();
            listCommand = ListCommand.AllUsers;
            tracks = new List<UInt16>();
            lastKnownAddress=IPAddress.None;
            prompt = server.Configuration.PromptFormat;
            Version = new Version {Tag = null, TagDistance = 0};
        }

        /// <summary>
        /// The host's local IP
        /// </summary>
        public IPAddress HairPinningIP { get; private set; }

        /// <summary>
        /// The host's global IP
        /// </summary>
        public IPAddress IP { get; private set; }

        static Host()
        {
            //Searching names for .NET/Mono compatibility
        	foreach (var name in typeof(Host).Assembly.GetManifestResourceNames())
        	if (name.EndsWith("CommandLine.cgt"))
        	{
            	commandGrammar=new Grammar(new BinaryReader(typeof(Host).Assembly.GetManifestResourceStream(name)));
            	break;
            }
            foreach (var name in typeof(Host).Assembly.GetManifestResourceNames())
            if (name.EndsWith("HelpPage.txt"))
            {
                helpPage=new List<string>();
                TextReader helpPageReader = new StreamReader(typeof (Host).Assembly.GetManifestResourceStream(name));
                string s;
                while((s=helpPageReader.ReadLine())!=null) helpPage.Add(s);
                break;
            }
            foreach (var name in typeof(Host).Assembly.GetManifestResourceNames())
            if (name.EndsWith("Header.txt"))
            {
                header = new List<string>();
                TextReader helpPageReader = new StreamReader(typeof(Host).Assembly.GetManifestResourceStream(name));
                string s;
                while ((s = helpPageReader.ReadLine()) != null) header.Add("CHECK |" + s + "\n\r");
                break;
            }
        }

        /// <summary>
        /// Occurs when [host disconnected].
        /// </summary>
        public event EventHandler<HostDisconnectedEventArgs> HostDisconnected;
        
        protected virtual void OnHostDisconnected(HostDisconnectedEventArgs e)
        {
            if (IsAdmin)
                switch (e.Reason)
                {
                    case HostDisconnectedReason.ByError:
                        log.Error("Error receiving from administrator at port "+Port+" due to error "+e.Error);
                        break;
                    case HostDisconnectedReason.ByException:
                        log.Error("Error receiving from administrator at port "+Port+" due to exception",e.Exception);
                        break;
                    case HostDisconnectedReason.ByServer:
                    case HostDisconnectedReason.ByClient:
                        log.Info("Connection to administrator at port "+Port+" closed..");
                        break;
                }
            else
            {
                switch (e.Reason)
                {
                    case HostDisconnectedReason.ByError:
                        Broadcast(">>> " + Address + " lost connection <<<", true, this);
                        log.Error("Error receiving from " + Nick + "@" + Address + ":" + Port + " due to error " + e.Error);
                        break;
                    case HostDisconnectedReason.ByException:
                        Broadcast(">>> " + Address + " lost connection <<<", true, this);
                        log.Error("Error receiving from "+Nick+"@"+Address+":"+Port+" due to exception",e.Exception);
                        break;
                    case HostDisconnectedReason.ByServer:
                    case HostDisconnectedReason.ByClient:
                        Broadcast(">>> " + Address + " disconnected <<<", true, this);
                        log.Info("Connection to "+Nick+"@"+Address+":"+Port+" closed..");
                        break;
                }
            }
            if (User != null) User.Host = null;
            if (HostDisconnected != null) HostDisconnected(this, e);
        }

        /// <summary>
        /// Starts this instance.
        /// </summary>
        public void Start()
        {
            lock (this)
            {
                if (active) return;
                localEcho = false;
                // Set the socket to blocking and keep-alive of 10 seconds, followed by 1 second-pings
                const int size = sizeof(UInt32);
                var inArray = new byte[size * 3];
                Array.Copy(BitConverter.GetBytes((UInt32)1), 0, inArray, 0, size);
                Array.Copy(BitConverter.GetBytes((UInt32)10000), 0, inArray, size, size);
                Array.Copy(BitConverter.GetBytes((UInt32)1000), 0, inArray, size * 2, size);
                socket.IOControl(IOControlCode.KeepAliveValues, inArray, null);
                socket.Blocking = true;
                active = true;
                commandInterpreter = new Thread(commandInterpreterThread)
                {
                    IsBackground = true,
                    Priority = ThreadPriority.Normal
                };
                commandInterpreter.Start();
                IP = Address;
                HairPinningIP = IP;
            }
        }

        /// <summary>
        /// Stops this instance.
        /// </summary>
        public void Stop()
        {
            if (Thread.CurrentThread == commandInterpreter) return;
            lock (this)
            {
                if (!active) return;
                foreach (var s in server.Configuration.Messages.Shutdown.Line) Send(s+"\n\r");
                active = false;
                socket.Shutdown(SocketShutdown.Both);
                socket.Close();
            }
            commandInterpreter.Abort();
            commandInterpreter.Join();
        }
        
        public IPAddress Address
        {
            get
            {
                try
                {
                    lastKnownAddress = (socket.RemoteEndPoint as IPEndPoint).Address;
                }
                catch (ObjectDisposedException){}
                catch (SocketException){}
                catch (NullReferenceException) { }
                return lastKnownAddress;
            }
        }
        
        public int Port
        {
            get
            {
                try
                {
                    return (socket.RemoteEndPoint as IPEndPoint).Port;
                }
                catch (ObjectDisposedException){}
                catch (SocketException){}
                catch (NullReferenceException) {}
                return 0;
            }
        }

        private void commandInterpreterThread()
        {
            try
            {
                foreach (var s in server.Configuration.Messages.Welcome.Line) Send(s+"\n\r");
                if (IPAddress.IsLoopback(Address))
                {
                    IsAdmin = true;
                    nick = "Administrator";
                    Send("\n\rAdministrator shell.\n\r");
                    log.Info("Connection to administrator at port "+Port+" established..");
                }
                else
                {
                    Broadcast(">>> " + Address + " connected <<<", true, this);
                    log.Info("Connection to "+Address+":"+Port+" established..");
                }
                
			    var buffer=new byte[1024];
                var command=new StringBuilder();
                var clientTerminated = false;
                while (active)
                {
                    var bytes = socket.Receive(buffer);
                    if (bytes < 1)
                    {
                        active = false;
                        OnHostDisconnected(new HostDisconnectedEventArgs(HostDisconnectedReason.ByClient, SocketError.Disconnecting, null));
                        clientTerminated = true;
                        break;
                    }
                    if (localEcho) Send(buffer, bytes);
                    if (log.IsDebugEnabled)
                    {
                        var debug = new StringBuilder();
                        for (var i = 0; i < bytes; i++)
                        {
                            if (buffer[i] < 0x20 || buffer[i] > 0x7F) debug.Append("\\X" + buffer[i].ToString("X2"));
                            else debug.Append((char) buffer[i]);
                        }
                        log.Debug("<<<[" + Address + "]" + debug);
                    }
                    var ignore = false;
                    for(var i=0;i<bytes;i++)
                    {
                        var c = (char) buffer[i];
                        if (c == '\n' || c == '\r' || c=='\x00')
                        {
                            if (!ignore) Interpret(command.ToString());
                            command.Length = 0;
                            ignore = true;
                        }
                        else
                        {
                            command.Append(c);
                            ignore = false;
                        }
                    }
                }
                if (!clientTerminated) OnHostDisconnected(new HostDisconnectedEventArgs(HostDisconnectedReason.ByServer, SocketError.Disconnecting, null));
            }
            catch (ThreadAbortException)
            {
                OnHostDisconnected(new HostDisconnectedEventArgs(HostDisconnectedReason.ByServer, SocketError.Disconnecting, null));
            }
            catch (SocketException ex)
            {
                if (ex.SocketErrorCode == SocketError.Interrupted || ex.SocketErrorCode == SocketError.ConnectionAborted)
                {
                    OnHostDisconnected(new HostDisconnectedEventArgs(HostDisconnectedReason.ByClient, SocketError.Disconnecting, null));
                    
                }
                else
                {
                    OnHostDisconnected(new HostDisconnectedEventArgs(HostDisconnectedReason.ByError, ex.SocketErrorCode, ex));
                }
            }
            catch (Exception ex)
            {
                OnHostDisconnected(new HostDisconnectedEventArgs(HostDisconnectedReason.ByException, SocketError.Fault, ex));
            }
            finally
            {
                socket.Close();
            }
        }

        /// <summary>
        /// Sends the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        public void Send(string message)
        {
            lock(this)
            try
            {
                if (log.IsDebugEnabled)
                {
                    var debug = new StringBuilder();
                    for (var i = 0; i < message.Length; i++)
                    {
                        if (message[i] < 0x20 || message[i] > 0x7F)
                            debug.Append("\\X" + ((UInt16) message[i]).ToString("X4"));
                        else debug.Append(message[i]);
                    }
                    log.Debug(">>>[" + Address + "]" + debug);
                }
                socket.Send(encoder.GetBytes(message));
            }
            catch(Exception ex)
            {
                log.Error("Error sending to "+Nick+"@"+Address+":"+Port, ex);
            }
        }

        private void Send(byte[] buffer, int bytes)
        {
            try
            {
                if (log.IsDebugEnabled)
                {
                    var debug = new StringBuilder();
                    for (var i = 0; i < bytes; i++)
                    {
                        if (buffer[i] < 0x20 || buffer[i] > 0x7F) debug.Append("\\X" + buffer[i].ToString("X2"));
                        else debug.Append((char) buffer[i]);
                    }
                    log.Debug(">>>[" + Address + "]" + debug);
                }
                socket.Send(buffer, bytes, SocketFlags.None);
            }
            catch (Exception ex)
            {
                log.Error("Error sending to " + Nick + "@" + Address + ":" + Port, ex);
            }
        }

        private void Interpret(string s)
        {
            var cl=new Parser(new StringReader(s), commandGrammar) {TrimReductions = false};
            HighResolutionDateTime now;
            var line = new StringBuilder();
            while (true)
            {
                switch (cl.Parse())
                {
                    case ParseMessage.Accept:
                        var node = cl.TokenSyntaxNode as CommandLineNode;
                        switch(node.RuleIndex)
                        {
                            case CommandLineRules.Command_h:
                            case CommandLineRules.Command_help:
                                PrintOutHelp();
                                break;
                            case CommandLineRules.Command_exit:
                            case CommandLineRules.Command_quit:
                                foreach (var l in server.Configuration.Messages.Farwell.Line) Send(l+"\n\r");
                                active = false;
                                break;
                            case CommandLineRules.Command_as:
                            case CommandLineRules.Command_assign:
                                var assignUser = GetPayload(node[1].Trimmed);
                                var assignPass = GetPayload(node[2].Trimmed);
                                var newPort = GetPayload(node[3].Trimmed);
                                if (assignUser == "admin" && assignPass == server.Configuration.Password.Admin)
                                {
                                    Broadcast(">>> \"" + Nick + "\" is now an administrator <<<", true, this);
                                    log.Info(Address + ":" + Port + " assigned as administrator");
                                    IsAdmin = true;
                                    Send("ASSIGN Administrator\n\r");
                                    break;
                                }
                                if (assignUser == "container" && (
                                    (server.Configuration.Password.Container != null && assignPass == server.Configuration.Password.Container) ||
                                    (server.Configuration.Password.Container == null && assignPass == server.Configuration.Password.Admin)))
                                {
                                    Broadcast(">>> \"" + Nick + "\" is now a container <<<", true, this);
                                    log.Info(Address + ":" + Port + " assigned as container");
                                    IsContainer = true;
                                    Send("ASSIGN Container\n\r");
                                    break;
                                }
                                var assignedUser = server.Map.GetUser(assignUser);
                                if (assignedUser != null)
                                {
                                    if (assignedUser.IsValidPassword(assignPass) || IsAdmin)
                                    {
                                        try
                                        {
                                            if (newPort!=null) assignedUser.SendToPort = Convert.ToInt16(newPort);
                                            Broadcast(">>> \"" + Nick + "\" is now known as \"" + assignedUser.Nick + "\" <<<",true, this);
                                            log.Info(Address + ":" + Port + " assigned to " + assignedUser.Name);
                                            if (User != null) User.Host = null;
                                            IsAdmin = false;
                                            User = assignedUser;
                                            var oldHost = User.Host;
                                            if (oldHost != null)
                                            {
                                                oldHost.User = null;
                                                User.Host = null; //This will also stop the User threads.
                                            }
                                            User.Host = this;
                                            User.Start(); //We will have to start again in order to connect STUN ports properly.
                                            nick = User.Nick;
                                            Send("ASSIGN " + assignUser + "\n\r");
                                        }
                                        catch
                                        {
                                            Send("ASSIGN ERR PORT\n\r");
                                        }
                                        break;
                                    }
                                    Send("ASSIGN ERR PASS\n\r");
                                    break;
                                }
                                Send("ASSIGN ERR USER\n\r");
                                break;
                            case CommandLineRules.Command_echo:
                                switch (node[1].RuleIndex)
                                {
                                    case CommandLineRules.Echo_on:
                                        localEcho = true;
                                        break;
                                    case CommandLineRules.Echo_off:
                                        localEcho = false;
                                        break;
                                }
                                Send("Echo is "+(localEcho?"on":"off")+".\n\r");
                                break;
                            case CommandLineRules.Command_ip:
                                var address = GetPayload(node[1].Trimmed);
                                if (address=="server")
                                {
                                    address = server.Configuration.Network.IP;
                                    if (address == null)
                                    {
                                        IP = (socket.LocalEndPoint as IPEndPoint).Address;
                                        Send("Actual IP: " + IP + "\n\r");
                                        HairPinningIP = Address;
                                    }
                                    else
                                    {
                                        IPAddress[] IPs = null;
                                        try
                                        {
                                            IPs = Dns.GetHostAddresses(address);
                                        }
                                        catch {}
                                        if (IPs!=null && IPs.Length>0)
                                        {
                                            Send("Actual IP: " + (IP = IPs[0]) + "\n\r");
                                            HairPinningIP = Address;
                                        }
                                        else Send("Actual IP ERR\n\r");
                                    }
                                }
                                else
                                {
                                    IPAddress ipNew;
                                    if (address != null && IPAddress.TryParse(address, out ipNew))
                                    {
                                        Send("Actual IP: " + (IP = ipNew) + "\n\r");
                                        HairPinningIP = IP;
                                    }
                                    else Send("Actual IP ERR\n\r");
                                }
                                break;
                            case CommandLineRules.Command_ip2:
                                Send("Target IP: " + IP + "\n\rRemote IP: " + Address + "\n\r");
                                break;
                            case CommandLineRules.Command_talk_EscapedConst:
                            case CommandLineRules.Command_t_EscapedConst:
				                Broadcast('[' + Nick + ']' + GetPayload(node[1].Trimmed), true);
                                break;
                            case CommandLineRules.Command_me_EscapedConst:
                                Broadcast('[' + Nick + ' ' + GetPayload(node[1].Trimmed) + ']', true);
                                break;
                            case CommandLineRules.Command_RADIO_EscapedConst:
                                Broadcast("RADIO:" + GetPayload(node[1].Trimmed), false);
                                break;
                            case CommandLineRules.Command_nick:
                            case CommandLineRules.Command_nk:
                                var nickNew = string.Empty;
                                if (node.Count>1) nickNew = GetPayload(node[1].Trimmed);
                                var nickOld = Nick;
                                Nick = nickNew;
                                Broadcast(">>> \"" + nickOld + "\" is now known as \"" + Nick + "\" <<<", true);
                                break;
                            case CommandLineRules.Command_prompt:
                            case CommandLineRules.Command_pr:
                                if (node.Count > 1) Prompt = GetPayload(node[1].Trimmed);
                                Send("Prompt set successfully.\n\r");
                                break;
                            case CommandLineRules.Command_dump:
                            case CommandLineRules.Command_d:
                                if (IsAdmin)
                                {
                                    switch(node[1].RuleIndex)
                                    {
                                        case CommandLineRules.Dump_connections:
                                        case CommandLineRules.Dump_cn:
                                            DumpConnections();
                                            break;
                                        case CommandLineRules.Dump_clients:
                                        case CommandLineRules.Dump_cl:
                                            DumpClients();
                                            break;
                                        case CommandLineRules.Dump_globals:
                                        case CommandLineRules.Dump_gl:
                                            DumpGlobals();
                                            break;
                                    }
                                }
                                else Send("^ Syntax error\n\r");
                                break;
                            case CommandLineRules.Command_join:
                            case CommandLineRules.Command_jn:
                                var joinUser = GetPayload(node[1].Trimmed);
                                var joinPort = GetPayload(node[2].Trimmed);
                                var joinPass = GetPayload(node[3].Trimmed);
                                try
                                {
                                    var joinPortValue = Convert.ToUInt16(joinPort);

                                    if (joinPortValue >= 0)
                                        switch (server.Map.Add(joinUser, joinPass))
                                        {
                                            case AddObjectResult.TooMuch:
                                                Send("JOIN  ERR RECV\n\r");
                                                break;
                                            case AddObjectResult.Collision:
                                                Send("JOIN  ERR USER " + joinUser + "\n\r");
                                                break;
                                            case AddObjectResult.OK:
                                                var newUser = server.Map.GetUser(joinUser);
                                                if (joinPortValue > 0) newUser.SendToPort = joinPortValue;
                                                else newUser.NAT = true;
                                                //Next comments are due to old client compatibility
                                                /*if (!isAdmin)
                                                {*/
                                                    if (User != null) User.Host = null;
                                                    User = newUser;
                                                    User.Host = this;
                                                    Broadcast(">>> \"" + joinUser + "\" joined the server <<<", true, this);
                                                    log.Info(joinUser + " joined server");
                                                    Send("JOIN  " + User.ReceiverPort + "\n\r");
                                                //Next comments are due to old client compatibility    
                                                /*}
                                                else
                                                {
                                                    user = null;
                                                    Broadcast(">>> \"" + Nick + "\" created \"" + joinUser + "\" <<<\r\n", this);
                                                    log.Info(Address + ":" + Port + " created " + joinUser);
                                                    Send("MADE  " + newUser.ReceiverPort + "\n\r");
                                                }*/
                                                break;
                                        }
                                }
                                catch
                                {
                                    Send("JOIN  ERR PORT\n\r");
                                }
                                break;
                            case CommandLineRules.Command_leave:
                            case CommandLineRules.Command_lv:
                                var leaveUser = GetPayload(node[1].Trimmed);
                                var leavePass = IsAdmin?server.Configuration.Password.Admin:GetPayload(node[2].Trimmed);
                                switch(server.Map.Remove(leaveUser, leavePass, this))
                                {
                                    case RemoveObjectResult.NotFound:
                                        Send("LEAVE ERR USER\n\r");
                                        break;
                                    case RemoveObjectResult.WrongPassword:
                                        Send("LEAVE ERR PASS\n\r");
                                        break;
                                    case RemoveObjectResult.Hosted:
                                        Send("LEAVE ERR BUSY\n\r");
                                        break;
                                    case RemoveObjectResult.OwnUser:
                                        Broadcast(">>> \"" + leaveUser + "\" left the server <<<", true, this);
                                        log.Info(leaveUser + " left server");
                                        Send("LEAVE OK\n\r");
                                        User = null;
                                        break;
                                    case RemoveObjectResult.OK:
                                        Broadcast(">>> \"" + Nick + "\" kicked \"" + leaveUser + "\" <<<", true, this);
                                        log.Info(Address + ":" + Port + " kicked " + leaveUser);
                                        Send("KICK  OK\n\r");
                                        break;
                                }
                                break;
                            case CommandLineRules.Command_list:
                            case CommandLineRules.Command_ls:
                                listCommand &= ~ListCommand.AllUsersDetailed;
                                node = node[1];
                                for (var i = 0; i < node.Count;i++)
                                    switch (node[i].RuleIndex)
                                    {
                                        case CommandLineRules.ListListConst_a:
                                        case CommandLineRules.ListListConst_all:
                                            listCommand = (listCommand & ~ListCommand.TargetUsers) | ListCommand.AllUsers;
                                            break;
                                        case CommandLineRules.ListListConst_c:
                                        case CommandLineRules.ListListConst_current:
                                            listCommand = (listCommand & ~ListCommand.TargetUsers) | ListCommand.CurrentUsers;
                                            break;
                                        case CommandLineRules.ListListConst_o:
                                        case CommandLineRules.ListListConst_own:
                                            listCommand = (listCommand & ~ListCommand.TargetUsers) | ListCommand.OwnUsers;
                                            break;
                                        case CommandLineRules.ListTargetConst_u:
                                        case CommandLineRules.ListTargetConst_users:
                                            listCommand &= ~ListCommand.AllObjects;
                                            break;
                                        case CommandLineRules.ListTargetConst_ob:
                                        case CommandLineRules.ListTargetConst_objects:
                                            listCommand |= ListCommand.AllObjects;
                                            break;
                                        case CommandLineRules.ListDetailConst_d:
                                        case CommandLineRules.ListDetailConst_detailed:
                                            listCommand |= ListCommand.AllUsersDetailed;
                                            break;
                                        default:
                                            listCommand |= ListCommand.TargetUsers;
                                            break;
                                    }
                                switch (listCommand)
                                {
                                    case ListCommand.AllUsers:
                                        ListAllUsers();
                                        break;
                                    case ListCommand.AllUsersDetailed:
                                        ListAllUsersDetailed();
                                        break;
                                    case ListCommand.OwnUsers:
                                        ListOwnUsers();
                                        break;
                                    case ListCommand.OwnUsersDetailed:
                                        ListOwnUsersDetailed();
                                        break;
                                    default:
                                        Evaluate(node, String.Empty);
                                        break;
                                }
                                break;
                            case CommandLineRules.Command_check:
                            case CommandLineRules.Command_chk:
                                switch (node[1].RuleIndex)
                                {
                                    case CommandLineRules.Check_s:
                                    case CommandLineRules.Check_system:
                                        //TODO: only first supported system will be reflected
                                        //      change this for future clients...
                                        Send("CHECK SYS "+server.Configuration.Support[0].Name+"\n\r");
                                        break;
                                    case CommandLineRules.Check_vs:
                                    case CommandLineRules.Check_vessels:
                                        foreach (object o in server.Configuration.Support[0].Items)
                                        {
                                            var vesselClass=o as OMPServerConfigurationSystemClass;
                                            if (vesselClass!=null)
                                            {
                                                Send("CHECK VSL\""+vesselClass.Name+"\""+
                                                     (vesselClass.Redirect ?? "*")+"\n\r");
                                            }
                                        }
                                        Send("CHECK VSL END\n\r");
                                        break;
                                    case CommandLineRules.Check_v:
                                    case CommandLineRules.Check_version:
                                        //If the host announced version is smaller than 0.8, send "OMPServer" to indicate server incompatibility
                                        Send("CHECK VER" + (Version < new Version {Tag = "0.8", TagDistance = 0} ? " OMPServer " : " ") + new Version() + "\n\r");
                                        break;
                                    case CommandLineRules.Check_t:
                                    case CommandLineRules.Check_time:
                                        Send("CHECK MJD " + server.Time.Now.ToString(CultureInfo.InvariantCulture.NumberFormat) + "\n\r");
                                        break;
                                    case CommandLineRules.Check_f:
                                    case CommandLineRules.Check_files:
                                        CheckFiles();
                                        break;
                                    case CommandLineRules.Check_g:
                                    case CommandLineRules.Check_gbodies:
                                        CheckGbodies();
                                        break;
                                    case CommandLineRules.Check_l:
                                    case CommandLineRules.Check_location:
                                        node = node[1];
                                        var payload = GetPayload(node[1].Trimmed);
                                        if (string.IsNullOrEmpty(payload)) CheckLocations();
                                        else
                                            try{CheckLocation(Convert.ToInt32(payload));}
                                            catch{Send("CHECK LOC ERR " + payload + "\n\r");}
                                        break;
                                    default:
                                        Evaluate(node, String.Empty);
                                        break;
                                }
                                break;
                            case CommandLineRules.Command_GINFO:
                            case CommandLineRules.Command_GINFO2:
                                try
                                {
                                    var id = GetPayload(node[1].Trimmed);
                                    if (id[0] == 'C') //GINFO for 'C'lient is version string
                                    {
                                        Version = Version.Parse(GetPayload(node[2].Trimmed));
                                        break;
                                    }
                                    var local = id[0] == 'L'; //GINFO for 'L'ocal is referenced by local id
                                    if (local) id = id.Substring(1);
                                    var ginfoId = Convert.ToUInt16(id);
                                    var strings=new List<string>();
                                    for (var i = 2; i < node.Count;i++) strings.Add(GetPayload(node[i].Trimmed));
                                    bool eval=false;
                                    Vessel vessel;
                                    if (local) vessel = User.GetVessel(ginfoId);
                                    else lock(server.Map.GlobalsById) vessel = server.Map.GlobalsById[ginfoId] as Vessel;
                                    lock(server.Map.VesselsByUser)
                                        if (server.Map.VesselsByUser[User].Contains(vessel))
                                            eval = vessel.EvaluateInfo(strings.ToArray());
                                    if (eval || local)
                                    {
                                        if (local)
                                        {
                                            //This is less elegant, because we have to re-code the protocol line.
                                            //A local ID must be converted to a global one on broadcast.
                                            var bc = new StringBuilder();
                                            bc.Append("GINFO ");
                                            bc.Append(vessel.GlobalId);
                                            foreach (var st in strings)
                                            {
                                                bc.Append('"');
                                                bc.Append(st);
                                            }
                                            s = bc.ToString();
                                        }
                                        Broadcast(s, false, this);
                                    }
                                }
                                catch
                                {
                                    Send("GINFO ERR ID\n\r");
                                }
                                break;
                            case CommandLineRules.Command_REQST:
                                UInt16 reqstId = 0;
                                try
                                {
                                    reqstId = Convert.ToUInt16(GetPayload(node[1].Trimmed));
                                    string[] strings=null;
                                    lock(server.Map.GlobalsById)
                                    {
                                        var obj = server.Map.GlobalsById[reqstId];
                                        if (!string.IsNullOrEmpty(obj.Name)) strings = obj.GenerateInfo();
                                    }
                                    if (strings!=null) foreach (var info in strings) Send("GINFO " + reqstId + info + "\n\r");
                                    else Send("GINFO ERR " + reqstId + "\n\r");
                                }
                                catch
                                {
                                    Send("GINFO ERR "+reqstId+"\n\r");
                                }
                                break;
                            case CommandLineRules.Command_REQST_ping:
                                var pingValue = GetPayload(node[2].Trimmed);
                                try
                                {
                                    ping = Convert.ToUInt16(pingValue);
                                    if (ping > 0) Send("PING  P" + ping + "\n\r");
                                    else Send("PING  OFF\n\r");
                                }
                                catch
                                {
                                    Send("PING  ERR " + pingValue + "\n\r");
                                }
                                break;
                            case CommandLineRules.Command_REQST_ping_from:
                                Evaluate(node, String.Empty);
                                break;
                            case CommandLineRules.Command_trk:
                            case CommandLineRules.Command_track:
                                node = node[1];
                                UInt16 trackId;
                                switch (node.RuleIndex)
                                {
                                    case CommandLineRules.Track:
                                        var payload = GetPayload(node[0].Trimmed);
                                        try
                                        {
                                            trackId = Convert.ToUInt16(payload);
                                            lock (server.Map.GlobalsById)
                                            {
                                                if (server.Map.GlobalsById.ContainsKey(trackId))
                                                {
                                                    if (server.Map.GlobalsById[trackId] is Vessel)
                                                    {
                                                        lock (tracks) if (!tracks.Contains(trackId)) tracks.Add(trackId);
                                                        payload="TRACK " + trackId + "\n\r";
                                                    }
                                                    else payload= "TRACK ERR OBJ\n\r";
                                                }
                                                else payload="TRACK ERR " + trackId + "\n\r";
                                            }
                                        }
                                        catch
                                        {
                                            payload= "TRACK ERR " + payload + "\n\r";
                                        }
                                        Send(payload);
                                        break;
                                    case CommandLineRules.Track_off2:
                                        try
                                        {
                                            trackId = Convert.ToUInt16(GetPayload(node[1].Trimmed));
                                            lock (tracks)
                                            {
                                                if (tracks.Contains(trackId))
                                                {
                                                    tracks.Remove(trackId);
                                                    Send("TRACK OFF " + trackId + "\n\r");
                                                }
                                                else Send("TRACK OFF ERR " + trackId + "\n\r");
                                            }
                                        }
                                        catch
                                        {
                                            Send("TRACK ERR " + GetPayload(node[1].Trimmed) + "\n\r");
                                        }
                                        break;
                                    case CommandLineRules.Track_off:
                                        lock (tracks)
                                        {
                                            if (tracks.Count > 0) foreach (var id in tracks) Send("TRACK OFF " + id + "\n\r");
                                            else Send("TRACK NON\n\r");
                                            tracks.Clear();
                                        }
                                        break;
                                    case CommandLineRules.Track2:
                                        lock (tracks)
                                        {
                                            if (tracks.Count > 0) foreach (var id in tracks) Send("TRACK " + id + "\n\r");
                                            else Send("TRACK NON\n\r");
                                        }
                                        break;
                                }
                                break;
                            case CommandLineRules.Command_mjd:
                                if (IsAdmin)
                                {
                                    node = node[1];
                                    switch (node.RuleIndex)
                                    {
                                        case CommandLineRules.MJD_status:
                                        case CommandLineRules.MJD_st:
                                            line.Append("Timestamp: ");
                                            line.Append(NTPTime.Now.ToDouble);
                                            line.Append(" (lokal) ");
                                            line.Append(server.NTP.Now.ToDouble);
                                            line.Append(" (global)\n\r");
                                            line.Append("MJD ");
                                            var mjd = server.Time.Now;
                                            now = HighResolutionDateTime.FromModifiedJulianDay(mjd);
                                            line.Append(mjd);
                                            line.Append(now.DateTime.ToString(" = dddd MMMM d HH:mm:ss yyyy\n\r"));
                                            Send(line.ToString());
                                            break;
                                        case CommandLineRules.MJD_reset:
                                        case CommandLineRules.MJD_r:
                                            DateTime dateTime;
                                            if (
                                                DateTime.TryParse(
                                                    GetPayload(node[1].Trimmed) + ' ' + GetPayload(node[2].Trimmed),
                                                    out dateTime))
                                            {
                                                now = new HighResolutionDateTime(dateTime);
                                                var pac = new StateInfoPacket {MJD = now.ToModifiedJulianDay()};
                                                switch (GetPayload(node[3].Trimmed))
                                                {
                                                    case "Y":
                                                    case "y":
                                                    case "J":
                                                    case "j":
                                                    case "1":
                                                        pac.Sent = server.NTP.Now.ToDouble;
                                                        server.Time.Update(null, pac);
                                                        log.Info(Address + ":" + Port + " reset server MJD to " +
                                                                 pac.MJD);
                                                        line.Append("MJD   TO ");
                                                        break;
                                                    default:
                                                        line.Append("MJD   IS ");
                                                        break;
                                                }
                                                line.Append(now.DateTime.ToString("dddd MMMM d HH:mm:ss yyyy = "));
                                                line.Append(pac.MJD);
                                                line.Append("\n\r");
                                                Send(line.ToString());
                                            }
                                            else Send("MJD   ERR\n\r");
                                            break;
                                    }
                                } else Send("^ Syntax error\n\r");
                                break;
                            case CommandLineRules.Command_sync:
                                if (IsAdmin)
                                {
                                    node = node[1];
                                    switch (node.RuleIndex)
                                    {
                                        case CommandLineRules.Sync_status:
                                            ListSyncStatus();
                                            break;
                                    }
                                } else Send("^ Syntax error\n\r");
                                break;
                            case CommandLineRules.Command_log:
                                if (IsAdmin) Send("Command 'log' is obsolete.\n\r");
                                else Send("^ Syntax error\n\r");
                                break;
                            case CommandLineRules.Command_observe:
                            case CommandLineRules.Command_obs:
                                node = node[1];
                                switch (node.RuleIndex)
                                {
                                    case CommandLineRules.Track:
                                        var payload = GetPayload(node[0].Trimmed);
                                        try
                                        {
                                            var observeId = Convert.ToUInt16(payload);
                                            lock (server.Map.GlobalsById)
                                            {
                                                if (server.Map.GlobalsById.ContainsKey(observeId))
                                                {
                                                    var observe = server.Map.GlobalsById[observeId] as Vessel;
                                                    if (observe!=null)
                                                    {
                                                        if (User != null)
                                                        {
                                                            lock (server.Map.VesselsByUser)
                                                                if (server.Map.VesselsByUser.ContainsKey(User) && server.Map.VesselsByUser[User].Contains(observe))
                                                                    payload = "OBSRV ERR OWN\n\r";
                                                                else
                                                                {
                                                                    server.Map.Add(User, observeId);
                                                                    payload = "OBSRV " + observeId + "\n\r";
                                                                }
                                                        }
                                                        else payload = "OBSRV ERR USER\n\r";
                                                    }
                                                    else payload = "OBSRV ERR OBJ\n\r";
                                                }
                                                else payload = "OBSRV ERR " + observeId + "\n\r";
                                            }
                                        }
                                        catch
                                        {
                                            payload = "OBSRV ERR " + payload + "\n\r";
                                        }
                                        Send(payload);
                                        break;
                                    case CommandLineRules.Track_off2:
                                        try
                                        {
                                            var observeId = Convert.ToUInt16(GetPayload(node[1].Trimmed));
                                            if (User != null)
                                            {
                                                if (server.Map.Remove(User, observeId)==RemoveObjectResult.OK) payload = "OBSRV OFF " + observeId + "\n\r";
                                                else payload="OBSRV OFF ERR " + observeId + "\n\r";
                                            }
                                            else payload = "OBSRV OFF ERR USER\n\r";
                                        }
                                        catch
                                        {
                                            payload="OBSRV ERR " + GetPayload(node[1].Trimmed) + "\n\r";
                                        }
                                        Send(payload);
                                        break;
                                    case CommandLineRules.Track_off:
                                        if (User != null)
                                        {
                                            List<UInt16> observeIds = null;
                                            lock (server.Map.ObserversById)
                                            {
                                                if (server.Map.ObservesByUser.ContainsKey(User))
                                                    observeIds = new List<UInt16>(server.Map.ObservesByUser[User]);
                                            }
                                            if (observeIds!=null)
                                            {
                                                foreach (var id in observeIds)
                                                {
                                                    if (server.Map.Remove(User, id) == RemoveObjectResult.OK)
                                                    {
                                                        Send("OBSRV OFF " + id + "\n\r");
                                                    }
                                                    else Send("OBSRV OFF ERR " + id + "\n\r");
                                                }
                                            }
                                            else Send("OBSRV NON\n\r");
                                        }
                                        else Send("OBSRV OFF ERR USER\n\r");
                                        break;
                                    case CommandLineRules.Track2:
                                        if (User != null)
                                        {
                                            List<UInt16> observeIds = null;
                                            lock (server.Map.ObserversById)
                                            {
                                                if (server.Map.ObservesByUser.ContainsKey(User))
                                                    observeIds = new List<UInt16>(server.Map.ObservesByUser[User]);
                                            }
                                            if (observeIds != null)
                                            {
                                                foreach (var id in observeIds) Send("OBSRV " + id + "\n\r");
                                            }
                                            else Send("OBSRV NON\n\r");
                                        }
                                        else Send("OBSRV ERR USER\n\r");
                                        break;
                                }
                                break;
                            case CommandLineRules.Command_offer2:
                                if (User != null)
                                {
                                    List<string> offerIds = null;
                                    lock (server.Map.OfferedToById)
                                    {
                                        if (server.Map.OffersByUser.ContainsKey(User))
                                        {
                                            var offers = server.Map.OffersByUser[User];
                                            offerIds = new List<string>(offers.Count);
                                            foreach (var offer in offers)
                                            {
                                                var users = server.Map.OfferedToById[offer];
                                                var names = new StringBuilder();
                                                names.Append(offer);
                                                names.Append(" TO \"");
                                                foreach (var user in users)
                                                {
                                                    names.Append(user.Name);
                                                    names.Append('"');
                                                }
                                                offerIds.Add(names.ToString());
                                            }
                                        }
                                    }
                                    if (offerIds != null)
                                    {
                                        foreach (var id in offerIds) Send("OFFER " + id + "\n\r");
                                    }
                                    else Send("OFFER NON\n\r");
                                }
                                else Send("OFFER ERR USER\n\r");
                                break;
                            case CommandLineRules.Command_offer_off2:
                                if (User != null)
                                {
                                    List<UInt16> offerIds = null;
                                    lock (server.Map.OfferedToById)
                                    {
                                        if (server.Map.OffersByUser.ContainsKey(User))
                                            offerIds = new List<UInt16>(server.Map.OffersByUser[User]);
                                    }
                                    if (offerIds != null)
                                    {
                                        foreach (var id in offerIds)
                                        {
                                            if (server.Map.Remove(id, User) == RemoveObjectResult.OK)
                                            {
                                                Send("OFFER OFF " + id + "\n\r");
                                            }
                                            else Send("OFFER OFF ERR " + id + "\n\r");
                                        }
                                    }
                                    else Send("OFFER NON\n\r");
                                }
                                else Send("OFFER OFF ERR USER\n\r");
                                break;
                            case CommandLineRules.Command_offer_off:
                                {
                                    string payload;
                                    try
                                    {
                                        var offerId = Convert.ToUInt16(GetPayload(node[2].Trimmed));
                                        if (User != null)
                                        {
                                            if (server.Map.Remove(offerId, User) == RemoveObjectResult.OK)
                                                payload = "OFFER OFF " + offerId + "\n\r";
                                            else payload = "OFFER OFF ERR " + offerId + "\n\r";
                                        }
                                        else payload = "OFFER OFF ERR USER\n\r";
                                    }
                                    catch
                                    {
                                        payload = "OFFER ERR " + GetPayload(node[2].Trimmed) + "\n\r";
                                    }
                                    Send(payload);
                                }
                                break;
                            case CommandLineRules.Command_offer:
                                {
                                    var payload = GetPayload(node[1].Trimmed);
                                    try
                                    {
                                        var offerId = Convert.ToUInt16(payload);
                                        lock (server.Map.GlobalsById)
                                        {
                                            if (server.Map.GlobalsById.ContainsKey(offerId))
                                            {
                                                var offer = server.Map.GlobalsById[offerId] as Vessel;
                                                if (offer != null)
                                                {
                                                    if (offer.User != null && User != null && offer.User==User)
                                                    {
                                                        payload = GetPayload(node[2].Trimmed);
                                                        lock (server.Map.UsersByName)
                                                            if (!server.Map.UsersByName.ContainsKey(payload))
                                                                payload = "OFFER ERR USER\n\r";
                                                            else
                                                            {
                                                                var receiver = server.Map.UsersByName[payload];
                                                                if (receiver == User)
                                                                    payload = "OFFER ERR OWN\n\r";
                                                                else
                                                                {
                                                                    server.Map.Add(offerId, User, receiver);
                                                                    payload = "OFFER OBJ " + offerId + " TO \"" + payload + "\"\n\r";
                                                                    var host = receiver.Host;
                                                                    if (host!=null) host.Send("GINFO "+offerId+"\"O\n\r");
                                                                }
                                                            }
                                                    }
                                                    else payload = "OFFER ERR NON\n\r";
                                                }
                                                else payload = "OFFER ERR OBJ\n\r";
                                            }
                                            else payload = "OFFER ERR " + offerId + "\n\r";
                                        }
                                    }
                                    catch
                                    {
                                        payload = "OFFER ERR " + GetPayload(node[1].Trimmed) + " TO \"" + GetPayload(node[2].Trimmed) + "\"\n\r";
                                    }
                                    Send(payload);
                                }
                                break;
                            case CommandLineRules.Command_accept2:
                                if (User != null)
                                {
                                    List<UInt16> acceptIds = null;
                                    lock (server.Map.OfferedToById)
                                    {
                                        if (server.Map.OffersForUser.ContainsKey(User))
                                            acceptIds = new List<UInt16>(server.Map.OffersForUser[User]);
                                    }
                                    if (acceptIds != null)
                                    {
                                        foreach (var id in acceptIds) Send("ACCPT " + id + "\n\r");
                                    }
                                    else Send("ACCPT NON\n\r");
                                }
                                else Send("ACCPT ERR USER\n\r");
                                break;
                            case CommandLineRules.Command_accept:
                                {
                                    var payload = GetPayload(node[1].Trimmed);
                                    try
                                    {
                                        var acceptId = Convert.ToUInt16(payload);
                                        var localId = Convert.ToUInt16(GetPayload(node[2].Trimmed));
                                        if (User==null) payload = "ACCPT ERR USER\n\r";
                                        else lock (server.Map.OfferedToById)
                                        {
                                            if (server.Map.OfferedToById.ContainsKey(acceptId))
                                            {
                                                if (server.Map.OfferedToById[acceptId].Contains(User))
                                                {
                                                    lock (server.Map.GlobalsById)
                                                    {
                                                        var vessel = server.Map.GlobalsById[acceptId] as Vessel;
                                                        if (vessel == null) payload = "ACCPT ERR FATAL\n\r";
                                                        else
                                                        {
                                                            if (User.GetVessel(localId) != null)
                                                                payload = "ACCPT ERR LOCAL\n\r";
                                                            else
                                                            {
                                                                server.Map.Remove(acceptId, vessel.User);
                                                                var host = vessel.User.Host;
                                                                server.Map.HandOver(vessel, User, localId);
                                                                if (host != null) host.Send("GINFO " + acceptId + "\"A\n\r");
                                                                payload = "ACCPT OBJ " + acceptId + " AS \"" + vessel.ClassName + "\" WITH "+ localId + "\n\r";
                                                            }
                                                        }
                                                    }
                                                }
                                                else payload = "ACCPT ERR NON\n\r";
                                            }
                                            else payload = "ACCPT ERR " + acceptId + "\n\r";
                                        }
                                    }
                                    catch
                                    {
                                        payload = "ACCPT ERR " + GetPayload(node[1].Trimmed) + " AS " + GetPayload(node[2].Trimmed) + "\n\r";
                                    }
                                    Send(payload);
                                }
                                break;
                            case CommandLineRules.Command_kick:
                                if (IsAdmin)
                                {
                                    try
                                    {
                                        if (!Kick(IPAddress.Parse(GetPayload(node[1].Trimmed)))) Send("KICK  ERR IP\n\r");
                                    }
                                    catch
                                    {
                                        Send("KICK  ERR " + GetPayload(node[1].Trimmed) + "\n\r");
                                    }
                                    break;
                                }
                                Send("^ Syntax error\n\r");
                                break;
                            case CommandLineRules.Command_kickban:
                                if (IsAdmin)
                                {
                                    var banned = server.Listener.Banned;
                                    node = node[1];
                                    switch (node.RuleIndex)
                                    {
                                        case CommandLineRules.Track:
                                            try
                                            {
                                                var a = IPAddress.Parse(GetPayload(node.Trimmed));
                                                if (banned.Contains(a)) Send("BAN   "+a+"\n\r");
                                                else
                                                {
                                                    Kick(a);
                                                    banned.Add(a);
                                                    Broadcast(">>> \"" + Nick + "\" banned " + a + " <<<", true, this);
                                                    log.Info(Address + ":" + Port + " banned " + a);
                                                    Send("BAN   " + a + "\n\r");
                                                }
                                            }
                                            catch
                                            {
                                                Send("BAN   ERR " + GetPayload(node.Trimmed) + "\n\r");
                                            }
                                            break;
                                        case CommandLineRules.Track_off2:
                                            try
                                            {
                                                var a = IPAddress.Parse(GetPayload(node[1].Trimmed));
                                                if (banned.Contains(a))
                                                {
                                                    banned.Remove(a);
                                                    Broadcast(">>> \"" + Nick + "\" unbanned " + a + " <<<", true, this);
                                                    log.Info(Address + ":" + Port + " unbanned " + a);
                                                    Send("BAN   OFF " + a + "\n\r");
                                                }
                                                else Send("BAN   OFF ERR IP\n\r");
                                            }
                                            catch
                                            {
                                                Send("BAN   OFF ERR " + GetPayload(node[1].Trimmed) + "\n\r");
                                            }
                                            break;
                                        case CommandLineRules.Track_off:
                                            if (banned.Count > 0)
                                            {
                                                foreach (var a in banned) Send("BAN   OFF " + a + "\n\r");
                                                banned.Clear();
                                                Broadcast(">>> \"" + Nick + "\" cleared ban-list <<<", true, this);
                                                log.Info(Address + ":" + Port + " cleared ban-list");
                                            }
                                            else Send("BAN   NON\n\r");
                                            break;
                                        case CommandLineRules.Track2:
                                            if (banned.Count>0) foreach (var a in banned) Send("BAN   " + a + "\n\r");
                                            else Send("BAN   NON\n\r");
                                            break;
                                    }
                                } else Send("^ Syntax error\n\r");
                                break;
                            default:
                                Evaluate(node, String.Empty);
                                break;
                        }
                        return;
                    case ParseMessage.InternalError:
                        Send(new string(' ', cl.LinePosition - 1) + "^ Internal error\n\r");
                        return;
                    case ParseMessage.LexicalError:
                        Send(new string(' ', cl.LinePosition - 1) + "^ Lexical error in token '" + cl.TokenText + "'\n\r");
                        return;
                    case ParseMessage.SyntaxError:
                        if (cl.LinePosition != 1 || cl.TokenText.Length != 0)
                        {
                            Send(new string(' ', cl.LinePosition - cl.TokenText.Length - 1) + "^ Syntax error\n\r");
                        }
                        return;
                    case ParseMessage.NotLoadedError:
                        throw new SystemException("Grammar not loaded!");
                    case ParseMessage.Empty:
                        return;
                    case ParseMessage.TokenRead:
                        cl.TokenSyntaxNode = new CommandLineNode(cl.TokenSymbol, cl.TokenText);
                        break;
                    case ParseMessage.Reduction:
                        var childs=new CommandLineNode[cl.ReductionCount];
                        for(var i=0;i<childs.Length;i++) childs[i] = cl.GetReductionSyntaxNode(i) as CommandLineNode;
                        cl.TokenSyntaxNode = new CommandLineNode(cl.ReductionRule, childs);
                        break;
                }
            }
        }

        public Version Version { get; set; }

        private void CheckLocation(int id)
        {
            var line = new StringBuilder();
            var locations = new List<string>();
            var longestLine = 0;
            lock (server.Configuration)
            {
                foreach (var o in server.Configuration.Support[0].Items)
                {
                    var location = o as string;
                    if (location == null) continue;
                    var locationStream = new StringReader(location);
                    var buffer = new StringBuilder();
                    int c;
                    line.Append("CHECK |");
                    var print = true;
                    var open = false;
                    var ready = false;
                    var closed = false;
                    while ((c = locationStream.Read()) > 0)
                    {
                        switch ((char)c)
                        {
                            case '\n':
                                if (print)
                                {
                                    if (open)
                                    {
                                        open = false;
                                        line.Append("${" + buffer);
                                    }
                                    if (!closed || line.Length > 7)
                                    {
                                        AppendLineToSlot(line, locations, ref longestLine);
                                        line.Append("CHECK |");
                                    }
                                }
                                closed = false;
                                break;
                            case '$':
                                if (ready && print) line.Append("$");
                                ready = true;
                                closed = false;
                                break;
                            case '{':
                                if (ready)
                                {
                                    ready = false;
                                    open = true;
                                    buffer.Length = 0;
                                }
                                else if (print) line.Append("{");
                                closed = false;
                                break;
                            case '}':
                                if (!open)
                                {
                                    if (ready && print) line.Append('$');
                                    if (print) line.Append('}');
                                    ready = false;
                                    closed = false;
                                    break;
                                }
                                open = false;
                                var command = buffer.ToString();
                                switch (command)
                                {
                                    case FITAG:
                                        print = true;
                                        break;
                                    case DESCTAG:
                                        locationStream.ReadLine();
                                        if (line.Length>7)
                                        {
                                            AppendLineToSlot(line, locations, ref longestLine);
                                            line.Append("CHECK |");
                                        }
                                        break;
                                    default:
                                        if (command.StartsWith(IFTAG))
                                        {
                                            try{print = Convert.ToInt32(command.Substring(IFTAG.Length)) == id;}
                                            catch{}
                                            break;
                                        }
                                        if (print) line.Append("${" + command + "}");
                                        break;
                                }
                                closed = true;
                                break;
                            default:
                                closed = false;
                                if (open) buffer.Append((char) c);
                                else if (print) line.Append((char) c);
                                break;
                        }
                    }
                    if (line.Length > 7) AppendLineToSlot(line, locations, ref longestLine);
                }
            }
            foreach (var s in header)
            {
                var expanded = s.Replace("${SYSTEM}", server.Configuration.Support[0].Name);
                expanded = expanded.Replace("${TIME}", server.Time.Now.ToString(CultureInfo.InvariantCulture.NumberFormat));
                Send(expanded);
            }
            foreach (var s in locations) Send(s);
            Send("CHECK EOF\n\r");
        }

        private void CheckLocations()
        {
            var locations = new SortedDictionary<int, string>();
            lock (server.Configuration)
            {
                var id = -1;
                foreach (var o in server.Configuration.Support[0].Items)
                {
                    var location = o as string;
                    if (location == null) continue;
                    var locationStream = new StringReader(location);
                    var buffer = new StringBuilder();
                    var open = false;
                    var ready = false;
                    int c;
                    while ((c = locationStream.Read()) > 0)
                    {
                        switch((char) c)
                        {
                            case '$':
                                ready = true;
                                break;
                            case '{':
                                if (ready)
                                {
                                    ready = false;
                                    open = true;
                                    buffer.Length = 0;
                                }
                                break;
                            case '}':
                                ready = false;
                                if (!open) break;
                                open = false;
                                var command = buffer.ToString();
                                switch(command)
                                {
                                    case FITAG:
                                        id = -1;
                                        break;
                                    case DESCTAG:
                                        if (id < 0) break;
                                        locations[id] = locationStream.ReadLine();
                                        break;
                                    default:
                                        if (command.StartsWith(IFTAG))
                                        {
                                            try{id = Convert.ToInt32(command.Substring(IFTAG.Length));}
                                            catch{}
                                        }
                                        break;
                                }
                                break;
                            default:
                                ready = false;
                                if (open) buffer.Append((char) c);
                                break;
                        }
                    }
                }
            }
            foreach (var s in locations) Send("CHECK LOC\""+s.Key+"\""+s.Value+"\n\r");
            Send("CHECK LOC END\n\r");
        }

        private bool Kick(IPAddress address)
        {
            var hosts=new List<Host>();
            lock (server.Map.Hosts)
            {
                foreach (var h in server.Map.Hosts) if (h.Address.Equals(address)) hosts.Add(h);
            }
            foreach (var h in hosts)
            {
                Broadcast(">>> \"" + Nick + "\" kicked " + address + ":" + h.Port + " <<<", true,
                          this);
                log.Info(Address + ":" + Port + " kicked " + address + ":" + h.Port);
                if (h == this) active = false;
                h.Stop();
            }
            if (hosts.Count > 0)
            {
                Send("KICK  " + address + "\n\r");
                return true;
            }
            Send("KICK  NON\n\r");
            return hosts.Count>0;
        }

        private void ListSyncStatus()
        {
            var line = new StringBuilder();
            var list = new List<string>();
            var longestLine = 0;
            var counter = 1;
            foreach (var info in server.NTP.Servers)
            {
                line.Append(counter++.ToString("d3"));
                if (info.Misses<=info.MissesAlarm*info.Delay)
				{
					if (info.Suspensions==0)
					{
						if (info.Misses<=info.MissesMaximum) line.Append("   OK since ");
						else line.Append(" PENDING at ");
					    line.Append(info.Misses.ToString("d4"));
						line.Append(" misses: ");
					}
					else
					{
					    line.Append(" BLOCKED at ");
					    line.Append(info.Misses.ToString("d4"));
					    line.Append(" misses for ");
					    line.Append(info.Suspensions.ToString("d3"));
					    line.Append(" hits: ");
					}
				}
				else
                {
                    line.Append(" BLOCKED at ");
				    line.Append(info.Misses.ToString("d4"));
				    line.Append(" misses PERMANENTLY: ");
                }
                line.Append(info.Address);
                AppendLineToSlot(line, list, ref longestLine);
            }
            Send("--SNTP Server status:".PadRight(longestLine, '-') + "\n\r");
            foreach (var s in list) Send(s);
            Send(("--Count:-"+list.Count.ToString("d3")).PadRight(longestLine, '-') + "\n\r");
        }

        private void ListAllUsersDetailed()
        {
            var line = new StringBuilder();
            var auditorList = new List<string>();
            var observerList = new List<string>();
            var userList = new List<string>();
            var longestLine = 0;
            lock (server.Map.Hosts)
            {
                var count = 0;
                foreach (var host in server.Map.Hosts)
                {
                    if (host.User != null) continue;
                    var nick = host.Nick;
                    var address = host.Address.ToString();
                    line.Append(' ');
                    line.Append(nick);
                    if (nick != address)
                    {
                        line.Append(" (");
                        line.Append(host.Address);
                        line.Append(')');
                    }
                    AppendLineToSlot(line, auditorList, ref longestLine);
                    count++;
                }
                line.Append(count);
                line.Append(" auditor(s) found.");
                AppendLineToSlot(line, auditorList, ref longestLine);
            }
            lock (server.Map.UsersByName)
            {
                var observerCount = 0;
                var userCount = 0;
                foreach (var pair in server.Map.UsersByName)
                {
                    lock (server.Map.VesselsByUser)
                    {
                        if (server.Map.VesselsByUser.ContainsKey(pair.Value))
                        {
                            if (userCount>0) AppendLineToSlot(line, userList, ref longestLine);
                            line.Append(' ');
                            line.Append(pair.Key);
                            line.Append(':');
                            AppendLineToSlot(line, userList, ref longestLine);
                            foreach (var vessel in server.Map.VesselsByUser[pair.Value])
                            {
                                line.Append(" [");
                                line.Append(vessel.GlobalId);
                                line.Append(']');
                                if (string.IsNullOrEmpty(vessel.ClassName)) line.Append(" no UDP!");
                                else
                                {
                                    line.Append(vessel.Name);
                                    line.Append('(');
                                    line.Append(vessel.ClassName);
                                    line.Append(')');
                                }
                                AppendLineToSlot(line, userList, ref longestLine);
                            }
                            userCount++;
                        }
                        else
                        {
                            line.Append(' ');
                            line.Append(pair.Key);
                            AppendLineToSlot(line, observerList, ref longestLine);
                            observerCount++;
                        }
                    }
                }
                if (userCount > 0) AppendLineToSlot(line, userList, ref longestLine);
                line.Append(userCount);
                line.Append(" client(s) found.");
                AppendLineToSlot(line, userList, ref longestLine);
                line.Append(observerCount);
                line.Append(" observer(s) found.");
                AppendLineToSlot(line, observerList, ref longestLine);
            }
            Send(new string('-', longestLine)+"\n\r");
            foreach (var s in auditorList) Send(s);
            Send("\n\r");
            foreach (var s in observerList) Send(s);
            Send("\n\r");
            foreach (var s in userList) Send(s);
        }

        private void ListAllUsers()
        {
            var line = new StringBuilder();
            var list = new List<string>();
            var longestLine = 0;
            lock (server.Map.Hosts)
            {
                foreach (var host in server.Map.Hosts)
                {
                    if (host.User != null) continue;
                    line.Append("LIST  ");
                    line.Append(host == this ? '*' : ' ');
                    line.Append(host.Nick);
                    line.Append('"');
                    line.Append(host.Nick);
                    line.Append('@');
                    line.Append(host.Address);
                    line.Append('"');
                    line.Append(host.IsAdmin ? 'A' : 'a');
                    AppendLineToSlot(line, list, ref longestLine);
                }
            }
            lock (server.Map.UsersByName)
            {
                foreach (var pair in server.Map.UsersByName)
                {
                    line.Append("LIST  ");
                    var host = pair.Value.Host;
                    line.Append(host == this ? '*' : ' ');
                    line.Append(pair.Value.Nick);
                    line.Append('"');
                    line.Append(pair.Key);
                    var count = 0;
                    lock (server.Map.VesselsByUser)
                        if (server.Map.VesselsByUser.ContainsKey(pair.Value))
                            count = server.Map.VesselsByUser[pair.Value].Count;
                    if (host != null)
                    {
                        line.Append('@');
                        line.Append(host.Address);
                        line.Append('"');
                        line.Append(host.IsContainer?"C":(host.IsAdmin ? (count>0?"U":"O") : (count>0?"u":"o")));
                    }
                    else
                    {
                        line.Append('"');
                        line.Append(count > 0 ? "i" : "z");
                    }
                    if (count > 0) line.Append(count);
                    AppendLineToSlot(line, list, ref longestLine);
                }
            }
            foreach (var s in list) Send(s);
            Send("LIST  END\n\r");
        }

        private void ListOwnUsersDetailed()
        {
            var line = new StringBuilder();
            var list = new List<string>();
            var longestLine = 0;
            lock (server.Map.UsersByName)
            {
                var count = 0;
                foreach (var pair in server.Map.UsersByName)
                {
                    if (pair.Value.Host != this) continue;
                    line.Append("Name:");
                    line.Append(pair.Key);
                    line.Append(" Xmit2:");
                    line.Append(pair.Value.SendToPort);
                    line.Append(" Recv@:");
                    line.Append(pair.Value.ReceiverPort);
                    AppendLineToSlot(line, list, ref longestLine);
                    count++;
                }
                line.Append(count);
                line.Append(" client(s) found.");
                AppendLineToSlot(line, list, ref longestLine);
            }
            foreach (var s in list) Send(s);
        }

        private void CheckFiles()
        {
            var line = new StringBuilder();
            var locations = new List<string>();
            var longestLine = 0;
            lock (server.Configuration)
            {
                foreach (var o in server.Configuration.Support[0].Items)
                {
                    var location = o as string;
                    if (location == null) continue;
                    var locationStream = new StringReader(location);
                    string s;
                    while ((s = locationStream.ReadLine()) != null)
                    {
                        line.Append("CHECK |");
                        line.Append(s);
                        AppendLineToSlot(line, locations, ref longestLine);
                    }
                }
            }
            foreach (var s in header)
            {
                var expanded=s.Replace("${SYSTEM}", server.Configuration.Support[0].Name);
                expanded = expanded.Replace("${TIME}", server.Time.Now.ToString(CultureInfo.InvariantCulture.NumberFormat));
                Send(expanded);
            }
            foreach (var s in locations) Send(s);
            Send("CHECK EOF\n\r");
        }

        private void CheckGbodies()
        {
            var line = new StringBuilder();
            var globals = new List<string>();
            var longestLine = 0;
            lock (server.Map.GlobalsById)
            {
                foreach (var o in server.Map.GlobalsById.Values)
                {
                    var body = o as CelestialBody;
                    if (body == null) continue;
                    line.Append(body.GlobalId);
                    switch (body.Type)
                    {
                        case CelestialBodyType.Star:
                            line.Append("\"*");
                            break;
                        case CelestialBodyType.Planet:
                            line.Append("\"O");
                            break;
                        case CelestialBodyType.Moon:
                            line.Append("\"o");
                            break;
                    }
                    line.Append(body.Name);
                    AppendLineToSlot(line, globals, ref longestLine);
                }
            }
            foreach (var s in globals) Send("CHECK GBY\""+s);
            Send("CHECK GBY END\n\r");
        }

        private void ListOwnUsers()
        {
            var line = new StringBuilder();
            var globals = new List<string>();
            var longestLine = 0;
            lock (server.Map.UsersByName)
            {
                foreach (var user in server.Map.UsersByName.Values) //foreach is only necessary to make old client work! See below
                {
                    if (/*THIS:*/user.Host==null/* IS TEMPORARY TO MAKE OLD CLIENT WORK */ ||
                                 user.Host==this)
                    {
                        line.Append("LIST  ");
                        line.Append(user.Name);
                        line.Append('"');
                        line.Append(user.SendToPort);
                        line.Append('"');
                        line.Append(user.ReceiverPort);
                        AppendLineToSlot(line, globals, ref longestLine);
                    }
                }
            }
            foreach (var s in globals) Send(s);
            Send("LIST  END\n\r");
        }

        private void DumpConnections()
        {
            var line=new StringBuilder();
            var containers = new List<string>();
            var admins=new List<string>();
            var auditors=new List<string>();
            var actives=new List<string>();
            var inActives = new List<string>();
            var longestLine=0;
            lock (server.Map.Hosts)
            {
                foreach (var host in server.Map.Hosts)
                {
                    List<string> slot;
                    if (host.IsContainer) slot = containers;
                    else if (host.IsAdmin) slot = admins;
                    else if (host.User == null) slot = auditors;
                    else slot=actives;
                    if (host.User != null)
                    {
                        line.Append(" UDP/TCP ");
                        line.Append(' ', 11);
                        GenerateUDPLine(line, host.User);
                        AppendLineToSlot(line, slot, ref longestLine);
                        line.Append("  +- ");
                    }
                    else
                    {
                        if (host == this) line.Append("*TCP ");
                        else line.Append(" TCP ");
                    }
                    line.Append((host.Address + " (" + host.IP + ")").PadLeft(32));
                    line.Append(" : ");
                    line.Append(host.Port.ToString().PadRight(5));
                    line.Append(" - ");
                    line.Append(host.Nick);
                    AppendLineToSlot(line, slot, ref longestLine);
                }
            }
            lock (server.Map.UsersByName)
            {
                foreach (var user in server.Map.UsersByName.Values)
                {
                    if (user.Host != null) continue;
                    line.Append(" UDP ");
                    line.Append(' ', 15);
                    GenerateUDPLine(line, user);
                    line.Append(" (");
                    line.Append(user.Nick);
                    line.Append(")");
                    AppendLineToSlot(line, inActives, ref longestLine);
                }
            }
            if (admins.Count > 0)
            {
                Send("--Administrators:".PadRight(longestLine, '-') + "\n\r");
                foreach (var s in admins) Send(s);
            }
            if (containers.Count > 0)
            {
                Send("--Containers:".PadRight(longestLine, '-') + "\n\r");
                foreach (var s in containers) Send(s);
            }
            if (auditors.Count > 0)
            {
                Send("--Auditors:".PadRight(longestLine, '-') + "\n\r");
                foreach (var s in auditors) Send(s);
            }
            if (actives.Count > 0)
            {
                Send("--Active users:".PadRight(longestLine, '-') + "\n\r");
                foreach (var s in actives) Send(s);
            }
            if (inActives.Count > 0)
            {
                Send("--Idle users:".PadRight(longestLine, '-') + "\n\r");
                foreach (var s in inActives) Send(s);
            }
            Send(new string('-', longestLine)+"\n\r");
        }

        private static void GenerateUDPLine(StringBuilder line, User user)
        {
            line.Append("RCVR : ");
            line.Append(user.ReceiverPort.ToString().PadRight(5));
            line.Append(" XMIT : ");
            line.Append(user.SendToPort != 0 ? user.SendToPort.ToString().PadRight(5) : "STUN ");
            line.Append(" - ");
            line.Append(user.Name);
        }

        private static void AppendLineToSlot(StringBuilder line, ICollection<string> slot, ref int longestLine)
        {
            line.Append("\n\r");
            slot.Add(line.ToString());
            if (line.Length>longestLine) longestLine = line.Length;
            line.Length = 0;
        }

        private void DumpGlobals()
        {
            var line = new StringBuilder();
            var globals = new List<string>();
            var longestLine = 12;
            lock (server.Map.GlobalsById)
            {
                foreach (var o in server.Map.GlobalsById.Values)
                {
                    var vessel=o as Vessel;
                    if (vessel!=null)
                    {
                        line.Append(vessel.GlobalId.ToString().PadLeft(6));
                        line.Append(" Vessel \"");
                        line.Append(vessel.Name);
                        line.Append("\" - \"");
                        line.Append(vessel.ClassName);
                        line.Append("\" class");
                        AppendLineToSlot(line, globals, ref longestLine);
                    }
                    else
                    {
                        var body=o as CelestialBody;
                        if (body!=null)
                        {
                            line.Append(body.GlobalId.ToString().PadLeft(6));
                            switch (body.Type)
                            {
                                case CelestialBodyType.Star:
                                    line.Append(" Star   \"");
                                    break;
                                case CelestialBodyType.Planet:
                                    line.Append(" Planet \"");
                                    break;
                                case CelestialBodyType.Moon:
                                    line.Append(" Moon   \"");
                                    break;
                            }
                            line.Append(body.Name);
                            line.Append("\" - Mass: ");
                            line.Append(body.Mass.ToString());
                            line.Append("kg");
                            AppendLineToSlot(line, globals, ref longestLine);
                        }
                    }
                }
            }
            Send("--Globals:".PadRight(longestLine, '-') + "\n\r");
            foreach (var s in globals) Send(s);
            Send(new string('-', longestLine) + "\n\r");
        }

        private void DumpClients()
        {
            var line = new StringBuilder();
            var clients = new List<string>();
            var longestLine = 12;
            lock (server.Map.UsersByName) lock(server.Map.VesselsByUser)
            {
                foreach (var user in server.Map.UsersByName.Values)
                {
                    
                    if (server.Map.VesselsByUser.ContainsKey(user)) line.Append(server.Map.VesselsByUser[user].Count.ToString().PadLeft(6));
                    else line.Append("    NO");
                    line.Append(" VSL - ");
                    line.Append(user.Name);
                    line.Append(" (");
                    line.Append(user.Nick);
                    line.Append(") ");
                    try
                    {
                        var offset = HighResolutionDateTime.FromModifiedJulianDay(user.LocalTime.Now).DateTime -
                                     HighResolutionDateTime.FromModifiedJulianDay(user.Time.Now).DateTime;
                        if (offset.Days != 0) line.Append(offset.Days + "d");
                        else if (offset.Hours != 0) line.Append(offset.Hours + "h");
                        else if (offset.Minutes != 0) line.Append(offset.Minutes + "m");
                        else line.Append(offset.TotalSeconds);
                    }
                    catch(ArgumentOutOfRangeException)
                    {
                        line.Append("ERROR");
                    }
                    var host = user.Host;
                    if (host != null)
                    {
                        line.Append(" V");
                        line.Append(host.Version);
                    }
                    AppendLineToSlot(line, clients, ref longestLine);
                }
            }
            Send("--Clients:".PadRight(longestLine, '-') + "\n\r");
            foreach (var s in clients) Send(s);
            Send(new string('-', longestLine) + "\n\r");
        }

        private void Broadcast(string message, bool formatPrompt, params Host[] excludedHosts)
        {
            lock(server.Map.Hosts)
            {
                chat.Info(
                    String.Format(formatPrompt ? server.Configuration.PromptFormat : "{0}", message, ((HighResolutionDateTime)server.NTP.Now).DateTime));
                foreach (var host in server.Map.Hosts)
                {
                    var excluded = false;
                    if (excludedHosts!=null && excludedHosts.Length>0)
                        foreach (var excludedHost in excludedHosts)
                        {
                            if (host != excludedHost) continue;
                            excluded = true;
                            break;
                        }
                    if (!excluded && !host.IsContainer) host.Send(String.Format((formatPrompt ? host.Prompt : "{0}") + "\n\r", message, ((HighResolutionDateTime)server.NTP.Now).DateTime));
                }
            }
        }
        
        private static string GetPayload(CommandLineNode node)
        {
            switch (node.SymbolIndex)
            {
                case CommandLineSymbols.S_StringConst:
                    var l = node.Content.Length-1;
                    return node.Content[l] == '"' ? node.Content.Substring(1,l-1) : node.Content.Substring(1);
                case CommandLineSymbols.S_EscapedConst:
                    return node.Content.Substring(1);
                default:
                    return node.Content;
            }
        }

        private void PrintOutHelp()
        {
            if (IsAdmin)
            {
                foreach (var s in helpPage) Send(s+"\n\r");
            }
            else
            {
                foreach (var s in helpPage)
                {
                    if (s.StartsWith("***")) break;
                    Send(s + "\n\r");
                }
            }
        }

        private void Evaluate(CommandLineNode node, string offset)
        {
            if (node.RuleIndex<0)
            {
                Send(offset + "'" + node.Content + "'\n\r");
                return;
            }
            Send(offset + node.Rule.Name + " := " + node.Rule.Definition + "\n\r");
            offset = offset.Replace('+', '|');
            offset = offset.Replace('^', ' ');
            for (var i = 0; i < node.Count-1; i++ )
            {
                Send(offset + " |\n\r");
                Evaluate(node[i], offset + " + ");
            }
            if (node.Count <= 0) return;
            Send(offset + " |\n\r");
            Evaluate(node[node.Count - 1], offset + " ^ ");
        }
    }
}
