﻿using System;
using System.Collections.Generic;
using System.Linq;
using Amberframe.Collections;
using Amberframe.Controllers.Abstractions.Events;

// ReSharper disable SuggestBaseTypeForParameter

namespace Amberframe.Controllers.Events
{
    public class InputEventManager : IInputEventManager
    {
        private readonly LinkedList<IInputEventPipe> actualPipes = new LinkedList<IInputEventPipe>();

        public readonly ObjectConstructorCollection<IInputEventPipe> Pipes;

        private IInputEventProviderManager providerManager;
        
        public IInputEventProviderManager ProviderManager
        {
            get { return providerManager; }
            set
            {
                if (providerManager != null)
                {
                    providerManager.Providers.Clear();
                    providerManager.Emit -= Emit;
                }
                providerManager = value;
                providerManager.Emit += Emit;
            }
        }

        public IInputEventConsumerManager ConsumerManager { get; set; }

        public InputEventManager()
        {
            Pipes = new ObjectConstructorCollection<IInputEventPipe> {Added = p => AddPipesToEnd(p)};
        }

        private bool Emit(InputEvent inputEvent)
        {
            return actualPipes.Count == 0
                ? ConsumerManager.Consume(inputEvent)
                : actualPipes.First.Value.Consume(inputEvent);
        }

        private static void PairPipes(IInputEventPipe previous, IInputEventPipe next)
        {
            if (previous != null && next != null)
            {
                if (previous == next)
                    throw new InvalidOperationException("Cannot pair a pipe to itself");
                previous.Emit += next.Consume;
            }
        }

        private static void UnPairPipes(IInputEventPipe previous, IInputEventPipe next)
        {
            if (previous != null && next != null)
                previous.Emit -= next.Consume;
        }

        public IEnumerable<IInputEventPipe> GetPipes(Type type)
        {
            return actualPipes.Where(p => p.GetType() == type);
        }

        public void AddPipesToEnd(params IInputEventPipe[] pipes)
        {
            foreach (IInputEventPipe pipe in pipes)
            {
                PairPipes(actualPipes.Last?.Value, pipe);
                if (actualPipes?.Last != null)
                    actualPipes.Last.Value.Emit -= ConsumerManager.Consume;
                actualPipes.AddLast(pipe);
            }
            if (actualPipes?.Last != null)
                actualPipes.Last.Value.Emit += ConsumerManager.Consume;
        }

        public void AddPipesAfterPipe(IInputEventPipe after, params IInputEventPipe[] pipes)
        {
            LinkedListNode<IInputEventPipe> node = actualPipes.Find(after);
            if (node == null)
                throw new InvalidOperationException("after is not in the actualPipes");
            IInputEventPipe next = node.Next?.Value;
            if (next == null && pipes.Length > 0)
            {
                after.Emit -= ConsumerManager.Consume;
                pipes.Last().Emit += ConsumerManager.Consume;
            }
            UnPairPipes(node.Value, next);
            PairPipes(pipes.Last(), next);
            for (int i = 0; i < pipes.Length; i++)
            {
                if (i < pipes.Length - 1)
                    PairPipes(pipes[i], pipes[i + 1]);
                IInputEventPipe pipe = pipes[i];
                node = actualPipes.AddAfter(node, pipe);
            }
        }

        public void AddPipesToStart(params IInputEventPipe[] pipes)
        {
            for (int i = pipes.Length - 1; i >= 0; i--)
            {
                PairPipes(pipes[i], actualPipes.First.Value);
                actualPipes.AddFirst(pipes[i]);
            }
        }

        public void AddPipesBeforePipe(IInputEventPipe after, params IInputEventPipe[] pipes)
        {
            LinkedListNode<IInputEventPipe> node = actualPipes.Find(after);
            if (node == null)
                throw new KeyNotFoundException(nameof(after));
            IInputEventPipe previous = node.Previous?.Value;
            UnPairPipes(previous, node.Value);
            PairPipes(previous, pipes.First());
            for (int i = pipes.Length - 1; i >= 0; i--)
            {
                if (i < pipes.Length - 1)
                    PairPipes(pipes[i], pipes[i + 1]);
                IInputEventPipe pipe = pipes[i];
                node = actualPipes.AddBefore(node, pipe);
            }
        }

        public void RemovePipe(IInputEventPipe pipe)
        {
            LinkedListNode<IInputEventPipe> node = actualPipes.Find(pipe);
            if (node == null)
                throw new KeyNotFoundException(nameof(pipe));
            IInputEventPipe prev = node.Previous?.Value;
            IInputEventPipe next = node.Next?.Value;
            UnPairPipes(prev, pipe);
            UnPairPipes(pipe, next);
            PairPipes(prev, next);
            actualPipes.Remove(node);
        }

        public void RemovePipes<T>() where T : IInputEventPipe
        {
            RemovePipes(typeof(T));
        }

        public void RemovePipes(Type type)
        {
            foreach (IInputEventPipe pipe in GetPipes(type))
                RemovePipe(pipe);
        }
    }
}