using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Globalization;

namespace Administration
{
    class MessageWrapper
    {
        public string messageName;
        public object messageData;

        public MessageWrapper(string messageName, object messageData)
        {
            this.messageName = messageName;
            this.messageData = messageData;
        }
    }

    class ClientCommunicator 
    {
        private Client client;        
        private Queue<MessageWrapper> messages;
        private object messagesLock = new object();
        private ProtocolReader reader = new ProtocolReader();
        private static NumberFormatInfo format = new NumberFormatInfo();                     

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

        private static ClientCommunicator instance = new ClientCommunicator();

        public static ClientCommunicator Instance
        {
            get
            {
                return instance;
            }
        }

        public bool IsActive
        {
            get
            {
                return client.IsActive;
            }
        }

        public void MessageReceivedHandler()
        {            
            string plainMessage = client.GetNextMessage();
            Message translatedMessage = reader.TranslateMessage(plainMessage);

            if (translatedMessage != null) {
                object decodedMessage = DecodeMessage(translatedMessage);

                if (decodedMessage != null) 
                {
                    lock (messagesLock)
                    {
                        messages.Enqueue(new MessageWrapper(translatedMessage.Name, decodedMessage));
                    }

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

        public void Connect(string server, int port)
        {
            client.Connect(server, port);
        }

        public void Disconnect()
        {
            client.Disconnect();
        }

        public bool SendMessage(object request)
        {
            try
            {
                client.SendMessage(EncodeMessage(request));
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }
        }

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

            return null;
        }

        public object DecodeMessage(Message translatedMessage) 
        {
            switch (translatedMessage.Name)
            {
                case "ping":
                    return DecodeMessage<PingAnswer>(translatedMessage);

                case Requests.REQUEST_ADMIN_PLAYERS:
                    return DecodeMessage<PlayersAnswer>(translatedMessage);

                case Requests.REQUEST_ADMIN_PLAYER_INFORMATION:
                    return DecodeMessage<PlayerInformationAnswer>(translatedMessage);

                case Requests.REQUEST_ADMIN_SHIP_INFORMATION:
                    return DecodeMessage<ShipInformationAnswer>(translatedMessage);

                case Requests.REQUEST_ADMIN_PLAYER_BAN_INFORMATION:
                    return DecodeMessage<PlayerBanInformationAnswer>(translatedMessage);

                case Requests.REQUEST_ADMIN_BAN_PLAYER:
                    return DecodeMessage<BanPlayerAnswer>(translatedMessage);

                case Requests.REQUEST_ADMIN_ADD_REALM:
                    return DecodeMessage<AddRealmAnswer>(translatedMessage);

                case Requests.REQUEST_ADMIN_LOGIN:
                    return new LoginAnswer();
            }

            return null;
        }

        public static string EncodeMessage(object request)
        {
            ProtocolWriter writer = new ProtocolWriter();

            string requestName = GetRequestName(request);

            writer.beginMessage(MessageType.MT_REQUEST, requestName);
            WriteClassValue(writer, request, String.Empty);
            writer.endMessage(true);

            return writer.copyMessage();
        }

        private ClientCommunicator()
        {
            messages = new Queue<MessageWrapper>();

            // Vytvoříme klienta pro komunikaci
            client = new Client();

            // Zaregistrujeme odběratele
            client.MessageAcceptedEvent += new Client.MessageAcceptedEventHandler(MessageReceivedHandler);            
        }

        static ClientCommunicator() {
           format.NumberDecimalSeparator = ".";
        }

        public static T DecodeMessage<T>(Message message)
        {
            return (T)ReadClassValue(message, typeof(T), null);
        }

        private static void WriteClassValue(ProtocolWriter writer, object request, string parentPrefix)
        {
            if (parentPrefix == null)
            {
                parentPrefix = string.Empty;
            }

            FieldInfo[] fields = request.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);

            foreach (FieldInfo field in fields)
            {
                WriteValue(writer, parentPrefix, GetFieldName(field), field.GetValue(request));
            }
        }

        private static object ReadClassValue(Message message, Type valueType, string prefix)
        {
            if (prefix == null) 
            {
                prefix = string.Empty;
            }

            object answer = Activator.CreateInstance(valueType);

            object[] classAttributes = answer.GetType().GetCustomAttributes(typeof(ProtocolMapping), false);
            FieldInfo[] fields = answer.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);

            foreach (FieldInfo field in fields)
            {
                string parameterName = prefix + GetFieldName(field);
                field.SetValue(answer, ReadValue(message, parameterName, field.FieldType));
            }

            return answer;
        }

        private static object ReadValue(Message message, string prefix, Type fieldType)
        {
            string parameterName = prefix;

            if (parameterName == null)
            {
                parameterName = string.Empty;
            }

