using System;
using System.Collections.Generic;
using ModestTree;
using System.Linq;
using ModestTree.Util;

namespace Zenject
{
    public abstract class KeyedFactoryBase<TBase, TKey> : IValidatable
    {
        [Inject]
        readonly DiContainer _container = null;

        [InjectOptional]
        readonly List<ValuePair<TKey, Type>> _typePairs = null;

        Dictionary<TKey, Type> _typeMap;

        [InjectOptional]
        readonly Type _fallbackType = null;

        protected DiContainer Container
        {
            get
            {
                return _container;
            }
        }

        protected abstract IEnumerable<Type> ProvidedTypes
        {
            get;
        }

        public ICollection<TKey> Keys
        {
            get { return _typeMap.Keys; }
        }

        protected Dictionary<TKey, Type> TypeMap
        {
            get { return _typeMap; }
        }

        [Inject]
        public void Initialize()
        {
            Assert.That(_fallbackType == null || _fallbackType.DerivesFromOrEqual<TBase>(),
                "Expected fallback type '{0}' to derive from '{1}'", _fallbackType, typeof(TBase));

            var duplicates = _typePairs.Select(x => x.First).GetDuplicates();

            if (!duplicates.IsEmpty())
            {
                throw Assert.CreateException(
                    "Found duplicate values in KeyedFactory: {0}", duplicates.Select(x => x.ToString()).Join(", "));
            }

            _typeMap = _typePairs.ToDictionary(x => x.First, x => x.Second);
            _typePairs.Clear();
        }

        public bool HasKey(TKey key)
        {
            return _typeMap.ContainsKey(key);
        }

        protected Type GetTypeForKey(TKey key)
        {
            Type keyedType;

            if (!_typeMap.TryGetValue(key, out keyedType))
            {
                Assert.IsNotNull(_fallbackType, "Could not find instance for key '{0}'", key);
                return _fallbackType;
            }

            return keyedType;
        }

        public virtual void Validate()
        {
            foreach (var constructType in _typeMap.Values)
            {
                Container.InstantiateExplicit(
                    constructType, ValidationUtil.CreateDefaultArgs(ProvidedTypes.ToArray()));
            }
        }

        protected static ConditionBinder AddBindingInternal<TDerived>(DiContainer container, TKey key)
            where TDerived : TBase
        {
            return container.Bind<ValuePair<TKey, Type>>()
                .FromInstance(ValuePair.New(key, typeof(TDerived)));
        }
    }

    // Zero parameters
    public class KeyedFactory<TBase, TKey> : KeyedFactoryBase<TBase, TKey>
    {
        protected override IEnumerable<Type> ProvidedTypes
        {
            get
            {
                return new Type[0];
            }
        }

        public virtual TBase Create(TKey key)
        {
            var type = GetTypeForKey(key);
            return (TBase)Container.Instantiate(type);
        }
    }

    // One parameter
    public class KeyedFactory<TBase, TKey, TParam1> : KeyedFactoryBase<TBase, TKey>
    {
        protected override IEnumerable<Type> ProvidedTypes
        {
            get
            {
                return new Type[] { typeof(TParam1) };
            }
        }

        public virtual TBase Create(TKey key, TParam1 param1)
        {
            return (TBase)Container.InstantiateExplicit(
                GetTypeForKey(key),
                new List<TypeValuePair>()
                {
                    InjectUtil.CreateTypePair(param1),
                });
        }
    }

    // Two parameters
    public class KeyedFactory<TBase, TKey, TParam1, TParam2> : KeyedFactoryBase<TBase, TKey>
    {
        protected override IEnumerable<Type> ProvidedTypes
        {
            get
            {
                return new Type[] { typeof(TParam1), typeof(TParam2) };
            }
        }

        public virtual TBase Create(TKey key, TParam1 param1, TParam2 param2)
        {
            return (TBase)Container.InstantiateExplicit(
                GetTypeForKey(key),
                new List<TypeValuePair>()
                {
                    InjectUtil.CreateTypePair(param1),
                    InjectUtil.CreateTypePair(param2),
                });
        }
    }

    // Three parameters
    public class KeyedFactory<TBase, TKey, TParam1, TParam2, TParam3> : KeyedFactoryBase<TBase, TKey>
    {
        protected override IEnumerable<Type> ProvidedTypes
        {
            get
            {
                return new Type[] { typeof(TParam1), typeof(TParam2), typeof(TParam3) };
            }
        }

        public virtual TBase Create(TKey key, TParam1 param1, TParam2 param2, TParam3 param3)
        {
            return (TBase)Container.InstantiateExplicit(
                GetTypeForKey(key),
                new List<TypeValuePair>()
                {
                    InjectUtil.CreateTypePair(param1),
                    InjectUtil.CreateTypePair(param2),
                    InjectUtil.CreateTypePair(param3),
                });
        }
    }

    // Four parameters
    public class KeyedFactory<TBase, TKey, TParam1, TParam2, TParam3, TParam4> : KeyedFactoryBase<TBase, TKey>
    {
        protected override IEnumerable<Type> ProvidedTypes
        {
            get
            {
                return new Type[] { typeof(TParam1), typeof(TParam2), typeof(TParam3), typeof(TParam4) };
            }
        }

        public virtual TBase Create(TKey key, TParam1 param1, TParam2 param2, TParam3 param3, TParam4 param4)
        {
            return (TBase)Container.InstantiateExplicit(
                GetTypeForKey(key),
                new List<TypeValuePair>()
                {
                    InjectUtil.CreateTypePair(param1),
                    InjectUtil.CreateTypePair(param2),
                    InjectUtil.CreateTypePair(param3),
                    InjectUtil.CreateTypePair(param4),
                });
        }
    }
}
