using System;
using System.Collections.Generic;
using SFML.Graphics;

namespace YourBeatingHeart.Main.Entities
{
    /// <summary>
    /// An EntityList holds a list of entities and provides convenience methods to call Update and Draw on all of them.
    /// Additionally it locks the list for the durations of these methods, so that any changes that occur (which would
    /// normally result in a ConcurrentModificationException) will be queued for later.
    /// 
    /// A more general method to call all Entities is ForEach. You can see an example of its usage in Update and Draw.
    /// </summary>
    public class EntityList<TEntity> where TEntity : Entity
    {
        protected int lockCounter;
        protected SortedDictionary<int, List<TEntity>> entityLayerDictionary;
        protected List<TEntity> addList;
        protected List<TEntity> removeList;
        private uint _count;

        protected bool IsLocked { get { return lockCounter > 0; } }

        public uint Count
        {
            get
            {
                return _count;
            }
        }

        public EntityList()
        {
            entityLayerDictionary = new SortedDictionary<int, List<TEntity>>();
            addList = new List<TEntity>();
            removeList = new List<TEntity>();

            lockCounter = 0;

            _count = 0;
        }

        public void Add(TEntity entity)
        {
            if (IsLocked)
            {
                addList.Add(entity);
            }
            else
            {
                InternalAdd(entity);
            }
        }

        public void AddRange(IEnumerable<TEntity> collection)
        {
            if (IsLocked)
            {
                addList.AddRange(collection);
            }
            else
            {
                foreach (var entity in collection)
                {
                    InternalAdd(entity);
                }
            }
        }

        public void Remove(TEntity entity)
        {
            if (IsLocked)
            {
                removeList.Add(entity);
                entity.MarkForRemoval();
            }
            else
            {
                InternalRemove(entity);
            }
        }

        public void RemoveRange(IEnumerable<TEntity> collection)
        {
            if (IsLocked)
            {
                removeList.AddRange(collection);
            }
            else
            {
                foreach (var entity in collection)
                {
                    InternalRemove(entity);
                }
            }
        }

        private void InternalAdd(TEntity entity)
        {
            if (!entityLayerDictionary.ContainsKey(entity.Layer))
                entityLayerDictionary.Add(entity.Layer, new List<TEntity>());

            entityLayerDictionary[entity.Layer].Add(entity);
            entity.Added();

            _count++;
        }

        private void InternalRemove(TEntity entity)
        {
            if (!entityLayerDictionary.ContainsKey(entity.Layer))
                return;

            var list = entityLayerDictionary[entity.Layer];
            list.Remove(entity);
            addList.Remove(entity);

            if (list.Count == 0)
                entityLayerDictionary.Remove(entity.Layer);

            _count--;

            entity.Removed();
        }

        public void Update(float elapsedTime)
        {
            ForEach(entity => entity.Update(elapsedTime));
        }

        public void Draw(RenderTarget renderTarget)
        {
            ForEach(entity => entity.Draw(renderTarget));
        }

        public void ForEach(Action<TEntity> function)
        {
            UpdateLists();

            lockCounter++;
            foreach (int layer in entityLayerDictionary.Keys)
            {
                entityLayerDictionary[layer].ForEach(function);
            }
            lockCounter--;
        }

        public SortedDictionary<int, List<TEntity>> CreateCopy()
        {
            UpdateLists();
            return new SortedDictionary<int, List<TEntity>>(entityLayerDictionary);
        }

        protected void UpdateLists()
        {
            if (lockCounter > 0)
                return;

            if (addList.Count > 0)
            {
                foreach (var entity in addList)
                {
                    InternalAdd(entity);
                }

                addList.Clear();
            }

            if (removeList.Count > 0)
            {
                foreach (var entity in removeList)
                {
                    InternalRemove(entity);
                }

                removeList.Clear();
            }
        }
    }
}