            if (fieldType.IsPrimitive)
            {
                if (fieldType.IsAssignableFrom(typeof(bool)))
                {
                    int value = int.Parse(ExtractFromMessage(message, parameterName));
                    return value == 1 ? true : false;
                }
                else if (fieldType.IsAssignableFrom(typeof(double))) {
                     return double.Parse(ExtractFromMessage(message, parameterName), format);
                }
                else if (fieldType.IsAssignableFrom(typeof(double)))
                {
                    return double.Parse(ExtractFromMessage(message, parameterName), format);
                }
                else if (fieldType.IsAssignableFrom(typeof(float)))
                {
                    return float.Parse(ExtractFromMessage(message, parameterName), format);
                }
                else
                {
                    return ConvertMapValue(message, fieldType, parameterName);
                }
            }
            else if (fieldType.IsAssignableFrom(typeof(string)))
            {
                return ExtractFromMessage(message, parameterName);
            }
            else if (fieldType.IsArray)
            {
                return ReadArrayValue(message, fieldType, parameterName);
            }
            else if (fieldType.IsClass)
            {
                return ReadClassValue(message, fieldType, parameterName + "_");
            }
            else if (fieldType.IsValueType)
            {
                return ReadClassValue(message, fieldType, parameterName + "_");
            }

            return null;
        }

        private static object ConvertMapValue(Message message, Type fieldType, string parameterName)
        {
            string mapValue = ExtractFromMessage(message, parameterName);

            if (mapValue != null)
            {
                return Convert.ChangeType(mapValue, fieldType);
            }
            else
            {
                return null;
            }
        }

        private static string ExtractFromMessage(Message message, string parameterName) {
            if (!message.Contains(parameterName))
            {
                Console.WriteLine("MISSING: " + parameterName + " in " + message.Name);
                return null;
            }
            return message[parameterName];
        }


        private static Array ReadArrayValue(Message message, Type type, string parameterName)
        {
            int size = int.Parse(message[CreateCountParameterName(parameterName)]);

            Array list = Array.CreateInstance(type.GetElementType(), size);            
       
            for (int realIndex = 0; realIndex < size; ++realIndex)
            {
                object value = ReadValue(message, CreateIndexParameterName(parameterName, realIndex), type.GetElementType());               
                list.SetValue(value, realIndex);
            }

            return list;
        }

        private static bool IsTypeEnumerable(Type type) {
            Type[] listInterfaces = type.GetInterfaces();

            foreach (Type propertyInterface in listInterfaces)               
            {
                if (propertyInterface.IsGenericType)
                {
                    if (propertyInterface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private static void WriteValue(ProtocolWriter writer, string prefix, string parameterName, object value)
        {
            if (value == null)
            {
                return;
            }

            if (prefix == null)
            {
                prefix = string.Empty;
            }

            Type valueType = value.GetType();

            if (valueType.IsPrimitive)
            {
                WritePrimitiveValue(writer, prefix + parameterName, value);
            }
            else if (valueType.IsAssignableFrom(typeof(decimal)))
            {
                writer.writeValue(prefix + parameterName, (decimal)value);
            }
            else if (valueType.IsAssignableFrom(typeof(string)))
            {
                writer.writeValue(prefix + parameterName, (string)value);
            }
            else if (IsTypeEnumerable(valueType))
            {                
                System.Collections.IEnumerable collection = (System.Collections.IEnumerable)value;                

                int index = 0;
                foreach (object collectionItem in collection)
                {
                    WriteValue(writer, prefix, CreateIndexParameterName(parameterName, index), collectionItem);
                    index++;
                }

                writer.writeValue(CreateCountParameterName(parameterName), index);
            }
            else if (valueType.IsClass)
            {
                WriteClassValue(writer, value, prefix + parameterName + "_");
            }
            else if (valueType.IsValueType)
            {
                WriteClassValue(writer, value, prefix + parameterName + "_");
            }
        }

        private static void WritePrimitiveValue(ProtocolWriter writer, string parameterName, object value)
        {
            if (value is bool)
            {
                writer.writeValue(parameterName, (bool)value ? 1 : 0);
            }
            else if (value is float)
            {
                writer.writeFloat(parameterName, (float) value);
            }
            else if (value is double)
            {
                writer.writeDouble(parameterName, (double) value);
            }
            else
            {
                writer.writeValue(parameterName, value);
            }
        }

        private static string GetFieldName(FieldInfo field)
        {
            string parameterName = null;

            object[] attributes = field.GetCustomAttributes(typeof(ProtocolMapping), false);

            if (attributes.Length == 1)
            {
                ProtocolMapping mapping = (ProtocolMapping)attributes[0];

                if (mapping != null)
                {
                    parameterName = mapping.Name;
                }
            }

            if (parameterName == null)
            {
                parameterName = field.Name;
            }

            return parameterName;
        }

        private static string GetRequestName(object request)
        {
            object[] classAttributes = request.GetType().GetCustomAttributes(typeof(ProtocolMapping), false);

            string requestName = null;

            if (classAttributes.Length == 1)
            {
                ProtocolMapping classMapping = (ProtocolMapping)classAttributes[0];

                if (classMapping != null)
                {
                    requestName = classMapping.Name;
                }
            }

            if (requestName == null)
            {
                requestName = request.GetType().Name;
            }

            return requestName;
        }

        private static string CreateCountParameterName(string parameterName)
        {
            return parameterName + "_" + "count";
        }

        private static string CreateIndexParameterName(string parameterName, int realIndex)
        {
            return parameterName + "_" + (realIndex + 1);
        }
    }
}
