//------------------------------------------------------------------------------
// <copyright file="ModelItemDictionary.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

namespace System.Activities.Presentation.Model {

    using System.Activities.Presentation.Internal.Properties;
    using System.Activities.Presentation;
    using System.Runtime;

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Windows;

    /// <summary>
    /// ModelItemDictionary derives from ModelItem and implements support for a 
    /// dictionary of key/value pairs.  Both the keys and values are items. 
    /// 
    /// ModelItemDictionary defines an attached property "Key", which is adds 
    /// to all items contained in the dictionary.  The data type of the Key 
    /// property is "ModelItem" and it is marked as non-browsable and 
    /// non-serializable.
    ///
    /// In addition to the Key property, ModelItemDictionary also returns an 
    /// Item property from its properties collection just like ModelItemCollection.  
    /// ModelItemDictionary reuses the ModelProperty defined on ModelItemCollection.  
    /// The value returned is an enumeration of the values in the dictionary.  
    /// The Source property of all items in the dictionary refers to this Item 
    /// property.
    /// </summary>
    public abstract class ModelItemDictionary : ModelItem, IDictionary<ModelItem, ModelItem>, IDictionary, INotifyCollectionChanged {

        /// <summary>
        /// Creates a new ModelItemDictionary.
        /// </summary>
        protected ModelItemDictionary() { }

        /// <summary>
        /// Returns the item at the given key.  Sets the item at the given 
        /// key to the given value.  If there is no item for the given key 
        /// this returns null because null is not a valid item.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException">if the dictionary is read only and you set a new value.</exception>
        /// <exception cref="KeyNotFoundException">if the given key is not in the dictionary.</exception>
        [SuppressMessage("Microsoft.Design", "CA1043:UseIntegralOrStringArgumentForIndexers")]
        public abstract ModelItem this[ModelItem key] { get; set; }

        /// <summary>
        /// Returns the item at the given key.  Sets the item at the given 
        /// key to the given value.  If there is no item for the given key 
        /// this returns null because null is not a valid item.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException">if the dictionary is read only and you set a new value.</exception>
        /// <exception cref="KeyNotFoundException">if the given key is not in the dictionary.</exception>
        [SuppressMessage("Microsoft.Design", "CA1043:UseIntegralOrStringArgumentForIndexers")]
        public abstract ModelItem this[object key] { get; set; }

        /// <summary>
        /// Returns the count of items in the dictionary.
        /// </summary>
        public abstract int Count { get; }

        /// <summary>
        /// Returns true if the dictionary is a fixed size.  
        /// The default implementation returns true if the
        /// dictionary is read only.
        /// </summary>
        protected virtual bool IsFixedSize {
            get { return IsReadOnly; }
        }

        /// <summary>
        /// Returns true if the dictionary cannot be modified.
        /// </summary>
        public abstract bool IsReadOnly { get; }

        /// <summary>
        /// Protected access to ICollection.IsSynchronized.
        /// </summary>
        protected virtual bool IsSynchronized {
            get { return false; }
        }

        /// <summary>
        /// Returns the keys of the collection.  The keys are guaranteed to be in 
        /// the same order as the values.  The resulting collection is read-only.
        /// </summary>
        [Fx.Tag.KnownXamlExternalAttribute]
        public abstract ICollection<ModelItem> Keys { get; }

        /// <summary>
        /// Protected access to the SyncRoot object used to synchronize
        /// this collection.  The default value returns "this".
        /// </summary>
        protected virtual object SyncRoot {
            get { return this; }
        }

        /// <summary>
        /// Returns the values of the collection.  The values are guaranteed to be 
        /// in the same order as the keys.  The resulting collection is read-only.
        /// </summary>
        [Fx.Tag.KnownXamlExternalAttribute]
        public abstract ICollection<ModelItem> Values { get; }

        /// <summary>
        /// This event is raised when the contents of this collection change.
        /// </summary>
        public abstract event NotifyCollectionChangedEventHandler CollectionChanged;

        /// <summary>
        /// Adds the item to the dictionary under the given key. 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <exception cref="InvalidOperationException">if the dictionary is read only.</exception>
        public abstract void Add(ModelItem key, ModelItem value);

        /// <summary>
        /// Adds the value to the dictionary under the given key.  This
        /// will wrap the key and value in an item.  It returns the item
        /// representing the key.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns>an item representing the key in the dictionary</returns>
        /// <exception cref="InvalidOperationException">if the dictionary is read only.</exception>
        public abstract ModelItem Add(object key, object value);

