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

namespace Administration
{
    public class Client
    {
        const uint MAX_MESSAGE_LENGTH = 65536;

        private Queue<string> messages = new Queue<string>();
        private object messagesLock = new object();
        private TcpClient client;
        private NetworkStream stream;
        private Thread receiveThread;
        private Thread sendThread;
        private string pingMessage;
        private DateTime lastPingTime;

        public delegate void MessageAcceptedEventHandler();
        public event MessageAcceptedEventHandler MessageAcceptedEvent;

        public Client()
        {
            ProtocolWriter writer = new ProtocolWriter();
            writer.beginMessage(MessageType.MT_REQUEST, Requests.REQUEST_ADMIN_PING);

            writer.endMessage(true);
            pingMessage = writer.copyMessage();

            lastPingTime = DateTime.Now;
        }

        ~Client()
        {
            Disconnect();
        }
       
        public void Connect(String server, int port)
        {
            // Create a TcpClient.
            client = new TcpClient(server, port);

            // Get a client stream for reading and writing.
            stream = client.GetStream();

            receiveThread = new Thread(new ThreadStart(ReceiveThreadRun));
            receiveThread.Start();

            sendThread = new Thread(new ThreadStart(SendThreadRun));
            sendThread.Start();
        }

        public bool IsActive
        {
            get
            {
                return client != null;
            }
        }

        public bool SendMessage(String message)
        {
            try
            {
                // Translate the passed message into ASCII and store it as a Byte array.
                Byte[] data = System.Text.Encoding.ASCII.GetBytes(message);

                if (!stream.CanWrite)
                {
                    return false;
                }
                
                // Send the message to the connected TcpServer. 
                stream.Write(data, 0, data.Length);
               
                Console.WriteLine("Sent: {0}", message);
                return true;
            }
            catch (ArgumentNullException e)
            {
                Console.WriteLine("ArgumentNullException: {0}", e);
            }
            catch (SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);
            }
            catch (System.IO.IOException e)
            {
                Console.WriteLine("IOException: {0}", e);
            }

            return false;
        }

        public void Disconnect()
        {
            if (receiveThread != null)
            {
                receiveThread.Abort();
            }

            if (sendThread != null)
            {
                sendThread.Abort();
            }

            if (stream != null)
            {
                // Close everything.
                stream.Close();
            }

            if (client != null)
            {
                client.Close();
            }
        }

        private bool ReadMessage(out string result)
        {
            result = null;
	        int length = 0;
	        try
	        {
		        // nacteme ze socketu pocet znaku v nasledujici zprave
		        string len = "";

		        while (true)
		        {
			        if (stream.CanRead)
			        {
                        int read = stream.ReadByte();

                        char c = (char) read;

                        if (char.IsDigit(c))
                        {
                            len += c;
                        }
                        else
                        {
                            break;
                        }
			        }
			        else
			        {
				        return false;
			        }
		        }

                length = int.Parse(len);
	        }
	        catch (Exception)
	        {
		        return false;
	        }

	        // zkontrolujeme, jestli zprava neni prilis velka
	        if (length > MAX_MESSAGE_LENGTH || length <= 0)
	        {
		        return false;
	        }

            // naalokujeme buffer pro zpravu
            byte[] data = new byte[length];

            // nacteme zpravu
	        try
	        {
		        for (int i = 0; i < length; i++) 
		        {
			        if (stream.CanRead)
			        {
                        data[i] = (byte) stream.ReadByte();
			        }
			        else
			        {
				        return false;
			        }
		        }
	        }
	        catch (Exception)
	        {
		        return false;
	        }

            // nastavime zpravu
            result = System.Text.Encoding.ASCII.GetString(data, 0, length);

	        return true;
        }

        private void ReceiveThreadRun()
        {
            while (true)
            {
                ReceiveMessages();
            }
        }

        private void SendThreadRun()
        {
            while (true)
            {
                SendPingIfNeccessary();
                Thread.Sleep(100);
            }
        }

        private void SendPingIfNeccessary()
        {           
            if ((DateTime.Now - lastPingTime).TotalMilliseconds > 3000)
            {
                SendMessage(pingMessage);
                lastPingTime = DateTime.Now;
            }
        }

        private void ReceiveMessages()
        {    
            while (true)
            {
		        string message;
                if (!ReadMessage(out message))
                {
			        break;
		        }

                lock (messagesLock)
                {
                    messages.Enqueue(message);
                }

                if (MessageAcceptedEvent != null)
                {
                    MessageAcceptedEvent();
                }

                Console.WriteLine("Received: {0}", message);
            }
        }

