//
// 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;
		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);
			this.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 ();

			var item = items [index];

			OnRemove (item, index);

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

		public bool Remove (T item)
		{
			var 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 ()
		{
			var array = new T [size];
			Array.Copy (items, 0, array, 0, size);
			return array;
		}

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

		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 = System.Math.Max (
				System.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 Enumerator (this);
		}

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

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

		public struct Enumerator : IEnumerator<T>, IDisposable {

			Collection<T> collection;
			T current;

			int next;
			readonly int version;

			public T Current {
				get { return current; }
			}

			object IEnumerator.Current {
				get {
					CheckState ();

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

					return current;
				}
			}

			internal Enumerator (Collection<T> collection)
				: this ()
			{
				this.collection = collection;
				this.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;
			}

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

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

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