using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Csla.Properties;
namespace Csla
{
  /// 
  /// This is the base class from which most business collections
  /// or lists will be derived.
  /// 
  /// Type of the business object being defined.
  /// Type of the child objects contained in the list.
  [System.Diagnostics.CodeAnalysis.SuppressMessage(
    "Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]
  [Serializable()]
  public abstract class BusinessListBase :
      Core.ExtendedBindingList,
      Core.IEditableCollection, ICloneable, Core.ISavable, Core.IParent
    where T : BusinessListBase
    where C : Core.IEditableBusinessObject
  {
    #region Constructors
    /// 
    /// Creates an instance of the object.
    /// 
    protected BusinessListBase()
    {
      Initialize();
    }
    #endregion
    #region Initialize
    /// 
    /// Override this method to set up event handlers so user
    /// code in a partial class can respond to events raised by
    /// generated code.
    /// 
    protected virtual void Initialize()
    { /* allows subclass to initialize events before any other activity occurs */ }
    #endregion
    #region IsDirty, IsValid
    /// 
    /// Gets a value indicating whether this object's data has been changed.
    /// 
    public bool IsDirty
    {
      get
      {
        // any non-new deletions make us dirty
        foreach (C item in DeletedList)
          if (!item.IsNew)
            return true;
        // run through all the child objects
        // and if any are dirty then then
        // collection is dirty
        foreach (C child in this)
          if (child.IsDirty)
            return true;
        return false;
      }
    }
    /// 
    /// Gets a value indicating whether this object is currently in
    /// a valid state (has no broken validation rules).
    /// 
    public virtual bool IsValid
    {
      get
      {
        // run through all the child objects
        // and if any are invalid then the
        // collection is invalid
        foreach (C child in this)
          if (!child.IsValid)
            return false;
        return true;
      }
    }
    #endregion
    #region Begin/Cancel/ApplyEdit
    /// 
    /// Starts a nested edit on the object.
    /// 
    /// 
    /// 
    /// When this method is called the object takes a snapshot of
    /// its current state (the values of its variables). This snapshot
    /// can be restored by calling 
    /// or committed by calling .
    /// 
    /// This is a nested operation. Each call to BeginEdit adds a new
    /// snapshot of the object's state to a stack. You should ensure that 
    /// for each call to BeginEdit there is a corresponding call to either 
    /// CancelEdit or ApplyEdit to remove that snapshot from the stack.
    /// 
    /// See Chapters 2 and 3 for details on n-level undo and state stacking.
    /// 
    /// This method triggers the copying of all child object states.
    /// 
    /// 
    public void BeginEdit()
    {
      if (this.IsChild)
        throw new NotSupportedException(Resources.NoBeginEditChildException);
      CopyState();
    }
    /// 
    /// Cancels the current edit process, restoring the object's state to
    /// its previous values.
    /// 
    /// 
    /// Calling this method causes the most recently taken snapshot of the 
    /// object's state to be restored. This resets the object's values
    /// to the point of the last 
    /// call.
    /// 
    /// This method triggers an undo in all child objects.
    /// 
    /// 
    public void CancelEdit()
    {
      if (this.IsChild)
        throw new NotSupportedException(Resources.NoCancelEditChildException);
      UndoChanges();
    }
    /// 
    /// Commits the current edit process.
    /// 
    /// 
    /// Calling this method causes the most recently taken snapshot of the 
    /// object's state to be discarded, thus committing any changes made
    /// to the object's state since the last 
    ///  call.
    /// 
    /// This method triggers an 
    ///  in all child objects.
    /// 
    /// 
    public void ApplyEdit()
    {
      if (this.IsChild)
        throw new NotSupportedException(Resources.NoApplyEditChildException);
      AcceptChanges();
    }
    void Core.IParent.ApplyEditChild(Core.IEditableBusinessObject child)
    {
      EditChildComplete(child);
    }
    /// 
    /// Override this method to be notified when a child object's
    ///  method has
    /// completed.
    /// 
    /// The child object that was edited.
    protected virtual void EditChildComplete(Core.IEditableBusinessObject child)
    {
      // do nothing, we don't really care
      // when a child has its edits applied
    }
    #endregion
    #region N-level undo
    void Core.IUndoableObject.CopyState()
    {
      CopyState();
    }
    void Core.IUndoableObject.UndoChanges()
    {
      UndoChanges();
    }
    void Core.IUndoableObject.AcceptChanges()
    {
      AcceptChanges();
    }
    private void CopyState()
    {
      // we are going a level deeper in editing
      _editLevel += 1;
      // cascade the call to all child objects
      foreach (C child in this)
        child.CopyState();
      // cascade the call to all deleted child objects
      foreach (C child in DeletedList)
        child.CopyState();
    }
    private void UndoChanges()
    {
      C child;
      // we are coming up one edit level
      _editLevel -= 1;
      if (_editLevel < 0) _editLevel = 0;
      // Cancel edit on all current items
      for (int index = Count - 1; index >= 0; index--)
      {
        child = this[index];
        child.UndoChanges();
        // if item is below its point of addition, remove
        if (child.EditLevelAdded > _editLevel)
        {
          bool oldAllowRemove = this.AllowRemove;
          try
          {
            this.AllowRemove = true;
            RemoveAt(index);
          }
          finally
          {
            this.AllowRemove = oldAllowRemove;
          }
        }
      }
      // cancel edit on all deleted items
      for (int index = DeletedList.Count - 1; index >= 0; index--)
      {
        child = DeletedList[index];
        child.UndoChanges();
        if (child.EditLevelAdded > _editLevel)
        {
          // if item is below its point of addition, remove
          DeletedList.RemoveAt(index);
        }
        else
        {
          // if item is no longer deleted move back to main list
          if (!child.IsDeleted) UnDeleteChild(child);
        }
      }
    }
    private void AcceptChanges()
    {
      // we are coming up one edit level
      _editLevel -= 1;
      if (_editLevel < 0) _editLevel = 0;
      // cascade the call to all child objects
      foreach (C child in this)
      {
        child.AcceptChanges();
        // if item is below its point of addition, lower point of addition
        if (child.EditLevelAdded > _editLevel) child.EditLevelAdded = _editLevel;
      }
      // cascade the call to all deleted child objects
      for (int index = DeletedList.Count - 1; index >= 0; index--)
      {
        C child = DeletedList[index];
        child.AcceptChanges();
        // if item is below its point of addition, remove
        if (child.EditLevelAdded > _editLevel)
          DeletedList.RemoveAt(index);
      }
    }
    #endregion
    #region Delete and Undelete child
    private List _deletedList;
    /// 
    /// A collection containing all child objects marked
    /// for deletion.
    /// 
    [System.Diagnostics.CodeAnalysis.SuppressMessage(
      "Microsoft.Design", "CA1002:DoNotExposeGenericLists")]
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected List DeletedList
    {
      get 
      { 
        if (_deletedList == null)
          _deletedList = new List();
        return _deletedList; 
      }
    }
    private void DeleteChild(C child)
    {
      // mark the object as deleted
      child.DeleteChild();
      // and add it to the deleted collection for storage
      DeletedList.Add(child);
    }
    private void UnDeleteChild(C child)
    {
      // we are inserting an _existing_ object so
      // we need to preserve the object's editleveladded value
      // because it will be changed by the normal add process
      int saveLevel = child.EditLevelAdded;
      Add(child);
      child.EditLevelAdded = saveLevel;
      // since the object is no longer deleted, remove it from
      // the deleted collection
      DeletedList.Remove(child);
    }
    /// 
    /// Returns  if the internal deleted list
    /// contains the specified child object.
    /// 
    /// Child object to check.
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    public bool ContainsDeleted(C item)
    {
      return DeletedList.Contains(item);
    }
    #endregion
    #region Insert, Remove, Clear
    /// 
    /// This method is called by a child object when it
    /// wants to be removed from the collection.
    /// 
    /// The child object to remove.
    void Core.IEditableCollection.RemoveChild(Csla.Core.IEditableBusinessObject child)
    {
      Remove((C)child);
    }
    /// 
    /// This method is called by a child object when it
    /// wants to be removed from the collection.
    /// 
    /// The child object to remove.
    void Core.IParent.RemoveChild(Csla.Core.IEditableBusinessObject child)
    {
      Remove((C)child);
    }
    /// 
    /// Sets the edit level of the child object as it is added.
    /// 
    /// Index of the item to insert.
    /// Item to insert.
    protected override void InsertItem(int index, C item)
    {
      // when an object is inserted we assume it is
      // a new object and so the edit level when it was
      // added must be set
      item.EditLevelAdded = _editLevel;
      item.SetParent(this);
      base.InsertItem(index, item);
    }
    /// 
    /// Marks the child object for deletion and moves it to
    /// the collection of deleted objects.
    /// 
    /// Index of the item to remove.
    protected override void RemoveItem(int index)
    {
      // when an object is 'removed' it is really
      // being deleted, so do the deletion work
      C child = this[index];
      base.RemoveItem(index);
      CopyToDeletedList(child);
    }
    private void CopyToDeletedList(C child)
    {
      DeleteChild(child);
      INotifyPropertyChanged c = child as INotifyPropertyChanged;
      if (c != null)
        c.PropertyChanged -= new PropertyChangedEventHandler(Child_PropertyChanged);
    }
    /// 
    /// Clears the collection, moving all active
    /// items to the deleted list.
    /// 
    protected override void ClearItems()
    {
      while (base.Count > 0) RemoveItem(0);
      base.ClearItems();
    }
    /// 
    /// Replaces the item at the specified index with
    /// the specified item, first moving the original
    /// item to the deleted list.
    /// 
    /// The zero-based index of the item to replace.
    /// 
    /// The new value for the item at the specified index. 
    /// The value can be null for reference types.
    /// 
    /// 
    protected override void SetItem(int index, C item)
    {
      // copy the original object to the deleted list,
      // marking as deleted, etc.
      C child = default(C);
      if (!ReferenceEquals(this[index], item))
        child = this[index];
      // replace the original object with this new
      // object
      base.SetItem(index, item);
      if (child != null)
        CopyToDeletedList(child);
    }
    #endregion
    #region Edit level tracking
    // keep track of how many edit levels we have
    private int _editLevel;
    /// 
    /// Returns the current edit level of the object.
    /// 
    [EditorBrowsable(EditorBrowsableState.Never)]
    protected int EditLevel
    {
      get { return _editLevel; }
    }
    #endregion
    #region IsChild
    [NotUndoable()]
    private bool _isChild = false;
    /// 
    /// Indicates whether this collection object is a child object.
    /// 
    /// True if this is a child object.
    protected bool IsChild
    {
      get { return _isChild; }
    }
    /// 
    /// Marks the object as being a child object.
    /// 
    /// 
    /// 
    /// By default all business objects are 'parent' objects. This means
    /// that they can be directly retrieved and updated into the database.
    /// 
    /// We often also need child objects. These are objects which are contained
    /// within other objects. For instance, a parent Invoice object will contain
    /// child LineItem objects.
    /// 
    /// To create a child object, the MarkAsChild method must be called as the
    /// object is created. Please see Chapter 7 for details on the use of the
    /// MarkAsChild method.
    /// 
    /// 
    protected void MarkAsChild()
    {
      _isChild = true;
    }
    #endregion
    #region ICloneable
    object ICloneable.Clone()
    {
      return GetClone();
    }
    /// 
    /// Creates a clone of the object.
    /// 
    /// A new object containing the exact data of the original object.
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected virtual object GetClone()
    {
      return Core.ObjectCloner.Clone(this);
    }
    /// 
    /// Creates a clone of the object.
    /// 
    /// A new object containing the exact data of the original object.
    public T Clone()
    {
      return (T)GetClone();
    }
    #endregion
    #region Cascade Child events
    private void Child_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
      for (int index = 0; index < Count; index++)
      {
        if (ReferenceEquals(this[index], sender))
        {
          OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, index));
          return;
        }
      }
    }
    #endregion
    #region Serialization Notification
    [OnDeserialized()]
    private void OnDeserializedHandler(StreamingContext context)
    {
      OnDeserialized(context);
      foreach (Core.IEditableBusinessObject child in this)
      {
        child.SetParent(this);
        INotifyPropertyChanged c = child as INotifyPropertyChanged;
        if (c != null)
          c.PropertyChanged += new PropertyChangedEventHandler(Child_PropertyChanged);
      }
      foreach (Core.IEditableBusinessObject child in DeletedList)
        child.SetParent(this);
    }
    /// 
    /// This method is called on a newly deserialized object
    /// after deserialization is complete.
    /// 
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected virtual void OnDeserialized(StreamingContext context)
    {
      // do nothing - this is here so a subclass
      // could override if needed
    }
    #endregion
    #region Data Access
    /// 
    /// Saves the object to the database.
    /// 
    /// 
    /// 
    /// Calling this method starts the save operation, causing the all child
    /// objects to be inserted, updated or deleted within the database based on the
    /// each object's current state.
    /// 
    /// All this is contingent on . If
    /// this value is , no data operation occurs. 
    /// It is also contingent on . If this value is 
    ///  an exception will be thrown to 
    /// indicate that the UI attempted to save an invalid object.
    /// 
    /// It is important to note that this method returns a new version of the
    /// business collection that contains any data updated during the save operation.
    /// You MUST update all object references to use this new version of the
    /// business collection in order to have access to the correct object data.
    /// 
    /// You can override this method to add your own custom behaviors to the save
    /// operation. For instance, you may add some security checks to make sure
    /// the user can save the object. If all security checks pass, you would then
    /// invoke the base Save method via MyBase.Save().
    /// 
    /// 
    /// A new object containing the saved values.
    public virtual T Save()
    {
      T result;
      if (this.IsChild)
        throw new NotSupportedException(Resources.NoSaveChildException);
      if (_editLevel > 0)
        throw new Validation.ValidationException(Resources.NoSaveEditingException);
      if (!IsValid)
        throw new Validation.ValidationException(Resources.NoSaveInvalidException);
      if (IsDirty)
        result = (T)DataPortal.Update(this);
      else
        result = (T)this;
      OnSaved(result);
      return result;
    }
    /// 
    /// Override this method to load a new business object with default
    /// values from the database.
    /// 
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")]
    protected virtual void DataPortal_Create()
    {
      throw new NotSupportedException(Resources.CreateNotSupportedException);
    }
    /// 
    /// Override this method to allow retrieval of an existing business
    /// object based on data in the database.
    /// 
    /// An object containing criteria values to identify the object.
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")]
    protected virtual void DataPortal_Fetch(object criteria)
    {
      throw new NotSupportedException(Resources.FetchNotSupportedException);
    }
    /// 
    /// Override this method to allow update of a business
    /// object.
    /// 
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")]
    protected virtual void DataPortal_Update()
    {
      throw new NotSupportedException(Resources.UpdateNotSupportedException);
    }
    /// 
    /// Override this method to allow immediate deletion of a business object.
    /// 
    /// An object containing criteria values to identify the object.
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")]
    protected virtual void DataPortal_Delete(object criteria)
    {
      throw new NotSupportedException(Resources.DeleteNotSupportedException);
    }
    /// 
    /// Called by the server-side DataPortal prior to calling the 
    /// requested DataPortal_xyz method.
    /// 
    /// The DataPortalContext object passed to the DataPortal.
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")]
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected virtual void DataPortal_OnDataPortalInvoke(DataPortalEventArgs e)
    {
    }
    /// 
    /// Called by the server-side DataPortal after calling the 
    /// requested DataPortal_xyz method.
    /// 
    /// The DataPortalContext object passed to the DataPortal.
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")]
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected virtual void DataPortal_OnDataPortalInvokeComplete(DataPortalEventArgs e)
    {
    }
    /// 
    /// Called by the server-side DataPortal if an exception
    /// occurs during data access.
    /// 
    /// The DataPortalContext object passed to the DataPortal.
    /// The Exception thrown during data access.
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", MessageId = "Member")]
    [EditorBrowsable(EditorBrowsableState.Advanced)]
    protected virtual void DataPortal_OnDataPortalException(DataPortalEventArgs e, Exception ex)
    {
    }
    #endregion
    #region ISavable Members
    object Csla.Core.ISavable.Save()
    {
      return Save();
    }
    [NonSerialized()]
    [NotUndoable]
    private EventHandler _nonSerializableSavedHandlers;
    [NotUndoable]
    private EventHandler _serializableSavedHandlers;
    /// 
    /// Event raised when an object has been saved.
    /// 
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
      "CA1062:ValidateArgumentsOfPublicMethods")]
    public event EventHandler Saved
    {
      add
      {
        if (value.Method.IsPublic &&
           (value.Method.DeclaringType.IsSerializable ||
            value.Method.IsStatic))
          _serializableSavedHandlers = (EventHandler)
            System.Delegate.Combine(_serializableSavedHandlers, value);
        else
          _nonSerializableSavedHandlers = (EventHandler)
            System.Delegate.Combine(_nonSerializableSavedHandlers, value);
      }
      remove
      {
        if (value.Method.IsPublic &&
           (value.Method.DeclaringType.IsSerializable ||
            value.Method.IsStatic))
          _serializableSavedHandlers = (EventHandler)
            System.Delegate.Remove(_serializableSavedHandlers, value);
        else
          _nonSerializableSavedHandlers = (EventHandler)
            System.Delegate.Remove(_nonSerializableSavedHandlers, value);
      }
    }
    /// 
    /// Raises the  event, indicating that the
    /// object has been saved, and providing a reference
    /// to the new object instance.
    /// 
    /// The new object instance.
    [System.ComponentModel.EditorBrowsable(EditorBrowsableState.Advanced)]
    protected void OnSaved(T newObject)
    {
      Csla.Core.SavedEventArgs args = new Csla.Core.SavedEventArgs(newObject);
      if (_nonSerializableSavedHandlers != null)
        _nonSerializableSavedHandlers.Invoke(this, args);
      if (_serializableSavedHandlers != null)
        _serializableSavedHandlers.Invoke(this, args);
    }
    #endregion
  }
}