        public string GetNextMessage()
        {
            lock (messagesLock)
            {
                if (messages.Count > 0)
                {
                    return messages.Dequeue();
                }
            }

            return null;
        }
    }

    public enum MessageType
    {
        MT_REQUEST, MT_ANSWER, MT_UPDATE, MT_UNDEFINED
    }

    public enum AnswerKind
    {
        AK_OK,
        AK_FAIL,
        AK_UNDEFINED
    }

    public class Message 
    {
        private string name;
        private AnswerKind answerKind;
        private MessageType messageType;        
        private Dictionary<String, String> values;

        public Message(MessageType messageType, string name, AnswerKind answer)
        {
            this.messageType = messageType;
            this.name = name;
            this.answerKind = answer;
            this.values = new Dictionary<string, string>();
        }

        public bool Contains(string name)
        {
            return values.ContainsKey(name);
        }

        public MessageType MessageType
        {
            get
            {
                return messageType;
            }
            set
            {
                messageType = value;
            }
        }

        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                name = value;
            }
        }

        public AnswerKind Answer
        {
            get
            {
                return answerKind;
            }
            set
            {
                answerKind = value;
            }
        }

        public string this[string index] {
            get {
                return values[index];
            }
            set
            {
                values[index] = value;
            }
        }
    }

    public class ProtocolReader
    {
        public Message TranslateMessage(string message)
        {
            int currentIndex = 0;

            string name = string.Empty;
            string scannedToken = string.Empty;
            MessageType messageType = MessageType.MT_UNDEFINED;
	        AnswerKind answer = AnswerKind.AK_UNDEFINED;	        
	        Message result = new Message(messageType, name, answer);
                       
		    scanToken(message, ref currentIndex, out scannedToken);

		    // zjisti typ zpravy
            if (scannedToken == Constants.MSG_CHAR_REQUEST)
            {   
			    result.MessageType = MessageType.MT_REQUEST;
		    }
            else if (scannedToken == Constants.MSG_CHAR_ANSWER)
            {
			    result.MessageType = MessageType.MT_ANSWER;
		    }
            else if (scannedToken == Constants.MSG_CHAR_UPDATE)
            {
                result.MessageType = MessageType.MT_UPDATE;
            }
            else
            {
                // spatny typ zpravy, skonci
                return null;
            }

	        // nacteme jmeno
	        scanToken(message, ref currentIndex, out scannedToken);
	        result.Name = scannedToken;

	        // nacteme hodnoty
	        bool scanned = true;
	        while (true)
	        {
		        if (!scanned) 
		        {
			        return null;
		        }

		        // nacteme jmeno hodnoty, nebo konec souboru
		        scanned &= scanToken(message, ref currentIndex, out scannedToken);

		        // jestlize je jmeno hodnoty ukonceni, ukonci to
		        // zjisti typ zpravy
                if (scannedToken == Constants.MSG_CHAR_UPDATE_END) 
		        {
			        if (result.MessageType != MessageType.MT_UPDATE) 
			        {
				        return null;
			        }
			        break;
		        }

                if (scannedToken == Constants.MSG_CHAR_REQUEST_END) 
		        {
			        if (result.MessageType != MessageType.MT_REQUEST) 
			        {
				        return null;
			        }
			        break;
		        }

                if (scannedToken == Constants.MSG_CHAR_ANSWER_END_OK) 
		        {
			        if (result.MessageType != MessageType.MT_ANSWER) 
                    {
				        return null;
			        }
			        result.Answer = AnswerKind.AK_OK;
			        break;
		        }

                if (scannedToken == Constants.MSG_CHAR_ANSWER_END_FA) 
		        {
			        if (result.MessageType != MessageType.MT_ANSWER) 
			        {
				        return null;
			        }
			        result.Answer = AnswerKind.AK_FAIL;
			        break;
		        }

		        string valueName = scannedToken;
		        scanned &= scanToken(message, ref currentIndex, out scannedToken);

                try
                {
                    int size = int.Parse(scannedToken);
                    scanned &= scanChars(message, size, ref currentIndex, out scannedToken);

                    result[valueName] = scannedToken;
                }
                catch
                {
                    return null;
                }
	        }

	        return result;
        }

        bool scanToken(string message, ref int currentIndex, out string scannedToken)
        {
            scannedToken = string.Empty;

            if (currentIndex == message.Length)
            {
                return false;
            }

            while ((currentIndex < message.Length) && (message[currentIndex] != ' '))
	        {
                scannedToken += message[currentIndex];
		        currentIndex++;
	        }

            if (currentIndex != message.Length) currentIndex++;

	        return true;
        }

        bool scanChars(string message, int size, ref int currentIndex, out string scannedToken)
        {
            scannedToken = string.Empty;

            if (currentIndex == message.Length) return false;

            while ((size > 0) && (currentIndex < message.Length))
	        {
                scannedToken += message[currentIndex];
		        currentIndex++;
		        size--;
	        }

            if (currentIndex != message.Length) currentIndex++;

	        return size == 0;
        }
    }

    class ProtocolWriter {
	    MessageType messageType;
	    string output;

        public void beginMessage(MessageType messageType, string requestName) {
	        this.messageType = messageType;

	        switch (messageType)
	        {
	        case MessageType.MT_REQUEST:
                output = Constants.MSG_CHAR_REQUEST + " ";
                break;
	        case MessageType.MT_ANSWER:
                output = Constants.MSG_CHAR_ANSWER + " ";
                break;
	        case MessageType.MT_UPDATE:
                output = Constants.MSG_CHAR_UPDATE + " ";
                break;
	        }

	        output += requestName + " ";
        }

        public string copyMessage()
        {
            return string.Copy(output);
        }

        public string endMessage(bool answerOk)
        {
	        switch (messageType)
	        {
	        case MessageType.MT_REQUEST:
                output += Constants.MSG_CHAR_REQUEST_END;                                          
                break;
            case MessageType.MT_ANSWER:
                output += answerOk ? Constants.MSG_CHAR_ANSWER_END_OK : Constants.MSG_CHAR_ANSWER_END_FA;
                break;
	        case MessageType.MT_UPDATE:
                output += Constants.MSG_CHAR_UPDATE_END;
                break;
	        }

	        int outputLength = output.Length;
	        output = outputLength + " " + output;

	        return output;
        }

        public void writeFloat(string valueName, float value)
        {
            string valueString = value.ToString(System.Globalization.CultureInfo.InvariantCulture);
            output += valueName + " " + valueString.Length + " " + valueString + " ";
        }

        public void writeDouble(string valueName, double value) {
            string valueString = value.ToString(System.Globalization.CultureInfo.InvariantCulture);
	        output += valueName + " " + valueString.Length + " " + valueString + " ";
        }

        public void writeValue<T>(string valueName, T value) 
        {
            string valueString = value.ToString();
	        output += valueName + " " + valueString.Length + " " + valueString + " ";
        }
    }
}
