//  
//  LapperServer.cs
//  
//  Author:
//       Robert BRACCAGNI alias Gai-Luron <lfsgailuron@free.fr>
// 
//  Copyright (c) 2010 Gai-Luron
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
// 
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.Collections;

namespace LapperInstances
{
    class Server
    {
        private TcpListener tcpListener;
        private Thread listenThread;
        private bool termAllThread = false;
        public setOfLapperInstances mySetOfLFSInstances;
        GLDebug.Debug myDebug;
		public int remotePort = 3000;
		public int timeOutScript = 2000;
        public string lapperServerPass = "";

        public Server( GLDebug.Debug pmyDebug  )
        {
            LFSServers.getServerConfig();
			remotePort = LFSServers.remotePort;
			timeOutScript = LFSServers.timeOutScript;

            this.tcpListener = new TcpListener(IPAddress.Any, remotePort);  //Listen to this IP and port of current lapper instance
            this.listenThread = new Thread(new ThreadStart(ListenForClients));
            this.listenThread.Start();
            this.lapperServerPass = UTILS.utils.RandomString(8,true);
            mySetOfLFSInstances = new setOfLapperInstances( pmyDebug );
            this.myDebug = pmyDebug;
        }
        public void stop()
        {
            mySetOfLFSInstances.stopAll();
            termAllThread = true;

            if (this.listenThread != null)
            {
                listenThread.Abort();
                listenThread.Join();
            }
        }
        private void ListenForClients()
        {
            try
            {
                Decoder myDecoder = new Decoder();
                this.tcpListener.Start();
                int i = 0;
                while (true)
                {
                    TcpClient client = null;
                    //blocks until a client has connected to the server
                    while (true) {
                        if (this.tcpListener.Pending())
                        {
                            client = this.tcpListener.AcceptTcpClient();
                            NetworkStream clientStream = client.GetStream();
                            //                        Console.WriteLine("Receive Query connection!");
                            byte[] buff = GetPackFromClient(clientStream, 5000); // Wait for 5 second for a valid packet
                            string PH = myDecoder.packetHead(buff);
                            if (PH == "CNT")
                            {
                                Decoder.CNT cnt = new Decoder.CNT(buff);
                                if (cnt.Pass == lapperServerPass)
                                {
                                    Console.WriteLine("Client OK");
                                    Console.WriteLine("Connecting Servers...");
                                    Console.WriteLine("----------------------------------------------------------------------------------");
                                    break;
                                }
                                else
                                    Console.WriteLine("LFSLapper adminPassword does not match your multiplayer admin password!!");

                            }
                            myDebug.WriteLine("err", "Cannot receive packet, close connection");
                            clientStream.Close();
                            client.Close();
                        }
                        // Wait before pool
                        System.Threading.Thread.Sleep(400);
                    }

                    //create a thread to handle communication
                    //with connected client
                    if (i < 10)
                    {
                        Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
                        clientThread.Start(client);
                    }
                }
            }
            catch
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("Cannot start Lapperinstance: another instance of LFSLapper is running with the same remoteport.");
                Console.ForegroundColor = ConsoleColor.White;
            }
        }
        private byte[] GetPackFromClient( NetworkStream clientStream, int timeOut )
        {
            try
            {
                clientStream.ReadTimeout = timeOut;
                int len = clientStream.ReadByte(); // Get the first byte to have the length
                if (len != -1)
                {
                    byte[] buff = new byte[len];
                    clientStream.Read(buff, 1, (int)(len - 1));
                    buff[0] = (byte)len;
                    return buff;
                }
                else
                {
                    throw new Exception("Error on GetPackFromClient, not receive byte");
                }
            }
            catch
            {
                clientStream.ReadTimeout = -1;
                return null;
            }
            finally
            {
                clientStream.ReadTimeout = -1;
            }

        }

