//
// Author:
//   Jb Evain (jbevain@gmail.com)
//
// Copyright (c) 2008 - 2015 Jb Evain
// Copyright (c) 2008 - 2011 Novell, Inc.
//
// Licensed under the MIT/X11 license.
//

using MonoFN.Cecil;
using System;
using System.Collections;
using System.Collections.Generic;

namespace MonoFN.Collections.Generic
{
    public class Collection<T> : IList<T>, IList
    {
        internal T[] items;
        internal int size;
        private int version;
        public int Count
        {
            get { return size; }
        }
        public T this[int index]
        {
            get
            {
                if (index >= size)
                    throw new ArgumentOutOfRangeException();

                return items[index];
            }
            set
            {
                CheckIndex(index);
                if (index == size)
                    throw new ArgumentOutOfRangeException();

                OnSet(value, index);

                items[index] = value;
            }
        }
        public int Capacity
        {
            get { return items.Length; }
            set
            {
                if (value < 0 || value < size)
                    throw new ArgumentOutOfRangeException();

                Resize(value);
            }
        }
        bool ICollection<T>.IsReadOnly
        {
            get { return false; }
        }
        bool IList.IsFixedSize
        {
            get { return false; }
        }
        bool IList.IsReadOnly
        {
            get { return false; }
        }
        object IList.this[int index]
        {
            get { return this[index]; }
            set
            {
                CheckIndex(index);

                try
                {
                    this[index] = (T)value;
                    return;
                }
                catch (InvalidCastException) { }
                catch (NullReferenceException) { }

                throw new ArgumentException();
            }
        }
        int ICollection.Count
        {
            get { return Count; }
        }
        bool ICollection.IsSynchronized
        {
            get { return false; }
        }
        object ICollection.SyncRoot
        {
            get { return this; }
        }

        public Collection()
        {
            items = Empty<T>.Array;
        }

        public Collection(int capacity)
        {
            if (capacity < 0)
                throw new ArgumentOutOfRangeException();

            items = capacity == 0 ? Empty<T>.Array : new T [capacity];
        }

        public Collection(ICollection<T> items)
        {
            if (items == null)
                throw new ArgumentNullException("items");

            this.items = new T [items.Count];
            items.CopyTo(this.items, 0);
            size = this.items.Length;
        }

        public void Add(T item)
        {
            if (size == items.Length)
                Grow(1);

            OnAdd(item, size);

            items[size++] = item;
            version++;
        }

        public bool Contains(T item)
        {
            return IndexOf(item) != -1;
        }

        public int IndexOf(T item)
        {
            return Array.IndexOf(items, item, 0, size);
        }

        public void Insert(int index, T item)
        {
            CheckIndex(index);
            if (size == items.Length)
                Grow(1);

            OnInsert(item, index);

            Shift(index, 1);
            items[index] = item;
            version++;
        }

        public void RemoveAt(int index)
        {
            if (index < 0 || index >= size)
                throw new ArgumentOutOfRangeException();

            T item = items[index];

            OnRemove(item, index);

            Shift(index, -1);
            version++;
        }

        public bool Remove(T item)
        {
            int index = IndexOf(item);
            if (index == -1)
                return false;

            OnRemove(item, index);

            Shift(index, -1);
            version++;

            return true;
        }

        public void Clear()
        {
            OnClear();

            Array.Clear(items, 0, size);
            size = 0;
            version++;
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            Array.Copy(items, 0, array, arrayIndex, size);
        }

        public T[] ToArray()
        {
            T[] array = new T [size];
            Array.Copy(items, 0, array, 0, size);
            return array;
        }

        private void CheckIndex(int index)
        {
            if (index < 0 || index > size)
                throw new ArgumentOutOfRangeException();
        }

        private void Shift(int start, int delta)
        {
            if (delta < 0)
                start -= delta;

            if (start < size)
                Array.Copy(items, start, items, start + delta, size - start);

            size += delta;

            if (delta < 0)
                Array.Clear(items, size, -delta);
        }

        protected virtual void OnAdd(T item, int index) { }
        protected virtual void OnInsert(T item, int index) { }
        protected virtual void OnSet(T item, int index) { }
        protected virtual void OnRemove(T item, int index) { }
        protected virtual void OnClear() { }

        internal virtual void Grow(int desired)
        {
            int new_size = size + desired;
            if (new_size <= items.Length)
                return;

            const int default_capacity = 4;

            new_size = Math.Max(Math.Max(items.Length * 2, default_capacity), new_size);

            Resize(new_size);
        }

        protected void Resize(int new_size)
        {
            if (new_size == size)
                return;
            if (new_size < size)
                throw new ArgumentOutOfRangeException();

            items = items.Resize(new_size);
        }

        int IList.Add(object value)
        {
            try
            {
                Add((T)value);
                return size - 1;
            }
            catch (InvalidCastException) { }
            catch (NullReferenceException) { }

            throw new ArgumentException();
        }

        void IList.Clear()
        {
            Clear();
        }

        bool IList.Contains(object value)
        {
            return ((IList)this).IndexOf(value) > -1;
        }

        int IList.IndexOf(object value)
        {
            try
            {
                return IndexOf((T)value);
            }
            catch (InvalidCastException) { }
            catch (NullReferenceException) { }

            return -1;
        }

        void IList.Insert(int index, object value)
        {
            CheckIndex(index);

            try
            {
                Insert(index, (T)value);
                return;
            }
            catch (InvalidCastException) { }
            catch (NullReferenceException) { }

            throw new ArgumentException();
        }

        void IList.Remove(object value)
        {
            try
            {
                Remove((T)value);
            }
            catch (InvalidCastException) { }
            catch (NullReferenceException) { }
        }

        void IList.RemoveAt(int index)
        {
            RemoveAt(index);
        }

        void ICollection.CopyTo(Array array, int index)
        {
            Array.Copy(items, 0, array, index, size);
        }

        public Enumerator GetEnumerator()
        {
            return new(this);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new Enumerator(this);
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return new Enumerator(this);
        }

        public struct Enumerator : IEnumerator<T>, IDisposable
        {
            private Collection<T> collection;
            private int next;
            private readonly int version;
            public T Current { get; private set; }
            object IEnumerator.Current
            {
                get
                {
                    CheckState();

                    if (next <= 0)
                        throw new InvalidOperationException();

                    return Current;
                }
            }

            internal Enumerator(Collection<T> collection) : this()
            {
                this.collection = collection;
                version = collection.version;
            }

            public bool MoveNext()
            {
                CheckState();

                if (next < 0)
                    return false;

                if (next < collection.size)
                {
                    Current = collection.items[next++];
                    return true;
                }

                next = -1;
                return false;
            }

            public void Reset()
            {
                CheckState();

                next = 0;
            }

            private void CheckState()
            {
                if (collection == null)
                    throw new ObjectDisposedException(GetType().FullName);

                if (version != collection.version)
                    throw new InvalidOperationException();
            }

            public void Dispose()
            {
                collection = null;
            }
        }
    }
}