#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2010 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
#if !NO_SPECIALIZED_COLLECTIONS
using System.Collections.Specialized;
#endif
namespace Nuclex.Support.Collections {
  /// List which fires events when items are added or removed
  /// Type of items the collection manages
  public class ObservableList : IList, IList, ICollection,
#if !NO_SPECIALIZED_COLLECTIONS
    INotifyCollectionChanged,
#endif
    IObservableCollection {
    /// Raised when an item has been added to the collection
    public event EventHandler> ItemAdded;
    /// Raised when an item is removed from the collection
    public event EventHandler> ItemRemoved;
    /// Raised when the collection is about to be cleared
    /// 
    ///   This could be covered by calling ItemRemoved for each item currently
    ///   contained in the collection, but it is often simpler and more efficient
    ///   to process the clearing of the entire collection as a special operation.
    /// 
    public event EventHandler Clearing;
    /// Raised when the collection has been cleared
    public event EventHandler Cleared;
#if !NO_SPECIALIZED_COLLECTIONS
    /// Called when the collection has changed
    public event NotifyCollectionChangedEventHandler CollectionChanged;
#endif
    /// 
    ///   Initializes a new instance of the ObservableList class that is empty.
    /// 
    public ObservableList() : this(new List()) { }
    /// 
    ///   Initializes a new instance of the ObservableList class as a wrapper
    ///   for the specified list.
    /// 
    /// The list that is wrapped by the new collection.
    /// List is null
    public ObservableList(IList list) {
      this.typedList = list;
      this.objectList = list as IList; // Gah!
    }
    /// Determines the index of the specified item in the list
    /// Item whose index will be determined
    /// The index of the item in the list or -1 if not found
    public int IndexOf(TItem item) {
      return this.typedList.IndexOf(item);
    }
    /// Inserts an item into the list at the specified index
    /// Index the item will be insertted at
    /// Item that will be inserted into the list
    public void Insert(int index, TItem item) {
      this.typedList.Insert(index, item);
      OnAdded(item);
#if !NO_SPECIALIZED_COLLECTIONS
      OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
#endif
    }
    /// Removes the item at the specified index from the list
    /// Index at which the item will be removed
    public void RemoveAt(int index) {
      TItem item = this.typedList[index];
      this.typedList.RemoveAt(index);
      OnRemoved(item);
#if !NO_SPECIALIZED_COLLECTIONS
      OnCollectionChanged(NotifyCollectionChangedAction.Remove, item, index);
#endif
    }
    /// Accesses the item at the specified index in the list
    /// Index of the item that will be accessed
    /// The item at the specified index
    public TItem this[int index] {
      get { return this.typedList[index]; }
      set {
        TItem oldItem = this.typedList[index];
        this.typedList[index] = value;
        OnRemoved(oldItem);
        OnAdded(value);
#if !NO_SPECIALIZED_COLLECTIONS
        if(CollectionChanged != null) {
          CollectionChanged(
            this, new NotifyCollectionChangedEventArgs(
              NotifyCollectionChangedAction.Replace, value, oldItem, index
            )
          );
        }
#endif
      }
    }
    /// Adds an item to the end of the list
    /// Item that will be added to the list
    public void Add(TItem item) {
      this.typedList.Add(item);
      OnAdded(item);
    }
    /// Removes all items from the list
    public void Clear() {
      OnClearing();
      this.typedList.Clear();
      OnCleared();
#if !NO_SPECIALIZED_COLLECTIONS
      if(CollectionChanged != null) {
        CollectionChanged(this, CollectionResetEventArgs);
      }
#endif
    }
    /// Checks whether the list contains the specified item
    /// Item the list will be checked for
    /// True if the list contains the specified items
    public bool Contains(TItem item) {
      return this.typedList.Contains(item);
    }
    /// Copies the contents of the list into an array
    /// Array the list will be copied into
    /// 
    ///   Index in the target array where the first item will be copied to
    /// 
    public void CopyTo(TItem[] array, int arrayIndex) {
      this.typedList.CopyTo(array, arrayIndex);
    }
    /// Total number of items in the list
    public int Count {
      get { return this.typedList.Count; }
    }
    /// Whether the list is a read-only list
    public bool IsReadOnly {
      get { return this.typedList.IsReadOnly; }
    }
    /// Removes the specified item from the list
    /// Item that will be removed from the list
    /// 
    ///   True if the item was found and removed from the list, false otherwise
    /// 
    public bool Remove(TItem item) {
      int index = this.typedList.IndexOf(item);
      if(index == -1) {
        return false;
      }
      TItem removedItem = this.typedList[index];
      this.typedList.RemoveAt(index);
      OnRemoved(removedItem);
#if !NO_SPECIALIZED_COLLECTIONS
      OnCollectionChanged(NotifyCollectionChangedAction.Remove, item, index);
#endif
      return true;
    }
    /// Returns an enumerator for the items in the list
    /// An enumerator for the list's items
    public IEnumerator GetEnumerator() {
      return this.typedList.GetEnumerator();
    }
    #region IEnumerable implementation
    /// Returns an enumerator for the items in the list
    /// An enumerator for the list's items
    IEnumerator IEnumerable.GetEnumerator() {
      return this.objectList.GetEnumerator();
    }
    #endregion // IEnumerable implementation
    #region ICollection implementation
    /// Copies the contents of the list into an array
    /// Array the list will be copied into
    /// 
    ///   Index in the target array where the first item will be copied to
    /// 
    void ICollection.CopyTo(Array array, int arrayIndex) {
      this.objectList.CopyTo(array, arrayIndex);
    }
    /// Whether this list performs thread synchronization
    bool ICollection.IsSynchronized {
      get { return this.objectList.IsSynchronized; }
    }
    /// Synchronization root used by the list to synchronize threads
    object ICollection.SyncRoot {
      get { return this.objectList.SyncRoot; }
    }
    #endregion // ICollection implementation
    #region IList implementation
    /// Adds an item to the list
    /// Item that will be added to the list
    /// 
    ///   The position at which the item has been inserted or -1 if the item was not inserted
    /// 
    int IList.Add(object value) {
      int index = this.objectList.Add(value);
      TItem addedItem = this.typedList[index];
      OnAdded(addedItem);
#if !NO_SPECIALIZED_COLLECTIONS
      OnCollectionChanged(NotifyCollectionChangedAction.Add, addedItem, index);
#endif
      return index;
    }
    /// Checks whether the list contains the specified item
    /// Item the list will be checked for
    /// True if the list contains the specified items
    bool IList.Contains(object item) {
      return this.objectList.Contains(item);
    }
    /// Determines the index of the specified item in the list
    /// Item whose index will be determined
    /// The index of the item in the list or -1 if not found
    int IList.IndexOf(object item) {
      return this.objectList.IndexOf(item);
    }
    /// Inserts an item into the list at the specified index
    /// Index the item will be insertted at
    /// Item that will be inserted into the list
    void IList.Insert(int index, object item) {
      this.objectList.Insert(index, item);
      TItem addedItem = this.typedList[index];
      OnAdded(addedItem);
#if !NO_SPECIALIZED_COLLECTIONS
      OnCollectionChanged(NotifyCollectionChangedAction.Add, addedItem, index);
#endif
    }
    /// Whether the list is of a fixed size
    bool IList.IsFixedSize {
      get { return this.objectList.IsFixedSize; }
    }
    /// Removes the specified item from the list
    /// Item that will be removed from the list
    void IList.Remove(object item) {
      int index = this.objectList.IndexOf(item);
      if(index == -1) {
        return;
      }
      TItem removedItem = this.typedList[index];
      this.objectList.RemoveAt(index);
      OnRemoved(removedItem);
#if !NO_SPECIALIZED_COLLECTIONS
      OnCollectionChanged(NotifyCollectionChangedAction.Remove, removedItem, index);
#endif
    }
    /// Accesses the item at the specified index in the list
    /// Index of the item that will be accessed
    /// The item at the specified index
    object IList.this[int index] {
      get { return this.objectList[index]; }
      set {
        TItem oldItem = this.typedList[index];
        this.objectList[index] = value;
        TItem newItem = this.typedList[index];
        OnRemoved(oldItem);
        OnAdded(newItem);
#if !NO_SPECIALIZED_COLLECTIONS
        if(CollectionChanged != null) {
          CollectionChanged(
            this, new NotifyCollectionChangedEventArgs(
              NotifyCollectionChangedAction.Replace, newItem, oldItem, index
            )
          );
        }
#endif
      }
    }
    #endregion // IList implementation
#if !NO_SPECIALIZED_COLLECTIONS
    /// Fires the CollectionChanged event
    /// Type of change that has occured
    /// The item that has been added, removed or replaced
    /// Index of the changed item
    protected virtual void OnCollectionChanged(
      NotifyCollectionChangedAction action, TItem item, int index
    ) {
      if(CollectionChanged != null) {
        CollectionChanged(
          this, new NotifyCollectionChangedEventArgs(action, item, index)
        );
      }
    }
#endif
    /// Fires the 'ItemAdded' event
    /// Item that has been added to the collection
    protected virtual void OnAdded(TItem item) {
      if(ItemAdded != null)
        ItemAdded(this, new ItemEventArgs(item));
    }
    /// Fires the 'ItemRemoved' event
    /// Item that has been removed from the collection
    protected virtual void OnRemoved(TItem item) {
      if(ItemRemoved != null)
        ItemRemoved(this, new ItemEventArgs(item));
    }
    /// Fires the 'Clearing' event
    protected virtual void OnClearing() {
      if(Clearing != null)
        Clearing(this, EventArgs.Empty);
    }
    /// Fires the 'Cleared' event
    protected virtual void OnCleared() {
      if(Cleared != null)
        Cleared(this, EventArgs.Empty);
    }
#if !NO_SPECIALIZED_COLLECTIONS
    /// Fixed event args used to notify that the collection has reset
    private static readonly NotifyCollectionChangedEventArgs CollectionResetEventArgs =
      new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
#endif
    /// The wrapped list under its type-safe interface
    private IList typedList;
    /// The wrapped list under its object interface
    private IList objectList;
  }
} // namespace Nuclex.Support.Collections