using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
/* Turn off documentation warnings in this file. Generally they are 
 * relevant in this library, but not for this file since it is mostly 
 * methods found in normal List<> class, and thus it is no need to write 
 * that comments once again with the drawback of making it hard to read.
 */
#pragma warning disable 1591

namespace Wayne.Lib
{
    /// <summary>
    /// Extends the IList interface with some more members found in List class.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IAdvancedList<T> : IList<T>
    {
        void AddRange(IEnumerable<T> collection);
        ReadOnlyCollection<T> AsReadOnly();
        void CopyTo(T[] array);
        void CopyTo(int index, T[] array, int arrayIndex, int count);
        void BeginUpdate();
        void EndUpdate();
    }

    /// <summary>
    /// Extends IAdvanced List with an event handler that makes it possible to 
    /// follow when the contents has benn changed.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IObservableList<T> : IAdvancedList<T>
    {
        event EventHandler Changed;
    }

    /// <summary>
    /// Observable list is a list class similar to .Net's List but with an
    /// event that notifies when something has been changed in the list.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ObservableList<T> : IObservableList<T>, IList
    {
        private object syncRoot;
        private readonly List<T> internalList = new List<T>();
        private int updateCount;
        private bool updated;

        public IEnumerator<T> GetEnumerator()
        {
            return internalList.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public int Count
        {
            get { return internalList.Count; }
        }

        int ICollection.Count
        {
            get { return Count; }
        }

        public bool Contains(T item)
        {
            return internalList.Contains(item);
        }

        public bool Contains(object value)
        {
            return IsCompatibleObject(value) && Contains((T)value);
        }

        public void Clear()
        {
            if (internalList.Count > 0)
            {
                internalList.Clear();
                ListChanged();
            }
        }

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

        public int IndexOf(T item)
        {
            return internalList.IndexOf(item);
        }

        int IList.IndexOf(object value)
        {
            return IsCompatibleObject(value) ? IndexOf((T)value) : -1;
        }

        public T this[int index]
        {
            get { return internalList[index]; }
            set
            {
                internalList[index] = value;
                ListChanged();
            }
        }

        object IList.this[int index]
        {
            get { return internalList[index]; }
            set
            {
                VerifyValueType(value);
                this[index] = (T)value;
            }
        }

        public void Add(T item)
        {
            internalList.Add(item);
            ListChanged();
        }

        public int Add(object value)
        {
            VerifyValueType(value);
            Add((T)value);
            return Count - 1;
        }

        public void AddRange(IEnumerable<T> collection)
        {
            internalList.AddRange(collection);
            ListChanged();
        }

        public ReadOnlyCollection<T> AsReadOnly()
        {
            return new ReadOnlyCollection<T>(internalList);
        }

        public void Insert(int index, T item)
        {
            internalList.Insert(index, item);
            ListChanged();
        }

        public void Insert(int index, object value)
        {
            VerifyValueType(value);
            Insert(index, (T)value);
        }

        public bool Remove(T item)
        {
            if (internalList.Remove(item))
            {
                ListChanged();
                return true;
            }
            return false;
        }

        public void Remove(object value)
        {
            VerifyValueType(value);
            Remove((T)value);
        }

        public void RemoveAt(int index)
        {
            internalList.RemoveAt(index);
            ListChanged();
        }

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

        bool IList.IsReadOnly
        {
            get { return false; }
        }

        public bool IsFixedSize
        {
            get { return false; }
        }

        public void CopyTo(T[] array)
        {
            internalList.CopyTo(array);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            internalList.CopyTo(array, arrayIndex);
        }

        public void CopyTo(int index, T[] array, int arrayIndex, int count)
        {
            internalList.CopyTo(index, array, arrayIndex, count);
        }

        public void CopyTo(Array array, int index)
        {
            if ((array != null) && (array.Rank != 1))
                throw new ArgumentException("Only single dimensional arrays are supported for the requested action.");
            try
            {
                Array.Copy(internalList.ToArray(), 0, array, index, internalList.Count);
            }
            catch (ArrayTypeMismatchException)
            {
                throw new ArgumentException("Target array type is not compatible with the type of items in the collection.");
            }
        }

        public object SyncRoot
        {
            get
            {
                if (syncRoot == null)
                    System.Threading.Interlocked.CompareExchange(ref syncRoot, new object(), null);
                return syncRoot;
            }
        }

        public bool IsSynchronized
        {
            get { return false; }
        }

        public bool IsReadOnly
        {
            get { return IsReadOnly; }
        }

        private static bool IsCompatibleObject(object value)
        {
            return (value is T) || (value == null && !typeof(T).IsValueType);
        }

        private static void VerifyValueType(object value)
        {
            if (!IsCompatibleObject(value))
                throw new ArgumentException(string.Concat("The value \"", value.GetType(), "\" is not of type \"", typeof(T), "\" and cannot be used in this generic collection."));
        }

        public event EventHandler Changed;

        public void BeginUpdate()
        {
            updateCount++;
        }

        public void EndUpdate()
        {
            updateCount--;
            if ((updateCount == 0) && updated)
            {
                updated = false;
                ListChanged();
            }
        }

        private void ListChanged()
        {
            if (updateCount > 0)
            {
                updated = true;
                return;
            }
            if (Changed != null)
                Changed(this, EventArgs.Empty);
        }
    }
}