        private void HandleClientComm(object client)
        {
            TcpClient tcpClient = (TcpClient)client;
            NetworkStream clientStream = tcpClient.GetStream();
            Encoder myEncoder = new Encoder();
            Decoder myDecoder = new Decoder();
            infoInstance ServerInfo;
            byte[] pack;


            pack = myEncoder.LP_MSG("Ok");
            sendToStream(clientStream, pack);

            byte[] packet;
            while (true)
            {
                try
                {
                    //blocks until a client sends a message
                    while (true)
                    {
                        if (clientStream.DataAvailable)
                        {
                            try
                            {
                                packet = GetPackFromClient(clientStream, 1000);
                            }
                            catch
                            {
                                myDebug.WriteLine("err", "Error on Reading TCP connection! Close connection!");
                                tcpClient.Close();
                                return;
                            }
                            break;
                        }
                        if (termAllThread)
                        {
                            tcpClient.Close();
                            return;
                        }
                        System.Threading.Thread.Sleep(100);
                    }
                }
                catch
                {
                    //a socket error has occured
                    break;
                }
                //message has successfully been received
                ASCIIEncoding encoder = new ASCIIEncoding();
                string PH = myDecoder.packetHead(packet);
                Decoder.CMD myCmd = new Decoder.CMD(packet);
                string cmd = myCmd.Cmd;

                //LFS Commands
                string commandchar = cmd.Substring(0, 1);
                if (commandchar.Contains("/"))
                {
                    if (cmd.Length > 1)
                        mySetOfLFSInstances.sendCmdToGroupOfInstance("", cmd);
                    else
                    {
                        sendToStream(clientStream, myEncoder.LP_MSG("Cannot send empty commands to LFS!"));
                    }
                    sendToStream(clientStream, myEncoder.LP_EOP());
                }

                string[] readed = cmd.Split(' ');
                switch (readed[0].ToLower())
                {
                    case "/":
                    break;
                    //Send message to lappergroup of instances
                    case "msg":
                        if(readed.Length > 1)
                        {
                            //trim the command first before sending the message
                            string text = cmd.Substring(4, cmd.Length-4);
                            if (text != "")
                            {
                                mySetOfLFSInstances.sendCmdToGroupOfInstance("", text);
                            }
                            else
                            {
                                sendToStream(clientStream, myEncoder.LP_MSG("Cannot send empty messages!"));
                            }
                        }
                        else
                        {
                            sendToStream(clientStream, myEncoder.LP_MSG("Invalid parameter to send messages!"));
                        }
                        sendToStream(clientStream, myEncoder.LP_EOP());
                        break;
                    //Get list of all instances that are enabled.
                    case "list":
                        Hashtable listServers = mySetOfLFSInstances.getListOfLapperInstanceFromFile();

                        foreach (DictionaryEntry de in listServers)
                        {
                            ServerInfo = (infoInstance) de.Value;

                            pack = myEncoder.LP_LST(
                                ServerInfo.name,
                                ServerInfo.ip,
                                ServerInfo.port,
                                ServerInfo.password,
                                ServerInfo.workingDir,
                                ServerInfo.iniFile,
								(int)mySetOfLFSInstances.getStatus(ServerInfo.name) //state
                            );
                            sendToStream(clientStream, pack);
                        }
                        sendToStream(clientStream, myEncoder.LP_EOP());
                        break;
                    //Set Instance in Standby mode
                    case "standby":
                        {
                            try
                            {
                                int ServerStatus = (int)mySetOfLFSInstances.getStatus(readed[1]);
                                ServerInfo = mySetOfLFSInstances.gostandbymode(readed[1]); //Set Lapper instance to Standby mode 
                                if (ServerStatus == 2)
                                {
                                    sendToStream(clientStream, myEncoder.LP_MSG("Lapper: [" + ServerInfo.ip + " / " + ServerInfo.port + "] in StandBy Mode!"));
                                }
                                if (ServerStatus == 1)
                                {
                                    sendToStream(clientStream, myEncoder.LP_MSG("Lapper: [" + ServerInfo.ip + " / " + ServerInfo.port + "] Already in standby mode!"));
                                }
                            }
                            catch
                            {
                                sendToStream(clientStream, myEncoder.LP_MSG("Error with setting this instance in StandBy Mode!"));
                            }
                            sendToStream(clientStream, myEncoder.LP_EOP());
                        }

                        break;
                    //Disconnect Instance from lfsserver
                    case "stop":
                        {
                            try
                            {
                                int ServerStatus = (int)mySetOfLFSInstances.getStatus(readed[1]);
                                if (ServerStatus != 0)
                                {
                                    ServerInfo = mySetOfLFSInstances.goAutoNoneMode(readed[1]); //Set instance in Standby mode first
                                    sendToStream(clientStream, myEncoder.LP_MSG("Lapper: [" + ServerInfo.ip + " / " + ServerInfo.port + "] Disconnecting, please wait....."));
                                    //System.Threading.Thread.Sleep(1000);
                                    ServerInfo = mySetOfLFSInstances.goAutoNoneMode(readed[1]); //Disconnect instance safely
                                    sendToStream(clientStream, myEncoder.LP_MSG("Lapper: [" + ServerInfo.ip + " / " + ServerInfo.port + "] Disconnected!!"));
                                }
                                else
                                {
                                    sendToStream(clientStream, myEncoder.LP_MSG("This instance is already disconnected!"));
                                }
                            }
                            catch
                            {
                                pack = myEncoder.LP_MSG("This Lapper instance doesnt exist, type 'list' with all the available instances!");
                                sendToStream(clientStream, pack);
                            }
                            sendToStream(clientStream, myEncoder.LP_EOP());
                        }
                        break;
                    //Start a Lapperinstance
                    case "start":
                        {
                            try
                            {
                                int ServerStatus = (int)mySetOfLFSInstances.getStatus(readed[1]);
                                ServerInfo = mySetOfLFSInstances.goWorkMode(readed[1]); //Start lapperinstance
                                if (ServerStatus != 2)
                                {
                                    sendToStream(clientStream, myEncoder.LP_MSG(" " + ServerStatus));
                                    sendToStream(clientStream, myEncoder.LP_MSG("Lapper: [" + ServerInfo.ip + " / " + ServerInfo.port + "] Started!"));
                                }
                                else
                                {
                                    sendToStream(clientStream, myEncoder.LP_MSG("Lapper: [" + ServerInfo.ip + " / " + ServerInfo.port + "] is already started!"));
                                }
                            }
                            catch
                            {
                                sendToStream(clientStream, pack);
                            }
                            sendToStream(clientStream, myEncoder.LP_EOP());
                        }
                        break;
                    case "help":
                        sendToStream(clientStream, myEncoder.LP_MSG("Commands:"));
                        sendToStream(clientStream, myEncoder.LP_MSG("       list - List with lapperservers enabled"));
                        sendToStream(clientStream, myEncoder.LP_MSG("       standby <id> - set the Lapper instance id in standby mode"));
                        sendToStream(clientStream, myEncoder.LP_MSG("       start <id> - Start the Lapper instance id"));
                        sendToStream(clientStream, myEncoder.LP_MSG("       stop <id> - Stop the Lapper instance id"));
                        sendToStream(clientStream, myEncoder.LP_MSG("       msg <msg> - Send a lfs message to all connected instances"));
                        sendToStream(clientStream, myEncoder.LP_MSG("       /<command> - Send a LFS command to all connected instances"));
                        sendToStream(clientStream, myEncoder.LP_MSG("       quit - Close LFSLapper"));
                        sendToStream(clientStream, myEncoder.LP_EOP());

                        break;
                    case "":
                        break;
                    default:
                        sendToStream(clientStream, myEncoder.LP_MSG("Unknown Command"));
                        sendToStream(clientStream, myEncoder.LP_EOP());
                        break;
                }

            }

            tcpClient.Close();
        }
        private void sendToStream(NetworkStream clientStream, string str)
        {
            ASCIIEncoding encoder = new ASCIIEncoding();
            byte[] buffer = encoder.GetBytes(str);
            clientStream.Write(buffer, 0, buffer.Length);
            clientStream.Flush();
        }
        private void sendToStream(NetworkStream clientStream, byte[] buffer)
        {
            clientStream.Write(buffer, 0, buffer.Length);
            clientStream.Flush();
        }
    }

    public class Decoder
    {
        enum TypePack
        {
            LP_NONE,		//  0					: not used
            LP_CNT,   		//  1 - instruction		: Connection
            LP_CMD, 		//  2 - instruction		: Server list
        }

        /// <summary>
        /// Encoder for messages sent to LFS console
        /// </summary>
        /// <param name="msg">Message to send</param>
        /// <returns></returns>
        /// 
        public string packetHead(byte[] packet)
        {
            if (packet == null)
                return "";
            string packetHead;
            packetHead = Enum.GetName(typeof(TypePack), packet[1]);
            if (packetHead == null)
                packetHead = "LP_" + packet[1].ToString();
            packetHead = packetHead.Remove(0, 3);
            return packetHead;
        }

        static int pakGetByte(byte[] pak, int first)
        {
            return (int)pak[first];
        }
        static string pakGetString(byte[] pak, int first, int len) ///n'importe oik
        {
            return InSim.LfsEncoding.Current.GetString(pak, first, len);
        }
        static int pakGetWord(byte[] pak, int first)
        {
            return (int)System.BitConverter.ToUInt16(pak, first);
        }
        static int pakGetShort(byte[] pak, int first)
        {
            return (int)System.BitConverter.ToInt16(pak, first);
        }
        static long pakGetUnsigned(byte[] pak, int first)
        {
            return (long)System.BitConverter.ToUInt32(pak, first);
        }
        static int pakGetInt(byte[] pak, int first)
        {
            return (int)System.BitConverter.ToInt32(pak, first);
        }
        static float pakGetFloat(byte[] pak, int first)
        {
            return (float)System.BitConverter.ToSingle(pak, first);
        }
        public class CNT
        {
            public readonly string Pass;

            public CNT(byte[] packet)
            {
                Pass = pakGetString(packet, 2, 30);
            }
        }
        public class CMD
        {
            public readonly string Cmd;

            public CMD(byte[] packet)
            {
                Cmd = pakGetString(packet, 2, 80);
            }
        }
    }

    public class Encoder
    {
        enum TypePack
        {
            LP_NONE,		//  0					: not used
            LP_EOP,   		//  1 - instruction		: End of packets
            LP_LST, 		//  2 - instruction		: Server list
            LP_MSG, 		//  3 - instruction		: Message
        }

        /// <summary>
        /// Encoder for messages sent to LFS console
        /// </summary>
        /// <param name="msg">Message to send</param>
        /// <returns></returns>
        /// 
        //Lapperserver list
        public byte[] LP_LST(string name, string ip, int port, string pass, string workingDir, string cfgFile, int state )
        {
            int len;

            byte packLen = 175;
            byte[] packet = new byte[packLen];

            packet[0] = packLen;
            packet[1] = (int)TypePack.LP_LST;

            len = Math.Min(name.Length, 30);
            System.Text.Encoding.ASCII.GetBytes(name, 0, len, packet, 2);

            len = Math.Min(ip.Length, 30);
            System.Text.Encoding.ASCII.GetBytes(ip, 0, len, packet, 32);
           
            packet[62] = (byte)(port % 256);  // LSB
            packet[63] = (byte)(port / 256);  // MSB

            len = Math.Min(pass.Length, 20);
            System.Text.Encoding.ASCII.GetBytes(pass, 0, len, packet, 64);

            len = Math.Min(workingDir.Length, 60);
            System.Text.Encoding.ASCII.GetBytes(workingDir, 0, len, packet, 84);

            len = Math.Min(cfgFile.Length, 30);
            System.Text.Encoding.ASCII.GetBytes(cfgFile, 0, len, packet, 144);

            packet[174] = (byte)state;

            return packet;
        }
        public byte[] LP_EOP()
        {
            byte packLen = 2;
            byte[] packet = new byte[packLen];

            packet[0] = packLen;
            packet[1] = (int)TypePack.LP_EOP;
            return packet;
        }
        public byte[] LP_MSG( string message )
        {
            byte packLen = 82;
            byte[] packet = new byte[packLen];

            packet[0] = packLen;
            packet[1] = (int)TypePack.LP_MSG;
            int len = Math.Min(message.Length, 80);
            System.Text.Encoding.ASCII.GetBytes(message, 0, len, packet, 2);
            return packet;
        }
    }
}