#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2013 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 an item is replaced in the collection
    public event EventHandler> ItemReplaced;
    /// 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, index);
    }
    /// 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, index);
    }
    /// 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;
        OnReplaced(oldItem, value, index);
      }
    }
    /// 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, this.typedList.Count - 1);
    }
    /// Removes all items from the list
    public void Clear() {
      OnClearing();
      this.typedList.Clear();
      OnCleared();
    }
    /// 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, index);
      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, index);
      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, index);
    }
    /// 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, index);
    }
    /// 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];
        OnReplaced(oldItem, newItem, index);
      }
    }
    #endregion // IList implementation
    /// Fires the 'ItemAdded' event
    /// Item that has been added to the collection
    /// Index of the added item
    protected virtual void OnAdded(TItem item, int index) {
      if(ItemAdded != null) {
        ItemAdded(this, new ItemEventArgs(item));
      }
#if !NO_SPECIALIZED_COLLECTIONS
      if(CollectionChanged != null) {
        CollectionChanged(
          this,
          new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)
        );
      }
#endif
    }
    /// Fires the 'ItemRemoved' event
    /// Item that has been removed from the collection
    /// Index the item has been removed from
    protected virtual void OnRemoved(TItem item, int index) {
      if(ItemRemoved != null) {
        ItemRemoved(this, new ItemEventArgs(item));
      }
#if !NO_SPECIALIZED_COLLECTIONS
      if(CollectionChanged != null) {
        CollectionChanged(
          this,
          new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)
        );
      }
#endif
    }
    /// Fires the 'ItemReplaced' event
    /// Item that has been replaced
    /// New item the original item was replaced with
    /// Index of the replaced item
    protected virtual void OnReplaced(TItem oldItem, TItem newItem, int index) {
      if(ItemReplaced != null) {
        ItemReplaced(this, new ItemReplaceEventArgs(oldItem, newItem));
      }
#if !NO_SPECIALIZED_COLLECTIONS
      if(CollectionChanged != null) {
        CollectionChanged(
          this,
          new NotifyCollectionChangedEventArgs(
            NotifyCollectionChangedAction.Replace, newItem, oldItem, index
          )
        );
      }
#endif
    }
    /// 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
      if(CollectionChanged != null) {
        CollectionChanged(this, Constants.NotifyCollectionResetEventArgs);
      }
#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