        /// <summary>
        /// Clears the contents of the dictionary.
        /// </summary>
        /// <exception cref="InvalidOperationException">if the dictionary is read only.</exception>
        public abstract void Clear();

        /// <summary>
        /// Copies into the given array.
        /// </summary>
        /// <param name="array"></param>
        /// <param name="arrayIndex"></param>
        protected virtual void CopyTo(KeyValuePair<ModelItem, ModelItem>[] array, int arrayIndex) {
            foreach (KeyValuePair<ModelItem, ModelItem> kv in this) {
                array[arrayIndex++] = kv;
            }
        }

        /// <summary>
        /// Returns true if the dictionary contains the given key value pair.
        /// </summary>
        protected virtual bool Contains(KeyValuePair<ModelItem, ModelItem> item) {
            ModelItem value;
            return TryGetValue(item.Key, out value) && value == item.Value;
        }

        /// <summary>
        /// Returns true if the dictionary contains the given key.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public abstract bool ContainsKey(ModelItem key);

        /// <summary>
        /// Returns true if the dictionary contains the given key.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public abstract bool ContainsKey(object key);

        //
        // Helper method that verifies that objects can be upcast to
        // the correct type.
        //
        private static ModelItem ConvertType(object value) {
            try {
                return (ModelItem)value;
            }
            catch (InvalidCastException) {
                throw FxTrace.Exception.AsError(new ArgumentException(
                    string.Format(CultureInfo.CurrentCulture,
                        Resources.Error_ArgIncorrectType,
                        "value", typeof(ModelItem).FullName)));
            }
        }

        /// <summary>
        /// Returns an enumerator for the items in the dictionary.
        /// </summary>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
        public abstract IEnumerator<KeyValuePair<ModelItem, ModelItem>> GetEnumerator();

        /// <summary>
        /// Removes the item from the dictionary.  This does nothing if the item 
        /// does not exist in the collection.
        /// </summary>
        /// <param name="key"></param>
        /// <exception cref="InvalidOperationException">if the dictionary is read only.</exception>
        public abstract bool Remove(ModelItem key);

        /// <summary>
        /// Removes the item from the dictionary.  This does nothing if the item 
        /// does not exist in the collection.
        /// </summary>
        /// <param name="key"></param>
        /// <exception cref="InvalidOperationException">if the dictionary is read only.</exception>
        public abstract bool Remove(object key);

        /// <summary>
        /// Retrieves the value for the given key, or returns false if the 
        /// value cant be found.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public abstract bool TryGetValue(ModelItem key, out ModelItem value);

        /// <summary>
        /// Retrieves the value for the given key, or returns false if the 
        /// value cant be found.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public abstract bool TryGetValue(object key, out ModelItem value);

        /// <summary>
        /// ModelItemDictionary provides an attached property "Key", which is adds to 
        /// all items contained in the dictionary.  The data type of the Key 
        /// property is "ModelItem".
        /// </summary>
        public static readonly DependencyProperty KeyProperty = DependencyProperty.RegisterAttachedReadOnly(
            "Key",
            typeof(ModelItem),
            typeof(ModelItemDictionary), null).DependencyProperty;

        // IDictionary API is synthesized on top of the generic API.

        #region IDictionary Members

        /// <summary>
        /// IDictionary Implementation maps back to public API.
        /// </summary>
        void IDictionary.Add(object key, object value) {
            Add(key, value);
        }

        /// <summary>
        /// IDictionary Implementation maps back to public API.
        /// </summary>
        void IDictionary.Clear() {
            Clear();
        }

