using MonoGame.FourEngine.Attributes;
using MonoGame.FourEngine.Extensions;
using MonoGame.FourEngine.Services;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;

namespace MonoGame.FourEngine
{
    public static class ServiceLocator
    {
        public static IReadOnlyCollection<Assembly> Assemblies { get; private set; }
        public static IReadOnlyDictionary<string, TypeInfo> Types { get; private set; }

        public static IKeyboardControl KeyboardControl => LazyResolver<IKeyboardControl>.Resolve();
        public static IPointerControl PointerControl => LazyResolver<IPointerControl>.Resolve();
        public static IGamePadControl GamePadControl => LazyResolver<IGamePadControl>.Resolve();
        public static IResourceLoader ResourceLoader => LazyResolver<IResourceLoader>.Resolve();
        public static IResourceManager ResourceManager => LazyResolver<IResourceManager>.Resolve();
        public static IResourceRegistry ResourceRegistry => LazyResolver<IResourceRegistry>.Resolve();
        public static ISpriteBatchFactory SpriteBatchFactory => LazyResolver<ISpriteBatchFactory>.Resolve();

        private static Dictionary<string, TypeInfo> _reg;

        public static void Init(params Assembly[] assemblies)
        {
            Assemblies = new ReadOnlyCollection<Assembly>(assemblies);

            var priorityTypes = assemblies.Select(a => a.DefinedTypes).ToList();
            var allTypes = priorityTypes.SelectMany(a => a).Where(t => t.IsPublic).ToList();
            var services = allTypes.Where(t => t.HasAttribute<ServiceAttribute>()).ToList();

            var typeDict = new Dictionary<string, TypeInfo>();

            foreach (var service in services)
            {
                TypeInfo impl = null;
                foreach (var types in priorityTypes)
                {
                    impl = types
                        .Where(t => service.IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface)
                        .OrderBy(t => t.HasAttribute<DefaultAttribute>())
                        .FirstOrDefault();
                    if (impl != null)
                        break;
                }
                if (impl != null)
                {
                    typeDict.Add(service.Name, impl);
                    continue;
                }
                throw new NotImplementedException($"Service [{service.Name}] has no implementation.");
            }
            _reg = typeDict;
            Types = new ReadOnlyDictionary<string, TypeInfo>(allTypes.ToDictionary(t => t.Name));
        }

        public static T Resolve<T>()
            => (T)Activator.CreateInstance(_reg[typeof(T).Name].AsType());

        public static object Resolve(string typeName)
            => Activator.CreateInstance(_reg[typeName].AsType());

        public static object Resolve(Type type)
            => Resolve(type.Name);

        public static class LazyResolver<T> where T : class
        {
            public static T Resolve()
                => _impl
                ?? (_impl = Resolve<T>());

            private static T _impl;
        }
    }
}