//  
//  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.IO;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.Collections;
using LapperInstances;


namespace LapperInstances
{
    class Server
    {
        private TcpListener tcpListener;
        private Thread listenThread;
        private bool termAllThread = false;
        private 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);
            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;
            listenThread.Abort();
            listenThread.Join();
        }
        private void ListenForClients()
        {
            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")
                        {
//                            Console.WriteLine("Receive connexion packet! Open connection");
                            Decoder.CNT cnt = new Decoder.CNT(buff);
                            if (cnt.Pass == lapperServerPass)
                                break;
                            else
                                Console.WriteLine("Console connection refused, wrong lapperServerPass");
                        }
                        myDebug.WriteLine("err", "Not a connexion 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);
                }
            }
        }
        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 servInfos;
            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;
                if (cmd[0] == '/')
                    cmd = "cmdLfs " + cmd;
                string[] readed = cmd.Trim().Split(' ');

                switch (readed[0].ToLower())
                {
                    case "cmdlfs":
                        cmd = cmd.Substring(7).Trim();
                        if (readed.Length > 1)
                        {
                            mySetOfLFSInstances.sendCmdToGroupOfInstance("", cmd);
                        }
                        sendToStream(clientStream, myEncoder.LP_EOP());

                        break;
                    case "list":
                        Hashtable listServers = mySetOfLFSInstances.getListOfLapperInstanceFromFile();

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

                            pack = myEncoder.LP_LST(
                                servInfos.name,
                                servInfos.ip,
                                servInfos.port,
                                servInfos.password,
                                servInfos.workingDir,
                                servInfos.iniFile,
								(int)mySetOfLFSInstances.getStatus(servInfos.name) //state
                            );
                            sendToStream(clientStream, pack);
                        }
                        sendToStream(clientStream, myEncoder.LP_EOP());


                        break;
                    case "stop":
                        {
                            try
                            {
                                servInfos = mySetOfLFSInstances.goAutoNoneMode(readed[1]);
                                pack = myEncoder.LP_MSG("Lapper " + servInfos.ip + " / " + servInfos.port + " Stopped!");
                                sendToStream(clientStream, pack);
                            }
                            catch
                            {
                                pack = myEncoder.LP_MSG("Can't stop this LFS Instance!");
                                sendToStream(clientStream, pack);
                                sendToStream(clientStream, myEncoder.LP_EOP());
                            }
                            sendToStream(clientStream, myEncoder.LP_EOP());
                        }
                        break;
                    case "start":
                        {
                            try
                            {
                                servInfos = mySetOfLFSInstances.goWorkMode(readed[1]);
                                if( servInfos != null )
                                    sendToStream(clientStream, myEncoder.LP_MSG("Lapper " + servInfos.ip + " / " + servInfos.port + " Stopped!"));
                                sendToStream(clientStream, myEncoder.LP_MSG("Lapper " + servInfos.ip + " / " + servInfos.port + " Starting!"));
                                sendToStream(clientStream, myEncoder.LP_MSG("Server started"));
                            }
                            catch
                            {
                                pack = myEncoder.LP_MSG("Can't start this LFS Instance!");
                                sendToStream(clientStream, pack);
                                sendToStream(clientStream, myEncoder.LP_EOP());
                            }
                            sendToStream(clientStream, myEncoder.LP_EOP());
                        }
                        break;
                    case "help":
                        sendToStream(clientStream, myEncoder.LP_MSG("Usage:"));
                        sendToStream(clientStream, myEncoder.LP_MSG("help : print this help"));
                        sendToStream(clientStream, myEncoder.LP_MSG("list : List all config available"));
                        sendToStream(clientStream, myEncoder.LP_MSG("start id : Start the Lapper instance id. Use list to view available config"));
                        sendToStream(clientStream, myEncoder.LP_MSG("stop id : Stop the Lapper instance id. Use list to view available config"));
                        sendToStream(clientStream, myEncoder.LP_MSG("cmdlfs msg : Send a lfs message to all instance connected"));
                        sendToStream(clientStream, myEncoder.LP_MSG("quit Exit from Lapper"));
                        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		: Connexion
            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.CodePage.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, 30);
            }
        }

    }

    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>
        /// 
        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;
        }
    }
}