namespace SRF
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using UnityEngine;
    /// 
    /// IList implementation which does not release the buffer when clearing/removing elements. Based on the NGUI BetterList
    /// 
    [Serializable]
    public class SRList : IList, ISerializationCallbackReceiver
    {
        [SerializeField] private T[] _buffer;
        [SerializeField] private int _count;
        private EqualityComparer _equalityComparer;
        private ReadOnlyCollection _readOnlyWrapper;
        public SRList() {}
        public SRList(int capacity)
        {
            Buffer = new T[capacity];
        }
        /// 
        /// Create a new list with the range of values. Contains a foreach loop, which will allocate garbage when used with most
        /// generic collection types.
        /// 
        public SRList(IEnumerable source)
        {
            AddRange(source);
        }
        public T[] Buffer
        {
            get { return _buffer; }
            private set { _buffer = value; }
        }
        private EqualityComparer EqualityComparer
        {
            get
            {
                if (_equalityComparer == null)
                {
                    _equalityComparer = EqualityComparer.Default;
                }
                return _equalityComparer;
            }
        }
        public int Count
        {
            get { return _count; }
            private set { _count = value; }
        }
        public IEnumerator GetEnumerator()
        {
            if (Buffer != null)
            {
                for (var i = 0; i < Count; ++i)
                {
                    yield return Buffer[i];
                }
            }
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        public void Add(T item)
        {
            if (Buffer == null || Count == Buffer.Length)
            {
                Expand();
            }
            Buffer[Count++] = item;
        }
        public void Clear()
        {
            Count = 0;
        }
        public bool Contains(T item)
        {
            if (Buffer == null)
            {
                return false;
            }
            for (var i = 0; i < Count; ++i)
            {
                if (EqualityComparer.Equals(Buffer[i], item))
                {
                    return true;
                }
            }
            return false;
        }
        public void CopyTo(T[] array, int arrayIndex)
        {
            Trim();
            Buffer.CopyTo(array, arrayIndex);
        }
        public bool Remove(T item)
        {
            if (Buffer == null)
            {
                return false;
            }
            var index = IndexOf(item);
            if (index < 0)
            {
                return false;
            }
            RemoveAt(index);
            return true;
        }
        public bool IsReadOnly
        {
            get { return false; }
        }
        public int IndexOf(T item)
        {
            if (Buffer == null)
            {
                return -1;
            }
            for (var i = 0; i < Count; ++i)
            {
                if (EqualityComparer.Equals(Buffer[i], item))
                {
                    return i;
                }
            }
            return -1;
        }
        public void Insert(int index, T item)
        {
            if (Buffer == null || Count == Buffer.Length)
            {
                Expand();
            }
            if (index < Count)
            {
                for (var i = Count; i > index; --i)
                {
                    Buffer[i] = Buffer[i - 1];
                }
                Buffer[index] = item;
                ++Count;
            }
            else
            {
                Add(item);
            }
        }
        public void RemoveAt(int index)
        {
            if (Buffer != null && index < Count)
            {
                --Count;
                Buffer[index] = default(T);
                for (var b = index; b < Count; ++b)
                {
                    Buffer[b] = Buffer[b + 1];
                }
            }
        }
        public T this[int index]
        {
            get
            {
                if (Buffer == null)
                {
                    throw new IndexOutOfRangeException();
                }
                return Buffer[index];
            }
            set
            {
                if (Buffer == null)
                {
                    throw new IndexOutOfRangeException();
                }
                Buffer[index] = value;
            }
        }
        public void OnBeforeSerialize()
        {
            Debug.Log("[OnBeforeSerialize] Count: {0}".Fmt(_count));
            // Clean buffer of unused elements before serializing
            Clean();
        }
        public void OnAfterDeserialize()
        {
            Debug.Log("[OnAfterDeserialize] Count: {0}".Fmt(_count));
        }
        /// 
        /// Add range of values to the list. Contains a foreach loop, which will allocate garbage when used with most
        /// generic collection types.
        /// 
        /// 
        public void AddRange(IEnumerable range)
        {
            foreach (var item in range)
            {
                Add(item);
            }
        }
        /// 
        /// Clear the list, optionally setting each element to default(T)
        /// 
        public void Clear(bool clean)
        {
            Clear();
            if (!clean)
            {
                return;
            }
            Clean();
        }
        public void Clean()
        {
            if (Buffer == null)
            {
                return;
            }
            for (var i = Count; i < _buffer.Length; i++)
            {
                _buffer[i] = default(T);
            }
        }
        /// 
        /// Get a read-only wrapper of this list. This is cached, so very little cost after first called.
        /// 
        /// 
        public ReadOnlyCollection AsReadOnly()
        {
            if (_readOnlyWrapper == null)
            {
                _readOnlyWrapper = new ReadOnlyCollection(this);
            }
            return _readOnlyWrapper;
        }
        /// 
        /// Helper function that expands the size of the array, maintaining the content.
        /// 
        private void Expand()
        {
            var newList = (Buffer != null) ? new T[Mathf.Max(Buffer.Length << 1, 32)] : new T[32];
            if (Buffer != null && Count > 0)
            {
                Buffer.CopyTo(newList, 0);
            }
            Buffer = newList;
        }
        /// 
        /// Trim the unnecessary memory, resizing the buffer to be of 'Length' size.
        /// Call this function only if you are sure that the buffer won't need to resize anytime soon.
        /// 
        public void Trim()
        {
            if (Count > 0)
            {
                if (Count >= Buffer.Length)
                {
                    return;
                }
                var newList = new T[Count];
                for (var i = 0; i < Count; ++i)
                {
                    newList[i] = Buffer[i];
                }
                Buffer = newList;
            }
            else
            {
                Buffer = new T[0];
            }
        }
        /// 
        /// List.Sort equivalent.
        /// 
        public void Sort(Comparison comparer)
        {
            var changed = true;
            while (changed)
            {
                changed = false;
                for (var i = 1; i < Count; ++i)
                {
                    if (comparer.Invoke(Buffer[i - 1], Buffer[i]) > 0)
                    {
                        var temp = Buffer[i];
                        Buffer[i] = Buffer[i - 1];
                        Buffer[i - 1] = temp;
                        changed = true;
                    }
                }
            }
        }
    }
}