using System;
using System.Linq;
using System.Threading.Tasks;
using Unity;

namespace Valg.EVA.Skanning.Commands
{
    public class CommandDispatcher : ICommandDispatcher
    {
        private readonly IUnityContainer container;

        public CommandDispatcher(IUnityContainer container)
        {
            this.container = container;
        }

        public void DispatchCommand<T>(T command)
        {
            var commandHandler = ResolveHandlerForCommandType<T>(command.GetType());
            commandHandler.HandleCommand(command);
        }

        public async Task DispatchCommandAsync<T>(T command)
        {
            var commandHandler = ResolveAsyncHandlerForCommandType<T>(command.GetType());
            await commandHandler.HandleCommandAsync(command).ConfigureAwait(false);
        }

        public async Task<R> DispatchCommandAsync<T, R>(T command)
        {
            var commandHandler = ResolveAsyncHandlerForCommandType<T, R>(command.GetType());
            var result = await commandHandler.HandleCommandAsync(command).ConfigureAwait(false);
            return result;
        }

        private IHandleAsyncCommandsWithResult<T, R> ResolveAsyncHandlerForCommandType<T, R>(Type commandType)
        {
            var handleCommandsType = typeof(IHandleAsyncCommandsWithResult<T, R>);
            var handler = ResolveHandler(commandType, handleCommandsType);
            return (IHandleAsyncCommandsWithResult<T, R>)handler;
        }

        private IHandleAsyncCommands<T> ResolveAsyncHandlerForCommandType<T>(Type commandType)
        {
            var handleCommandsType = typeof(IHandleAsyncCommands<T>);
            var handler = ResolveHandler(commandType, handleCommandsType);
            return (IHandleAsyncCommands<T>)handler;
        }

        public R DispatchCommand<T, R>(T command)
        {
            var commandHandler = ResolveHandlerForCommandType<T, R>(command.GetType());
            commandHandler.HandleCommand(command);
            return commandHandler.CommandResult;
        }

        private IHandleCommandsWithReturnValue<T, R> ResolveHandlerForCommandType<T, R>(Type commandType)
        {
            var handleCommandsType = typeof(IHandleCommandsWithReturnValue<T, R>);
            var handler = ResolveHandler(commandType, handleCommandsType);
            return (IHandleCommandsWithReturnValue<T, R>)handler;
        }

        private IHandleCommands<T> ResolveHandlerForCommandType<T>(Type commandType)
        {
            var handleCommandsType = typeof(IHandleCommands<T>);
            var handler = ResolveHandler(commandType, handleCommandsType);
            return (IHandleCommands<T>)handler;
        }

        private object ResolveHandler(Type commandType, Type handleCommandsType)
        {
            var registration = container.Registrations.Where(c =>
            {
                if (handleCommandsType.IsAssignableFrom(c.MappedToType))
                {
                    var interfaces = c.MappedToType.GetInterfaces();
                    return interfaces.Any(i => i.GenericTypeArguments[0] == commandType);
                }

                return false;
            }).First();
            var handler = container.Resolve(registration.RegisteredType);
            return handler;
        }
    }
}
