using System;
using System.Collections.Generic;
using System.Reflection;

namespace Wayne.Lib
{
    public static class ServiceActivator
    {
        public static T Create<T>(IServiceLocator serviceLocator, params object[] additionalParameter) where T : class
        {
            return (T)CreateInstance(string.Empty, typeof(T), serviceLocator, additionalParameter);
        }

        public static T Create<T>(string serviceId, IServiceLocator serviceLocator, params object[] additionalParameter) where T : class
        {
            return (T)CreateInstance(serviceId, typeof(T), serviceLocator, additionalParameter);
        }

        private static object CreateInstance(string servicId, Type typeToInstantiate, IServiceLocator serviceLocator, params object[] additionalParameter)
        {
            List<string> explaination = new List<string>();
            object result = TryCreate(typeToInstantiate, serviceLocator, servicId, explaination, additionalParameter);

            if (result == null)
                throw new ServiceContainerException(typeToInstantiate, string.Format("Could not create object of type {0} \r\n {1}", typeToInstantiate, string.Join("\r\n", explaination.ToArray())));

            return result;
        }

        private static object TryCreate(Type typeToCreate, IServiceLocator serviceLocator, string serviceId, List<string> explaination, params object[] additionalObjects)
        {
            var constructors = typeToCreate.GetConstructors();

            Array.Reverse(constructors);
            var parameters = new List<object>();
            ConstructorInfo foundConstructor = null;
            foreach (ConstructorInfo constructor in constructors)
            {
                var parameterInfos = constructor.GetParameters();
                parameters.Clear();
                foreach (var parameterInfo in parameterInfos)
                {
                    //Check for additionalObjectMatch first!
                    int firstMatchingParameterObjectIndex = -1;

                    for (int index = 0; index < additionalObjects.Length; index++)
                    {
                        object additionalObject = additionalObjects[index];
                        if (parameterInfo.ParameterType.IsInstanceOfType(additionalObject))
                        {
                            firstMatchingParameterObjectIndex = index;
                            break;
                        }
                    }

                    if (firstMatchingParameterObjectIndex >= 0)
                    {
                        parameters.Add(additionalObjects[firstMatchingParameterObjectIndex]);
                    }
                    else
                    {
                        var dependency = serviceLocator.TryGetService(parameterInfo.ParameterType, serviceId);

                        if (dependency != null)
                        {
                            parameters.Add(dependency);
                        }
                        else
                        {
                            explaination.Add(string.Format("Could not resolve parameter {0} of type {1}", parameterInfo.Name, parameterInfo.ParameterType));
                        }
                    }
                }

                if (parameters.Count == parameterInfos.Length)
                {
                    foundConstructor = constructor;
                    break;
                }
            }

            if (foundConstructor != null)
            {
                var stateObject = foundConstructor.Invoke(parameters.ToArray());
                return stateObject;
            }

            return null;
        }
    }
}