        /// <summary>
        /// IDictionary Implementation maps back to public API.
        /// </summary>
        [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
        bool IDictionary.Contains(object key) {
            return ContainsKey(key);
        }

        /// <summary>
        /// IDictionary Implementation maps back to public API.
        /// </summary>
        IDictionaryEnumerator IDictionary.GetEnumerator() {
            return new DictionaryEnumerator(GetEnumerator());
        }

        /// <summary>
        /// IDictionary Implementation maps back to public API.
        /// </summary>
        bool IDictionary.IsFixedSize {
            get { return IsFixedSize; }
        }

        /// <summary>
        /// IDictionary Implementation maps back to public API.
        /// </summary>
        bool IDictionary.IsReadOnly {
            get { return IsReadOnly; }
        }

        /// <summary>
        /// IDictionary Implementation maps back to public API.
        /// </summary>
        ICollection IDictionary.Keys
        {
            get {
                object[] keys = new object[Count];
                int idx = 0;
                foreach (KeyValuePair<ModelItem, ModelItem> kv in this) {
                    keys[idx++] = kv.Key;
                }
                return keys;
            }
        }

        /// <summary>
        /// IDictionary Implementation maps back to public API.
        /// </summary>
        void IDictionary.Remove(object key) {
            Remove(key);
        }

        /// <summary>
        /// IDictionary Implementation maps back to public API.
        /// </summary>
        ICollection IDictionary.Values
        {
            get {
                object[] values = new object[Count];
                int idx = 0;
                foreach (KeyValuePair<ModelItem, ModelItem> kv in this) {
                    values[idx++] = kv.Value;
                }
                return values;
            }
        }

        /// <summary>
        /// IDictionary Implementation maps back to public API.
        /// </summary>
        object IDictionary.this[object key] {
            get { return this[ConvertType(key)]; }
            set { this[ConvertType(key)] = ConvertType(value); }
        }

        #endregion

        #region ICollection Members

        /// <summary>
        /// ICollection Implementation maps back to public API.
        /// </summary>
        void ICollection.CopyTo(Array array, int index) {

            if (Count > 0) {
                int len = array.GetLength(0);
                if (index >= len) {
                    throw FxTrace.Exception.AsError(new ArgumentException(
                        string.Format(CultureInfo.CurrentCulture,
                        Resources.Error_InvalidArrayIndex, index)));
                }

                KeyValuePair<ModelItem, ModelItem>[] typedArray = new KeyValuePair<ModelItem, ModelItem>[len];

                CopyTo(typedArray, index);

                for (; index < typedArray.Length; index++) {
                    array.SetValue(typedArray[index], index);
                }
            }
        }

        /// <summary>
        /// ICollection Implementation maps back to public API.
        /// </summary>
        int ICollection.Count {
            get { return Count; }
        }

        /// <summary>
        /// ICollection Implementation maps back to public API.
        /// </summary>
        bool ICollection.IsSynchronized {
            get { return IsSynchronized; }
        }

        /// <summary>
        /// ICollection Implementation maps back to public API.
        /// </summary>
        object ICollection.SyncRoot {
            get { return SyncRoot; }
        }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// IEnumerator Implementation maps back to public API.
        /// </summary>
        IEnumerator IEnumerable.GetEnumerator() {
            foreach (KeyValuePair<ModelItem, ModelItem> kv in this) {
                yield return kv;
            }
        }

        #endregion

        #region ICollection<KeyValuePair<ModelItem,ModelItem>> Members

        void ICollection<KeyValuePair<ModelItem, ModelItem>>.Add(KeyValuePair<ModelItem, ModelItem> item) {
            Add(item.Key, item.Value);
        }

        bool ICollection<KeyValuePair<ModelItem, ModelItem>>.Contains(KeyValuePair<ModelItem, ModelItem> item) {
            return Contains(item);
        }

        void ICollection<KeyValuePair<ModelItem, ModelItem>>.CopyTo(KeyValuePair<ModelItem, ModelItem>[] array, int arrayIndex) {
            if (arrayIndex >= array.Length) {
                throw FxTrace.Exception.AsError(new ArgumentException(
                    string.Format(CultureInfo.CurrentCulture,
                    Resources.Error_InvalidArrayIndex, arrayIndex)));
            }

            CopyTo(array, arrayIndex);
        }

        bool ICollection<KeyValuePair<ModelItem, ModelItem>>.Remove(KeyValuePair<ModelItem, ModelItem> item) {
            ModelItem value;
            if (TryGetValue(item.Key, out value) && value == item.Value) {
                return Remove(item.Key);
            }

            return false;
        }

        #endregion

        //
        // This is a simple struct that implements a dictionary enumerator,
        // since this isn't supported in the iterator pattern.
        //
        private struct DictionaryEnumerator : IDictionaryEnumerator {

            private IEnumerator<KeyValuePair<ModelItem, ModelItem>> _real;

            internal DictionaryEnumerator(IEnumerator<KeyValuePair<ModelItem, ModelItem>> real) {
                _real = real;
            }

            #region IDictionaryEnumerator Members

            public DictionaryEntry Entry {
                get { return new DictionaryEntry(_real.Current.Key, _real.Current.Value); }
            }

            public object Key {
                get { return _real.Current.Key; }
            }

            public object Value {
                get { return _real.Current.Value; }
            }

            #endregion

            #region IEnumerator Members

            public object Current {
                get { return Entry; }
            }

            public bool MoveNext() {
                return _real.MoveNext();
            }

            public void Reset() {
                _real.Reset();
            }

            #endregion
        }
    }
}
