using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Reflection;
using System.Collections.ObjectModel;
using System.ServiceModel;

namespace CommunicationSupport
{
    [Serializable]
    internal class ServiceInfo
    {
        #region Members

        Type _serviceType;

        #endregion

        #region Properties

        public ServiceInfo() { }
        public ServiceInfo(Type serviceType)
        {
            SetService(serviceType);
        }
 
        public Type ServiceType
        {
            get { return _serviceType; }

            set 
            {
                SetService(value);
            }
        }

        public Dictionary<string,ServiceMethodInfo> Methods { get; private set; }
        public Collection<Type> KnownTypes { get; private set; }
        public Dictionary<string, Type> JsonMessageClasses { get; private set; }

        #endregion

        #region Private Methods

        void SetService(Type serviceType)
        {
            if(serviceType != null)
                CheckIfServiceIsCallable(serviceType);

            _serviceType = serviceType;
            Methods = null;
            KnownTypes = null;

            if (serviceType != null)
            {
                AssignKnownTypes(serviceType);
                GetCallableMethods(serviceType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
            }
        }


        void CheckIfServiceIsCallable(Type serviceType)
        {
            // sprawdza, czy dla danej klasy istnieje kontrakt usługi
            object[] attributes = serviceType.GetCustomAttributes(typeof(ServiceContractAttribute), true);
            if (attributes.Length == 0)
            {
                bool found = false;
                // jeśli nie, sprawdza, czy dla dana klasa ma jakiś interfejs
                object[] interfaces = serviceType.GetInterfaces();
                foreach (object interfaceType in interfaces)
                {
                    if(((Type)interfaceType).GetCustomAttributes(typeof(ServiceContractAttribute),true).Length > 0)
                    {
                        found = true;
                        break;
                    }
                }
                if(found == false)
                    throw new ArgumentException("Typ usługi nie zawiera atrybutu ServiceContract");
            }
        }

        void AssignKnownTypes(Type serviceType)
        {
            KnownTypes = new Collection<Type>();
            
            // uzyskuje typy znane na poziomie usług
            object[] serviceKnownTypeAttributes = serviceType.GetCustomAttributes(typeof(ServiceKnownTypeAttribute), true);
            foreach (object knownType in serviceKnownTypeAttributes)
            {
                KnownTypes.Add(((ServiceKnownTypeAttribute)knownType).Type);
            }

            // uzyskuje typy znane na poziomie metod
            MethodInfo[] serviceMethods = serviceType.GetMethods();
            foreach (MethodInfo method in serviceMethods)
            {
                object[] methodKnownTypeAttributes = method.GetCustomAttributes(typeof(ServiceKnownTypeAttribute), true);
                foreach (object knownType in methodKnownTypeAttributes)
                {
                    KnownTypes.Add(((ServiceKnownTypeAttribute)knownType).Type);
                }
            }

            // uzyskuje typy znane na poziomie interfejsów
            Type[] serviceInterfaces = serviceType.GetInterfaces();
            foreach (Type interfaceType in serviceInterfaces)
            {
                object[] interfaceKnownTypeAttributes = interfaceType.GetCustomAttributes(typeof(ServiceKnownTypeAttribute), true);
                foreach (object knownType in interfaceKnownTypeAttributes)
                {
                    KnownTypes.Add(((ServiceKnownTypeAttribute)knownType).Type);
                }

                // uzyskuje typy znane na poziomie metod interfejsu
                MethodInfo[] interfaceMethods = interfaceType.GetMethods();
                foreach (MethodInfo method in interfaceMethods)
                {
                    object[] methodKnownTypeAttributes = method.GetCustomAttributes(typeof(ServiceKnownTypeAttribute), true);
                    foreach (object knownType in methodKnownTypeAttributes)
                    {
                        KnownTypes.Add(((ServiceKnownTypeAttribute)knownType).Type);
                    }
                }
            }
        }

        void AddJsonMessageClass(MethodInfo method)
        {
            object[] jsonMessageClassAttributes = method.GetCustomAttributes(typeof(JsonMessageClassAttribute), false);
            foreach (JsonMessageClassAttribute jsonAttribute in jsonMessageClassAttributes)
            {
                JsonMessageClasses.Add(method.Name, jsonAttribute.ClassType);
                KnownTypes.Add(jsonAttribute.ClassType);
            }

        }
        void GetCallableMethods(MethodInfo[] methods)
        {
            Methods = new Dictionary<string, ServiceMethodInfo>();

            JsonMessageClasses = new Dictionary<string, Type>();

            foreach (MethodInfo method in methods)
            {
                // sprawdza, czy istnieje metoda, którą można wywołać
                object[] attributes = method.GetCustomAttributes(typeof(OperationContractAttribute), true);
                if (attributes.Length == 0)
                {
                    GetCallableMethodFromInterface(method.Name);
                }
                else
                {
                    Methods.Add(method.Name, new ServiceMethodInfo(method));
                    AddJsonMessageClass(method);
                }
            }

            if (Methods.Count == 0)
                throw new ArgumentException("Klasa usługi nie zawiera żadnych metod do wywołania");

        }

        void GetCallableMethodFromInterface(string methodName)
        {
            object[] interfaces = ServiceType.GetInterfaces();
            foreach (object interfaceType in interfaces)
            {
                object[] methods = ((Type)interfaceType).GetMethods();
                foreach (MethodInfo method in methods)
                {
                    if (method.Name == methodName)
                    {
                        if ((method.GetCustomAttributes(typeof(OperationContractAttribute), true).Length == 1))
                        {
                            Methods.Add(method.Name, new ServiceMethodInfo(method));
                            AddJsonMessageClass(method);
                        }
                    }
                }
            }

        }

        
        #endregion

    }

}
