File: System.Activities.Presentation\System\Activities\Presentation\Base\Core\Internal\PropertyEditing\Model\ModelPropertyValueCollection.cs
Project: ndp\cdf\src\NetFx40\Tools\System.Activities.Presentation.csproj (System.Activities.Presentation)
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//----------------------------------------------------------------
namespace System.Activities.Presentation.Internal.PropertyEditing.Model 
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Text;
 
    using System.Activities.Presentation;
    using System.Activities.Presentation.Model;
    using System.Activities.Presentation.PropertyEditing;
 
    using System.Activities.Presentation.Internal.Properties;
    using System.Runtime;
 
    // <summary>
    // Collection of ModelPropertyValues used to model collections of ModelItems
    // </summary>
    internal class ModelPropertyValueCollection : PropertyValueCollection 
    {
 
        private List<ModelPropertyIndexer> _values;
        private bool _listenToCollectionChanges = true;
 
        // <summary>
        // Basic ctor
        // </summary>
        // <param name="parentValue">Parent PropertyValue</param>
        public ModelPropertyValueCollection(ModelPropertyValue parentValue) : base(parentValue) 
        {
            if (parentValue == null)
            {
                throw FxTrace.Exception.ArgumentNull("parentValue");
            }
 
            // Wrap each existing item in the collection in ModelPropertyEntryIndexer
            ModelItemCollection collection = this.GetRawCollection();
            if (collection != null && collection.Count > 0) 
            {
                _values = new List<ModelPropertyIndexer>();
                int i = 0;
                foreach (ModelItem item in collection) 
                {
                    _values.Add(CreateModelPropertyIndexer(item, i++));
                }
            }
 
            // Hook into the collection changed events
            if (collection != null)
            {
                collection.CollectionChanged += new NotifyCollectionChangedEventHandler(OnUnderlyingCollectionChanged);
            }
        }
 
        // <summary>
        // Gets the number of items in this collection
        // </summary>
        public override int Count 
        {
            get {
                return _values == null ? 0 : _values.Count;
            }
        }
 
        // <summary>
        // Gets the PropertyValue at the specified index
        // </summary>
        // <param name="index">Index to look up</param>
        // <returns>PropertyValue at the specified index</returns>
        public override PropertyValue this[int index] {
            get {
                VerifyExistingIndex(index);
                return _values[index].PropertyValue;
            }
        }
 
        // <summary>
        // Adds the specified object to this collection, returning its wrapped version
        // </summary>
        // <param name="value">Value to add and wrap in PropertyValue</param>
        // <returns>Wrapped value</returns>
        public override PropertyValue Add(object value) 
        {
            return Insert(value, this.Count);
        }
 
        // <summary>
        // Inserts the specified object into this collection, returning its wrapped version
        // </summary>
        // <param name="value">Value to insert and wrap</param>
        // <param name="index">Index to insert at</param>
        // <returns>Wrapped version of the inserted value</returns>
        public override PropertyValue Insert(object value, int index) 
        {
            VerifyNewIndex(index);
 
            if (_values == null)
            {
                _values = new List<ModelPropertyIndexer>();
            }
 
            ModelItem item;
            bool previouslyActive = SnoozeListeningToCollectionChanges();
            try 
            {
                item = GetRawCollection().Insert(index, value);
            }
            finally 
            {
                StartListeningToCollectionChanges(previouslyActive);
            }
 
            return InsertExternal(item, index);
        }
 
        // Same as Insert(), except it doesn't modify the raw collection, because it assumes
        // that the raw collection was already modified externally.
        private PropertyValue InsertExternal(ModelItem item, int index) 
        {
            if (_values == null)
            {
                _values = new List<ModelPropertyIndexer>();
            }
 
            PropertyValue insertedValue = InsertHelper(item, index);
 
            // Fire OnChanged event
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, insertedValue, index));
 
            return insertedValue;
        }
 
        // Updates internal structures, but does not fire any notification
        private PropertyValue InsertHelper(ModelItem item, int index) 
        {
            // Only insert the value into the collection, if it's not already there.
            // Say that an ItemAdded event comes in even though this collection has not yet been used.
            // By requesting the instance of this collection for the first time, the collection
            // gets populated correctly and fully.  Now when InsertExternal is called as a result
            // of the ItemAdded event, we would be adding the new item into the collection twice.
            // We need to prevent that from happening.
            if (_values.Count > index &&
                object.Equals(_values[index].ModelItem, item))
            {
                return _values[index].PropertyValue;
            }
 
            ModelPropertyIndexer indexer = CreateModelPropertyIndexer(item, index);
            _values.Insert(index, indexer);
 
            // Adjust all indexes of the remaining indexers in the list
            for (int i = index + 1; i < _values.Count; i++)
            {
                _values[i].Index++;
            }
 
            return indexer.PropertyValue;
        }
 
        // <summary>
        // Removes the specified PropertyValue from the collection.
        // </summary>
        // <param name="property">Property to remove</param>
        // <returns>True, if the PropertyValue was found and removed, false
        // otherwise.</returns>
        public override bool Remove(PropertyValue propertyValue) 
        {
            if (propertyValue == null)
            {
                throw FxTrace.Exception.ArgumentNull("property");
            }
 
            if (_values == null)
            {
                return false;
            }
 
            for (int i = 0; i < _values.Count; i++) 
            {
                if (_values[i].PropertyValue == propertyValue) {
                    this.RemoveAt(i);
                    return true;
                }
            }
 
            // Call to RemoveAt() already fires the right CollectionChanged events
            return false;
        }
 
        // Same as Remove, except it doesn't modify the raw collection, because it's
        // assumed that the raw collection was already modified externally.
        private bool RemoveExternal(ModelItem item) 
        {
            Fx.Assert(item != null, "item parameter should not be null");
            Fx.Assert(_values != null, "_values parameter should not be null");
 
            for (int i = 0; i < _values.Count; i++) 
            {
                if (_values[i].ModelItem == item) {
                    this.RemoveAtExternal(i);
                    return true;
                }
            }
 
            return false;
        }
 
        // <summary>
        // Removes the PropertyValue at the specified index.
        // </summary>
        // <param name="index">Index at which to remove the value.</param>
        public override void RemoveAt(int index) 
        {
            VerifyExistingIndex(index);
 
            bool previouslyActive = SnoozeListeningToCollectionChanges();
            try 
            {
                this.GetRawCollection().RemoveAt(index);
            }
            finally 
            {
                StartListeningToCollectionChanges(previouslyActive);
            }
 
            RemoveAtExternal(index);
        }
 
        // Same as RemoveAt, except it doesn't modify the raw collection, because it's
        // assumed that the raw collection was already modified externally.
        private void RemoveAtExternal(int index) 
        {
            VerifyExistingIndex(index);
            PropertyValue removedValue = RemoveAtHelper(index);
 
            // Fire OnChanged event
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedValue, index));
        }
 
        // Updates internal structures, but does not fire any notification
        private PropertyValue RemoveAtHelper(int index) 
        {
            // invalidate the ModelPropertyEntryIndexer at the index and adjust all other indexes
            ModelPropertyIndexer indexer = _values[index];
            DestroyModelPropertyIndexer(indexer);
 
            _values.RemoveAt(index);
            for (int i = index; i < _values.Count; i++)
            {
                _values[i].Index--;
            }
 
            return indexer.PropertyValue;
        }
 
        // Replaces the old ModelItem with the new one, assuming that the raw collection
        // has already been verified
        private void ReplaceExternal(ModelItem oldItem, ModelItem newItem) 
        {
            Fx.Assert(_values != null, "_values parameter should not be null");
            Fx.Assert(oldItem != null, "oldItem parameter should not be null");
            Fx.Assert(newItem != null, "newItem parameter should not be null");
 
            for (int i = 0; i < _values.Count; i++) 
            {
                if (_values[i].ModelItem == oldItem) {
                    this.RemoveAtHelper(i);
                    this.InsertHelper(newItem, i);
 
                    // Fire OnChanged event
                    this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, _values[i].PropertyValue));
                    return;
                }
            }
 
            Debug.Fail("Didn't find the expected item to remove");
        }
 
        // Clears the collection, assuming that the raw collection was already cleared externally
        private void ClearExternal() 
        {
            _values.Clear();
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
 
        // <summary>
        // Swaps the items at the specified indexes
        // </summary>
        // <param name="currentIndex">Index of item 1</param>
        // <param name="newIndex">Index of item 2</param>
        public override void SetIndex(int currentIndex, int newIndex) 
        {
 
            VerifyExistingIndex(currentIndex);
            VerifyExistingIndex(newIndex);
 
            if (currentIndex == newIndex)
            {
                return;
            }
 
            ModelItemCollection collection = this.GetRawCollection();
            ModelPropertyIndexer currentIndexer = _values[currentIndex];
            ModelPropertyIndexer newIndexer = _values[newIndex];
 
            bool previouslyActive = SnoozeListeningToCollectionChanges();
            try 
            {
                // Remove the higher index first (doesn't affect the value of the lower index)
                if (currentIndex < newIndex) 
                {
                    collection.RemoveAt(newIndex);
                    collection.RemoveAt(currentIndex);
                }
                else 
                {
                    collection.RemoveAt(currentIndex);
                    collection.RemoveAt(newIndex);
                }
 
                // Insert the lower index first (fixes the value of the higher index)
                if (currentIndex < newIndex) 
                {
                    collection.Insert(currentIndex, newIndexer.ModelItem);
                    collection.Insert(newIndex, currentIndexer.ModelItem);
                }
                else 
                {
                    collection.Insert(newIndex, currentIndexer.ModelItem);
                    collection.Insert(currentIndex, newIndexer.ModelItem);
                }
            }
            finally 
            {
                StartListeningToCollectionChanges(previouslyActive);
            }
 
            SetIndexExternal(currentIndex, newIndex);
        }
 
        // Same as SetIndex, except it doesn't modify the raw collection, because it's
        // assumed that the raw collection was already modified externally.
        private void SetIndexExternal(int currentIndex, int newIndex) 
        {
 
            if (currentIndex == newIndex)
            {
                return;
            }
 
            ModelPropertyIndexer currentIndexer = _values[currentIndex];
            ModelPropertyIndexer newIndexer = _values[newIndex];
 
            // Remove the higher index first (doesn't affect the value of the lower index)
            if (currentIndex < newIndex) 
            {
                _values.RemoveAt(newIndex);
                _values.RemoveAt(currentIndex);
            }
            else 
            {
                _values.RemoveAt(currentIndex);
                _values.RemoveAt(newIndex);
            }
 
            // Insert the lower index first (fixes the value of the higher index)
            if (currentIndex < newIndex) 
            {
                _values.Insert(currentIndex, newIndexer);
                _values.Insert(newIndex, currentIndexer);
            }
            else 
            {
                _values.Insert(newIndex, currentIndexer);
                _values.Insert(currentIndex, newIndexer);
            }
 
            newIndexer.Index = currentIndex;
            currentIndexer.Index = newIndex;
 
            // Fire OnChanged event
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(
                NotifyCollectionChangedAction.Move, currentIndexer.PropertyValue, newIndex, currentIndex));
        }
 
        // <summary>
        // Gets the underlying ModelItemCollection
        // </summary>
        // <returns>The underlying ModelItemCollection</returns>
        internal ModelItemCollection GetRawCollection() 
        {
            ModelPropertyEntry parentAsEntry = ParentValue.ParentProperty as ModelPropertyEntry;
            if (parentAsEntry != null)
            {
                return parentAsEntry.FirstModelProperty.Collection;
            }
 
            ModelPropertyIndexer parentAsIndexer = ParentValue.ParentProperty as ModelPropertyIndexer;
            if (parentAsIndexer != null) 
            {
                ModelItemCollection modelItemCollection = parentAsIndexer.ModelItem as ModelItemCollection;
 
                // If the parent is an indexer, that means we are a collection within another collection
                // and the ModelItem of the indexer is really a ModelItemCollection.
                Fx.Assert(modelItemCollection != null, "modelItemCollection should not be null");
 
                return modelItemCollection;
            }
 
            Debug.Fail("A new class was introduced that derives from PropertyEntry.  Need to update ModelPropertyValueCollection code as well.");
            return null;
        }
 
        // <summary>
        // Gets the enumerator over this collection
        // </summary>
        // <returns>Enumerator over this collection</returns>
        public override IEnumerator<PropertyValue> GetEnumerator() 
        {
            if (_values == null)
            {
                yield break;
            }
 
            foreach (ModelPropertyIndexer value in _values) 
            {
                yield return value.PropertyValue;
            }
        }
 
        // Handler for all collection changed events that happen through the model
        private void OnUnderlyingCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
        {
            if (!_listenToCollectionChanges)
            {
                return;
            }
 
            switch (e.Action) 
            {
                case NotifyCollectionChangedAction.Add:
                    int startingIndex = e.NewStartingIndex < 0 ? this.Count : e.NewStartingIndex;
                    foreach (ModelItem item in e.NewItems) 
                    {
                        this.InsertExternal(item, startingIndex++);
                    }
                    break;
 
                case NotifyCollectionChangedAction.Remove:
                    foreach (ModelItem item in e.OldItems) 
                    {
                        this.RemoveExternal(item);
                    }
                    break;
 
                case NotifyCollectionChangedAction.Move:
                    int oldIndex = e.OldStartingIndex, newIndex = e.NewStartingIndex;
                    for (int i = 0; i < e.OldItems.Count; i++) 
                    {
                        this.SetIndexExternal(oldIndex++, newIndex++);
                    }
 
                    break;
 
                case NotifyCollectionChangedAction.Replace:
                    for (int i = 0; i < e.OldItems.Count; i++) 
                    {
                        ModelItem oldItem = e.OldItems[i] as ModelItem;
                        ModelItem newItem = e.NewItems[i] as ModelItem;
                        this.ReplaceExternal(oldItem, newItem);
                    }
                    break;
 
                case NotifyCollectionChangedAction.Reset:
                    this.ClearExternal();
                    break;
            }
        }
 
        // Activates the CollectionChanged event handler
        private void StartListeningToCollectionChanges(bool previousValue) 
        {
            _listenToCollectionChanges = previousValue;
        }
 
        // Suspends the CollectionChanged event handler
        private bool SnoozeListeningToCollectionChanges() 
        {
            bool previousValue = _listenToCollectionChanges;
            _listenToCollectionChanges = false;
            return previousValue;
        }
 
        [SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
        private void VerifyExistingIndex(int index) 
        {
            if (_values == null || _values.Count <= index || index < 0)
            {
                throw FxTrace.Exception.AsError(new IndexOutOfRangeException(index.ToString(CultureInfo.InvariantCulture)));
            }
        }
 
        [SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
        private void VerifyNewIndex(int index) 
        {
            if ((_values == null && index != 0) ||
                (_values != null && _values.Count < index) ||
                index < 0)
            {
                throw FxTrace.Exception.AsError(new IndexOutOfRangeException(index.ToString(CultureInfo.InvariantCulture)));
            }
        }
 
        private ModelPropertyIndexer CreateModelPropertyIndexer(ModelItem item, int index) 
        {
            ModelPropertyIndexer indexer = new ModelPropertyIndexer(item, index, this);
            return indexer;
        }
 
        private static void DestroyModelPropertyIndexer(ModelPropertyIndexer indexer) 
        {
            indexer.Index = -1;
        }
    }
}