namespace Caliburn.Micro.Core {
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    /// 
    /// A dictionary in which the values are weak references.
    /// 
    /// The type of keys in the dictionary.
    /// The type of values in the dictionary.
    internal class WeakValueDictionary : IDictionary
        where TValue : class {
        private readonly Dictionary inner;
        private readonly WeakReference gcSentinel = new WeakReference(new object());
        #region Cleanup handling
        private bool IsCleanupNeeded() {
            if (gcSentinel.Target == null) {
                gcSentinel.Target = new object();
                return true;
            }
            return false;
        }
        private void CleanAbandonedItems() {
            var keysToRemove = inner.Where(pair => !pair.Value.IsAlive)
                .Select(pair => pair.Key)
                .ToList();
            keysToRemove.Apply(key => inner.Remove(key));
        }
        private void CleanIfNeeded() {
            if (IsCleanupNeeded()) {
                CleanAbandonedItems();
            }
        }
        #endregion
        #region Constructors
        /// 
        /// Initializes a new instance of the  class that is empty, has the default initial capacity, and uses the default equality comparer for the key type.
        /// 
        public WeakValueDictionary() {
            inner = new Dictionary();
        }
        /// 
        /// Initializes a new instance of the  class that contains elements copied from the specified  and uses the default equality comparer for the key type.
        /// 
        /// The  whose elements are copied to the new .
        public WeakValueDictionary(IDictionary dictionary) {
            inner = new Dictionary();
            dictionary.Apply(item => inner.Add(item.Key, new WeakReference(item.Value)));
        }
        /// 
        /// Initializes a new instance of the  class that contains elements copied from the specified  and uses the specified .
        /// 
        /// The  whose elements are copied to the new .
        /// The  implementation to use when comparing keys, or null to use the default  for the type of the key.
        public WeakValueDictionary(IDictionary dictionary, IEqualityComparer comparer) {
            inner = new Dictionary(comparer);
            dictionary.Apply(item => inner.Add(item.Key, new WeakReference(item.Value)));
        }
        /// 
        /// Initializes a new instance of the  class that is empty, has the default initial capacity, and uses the specified .
        /// 
        /// The  implementation to use when comparing keys, or null to use the default  for the type of the key.
        public WeakValueDictionary(IEqualityComparer comparer) {
            inner = new Dictionary(comparer);
        }
        /// 
        /// Initializes a new instance of the  class that is empty, has the specified initial capacity, and uses the default equality comparer for the key type.
        /// 
        /// The initial number of elements that the  can contain.
        public WeakValueDictionary(int capacity) {
            inner = new Dictionary(capacity);
        }
        /// 
        /// Initializes a new instance of the  class that is empty, has the specified initial capacity, and uses the specified .
        /// 
        /// The initial number of elements that the  can contain.
        /// The  implementation to use when comparing keys, or null to use the default  for the type of the key.
        public WeakValueDictionary(int capacity, IEqualityComparer comparer) {
            inner = new Dictionary(capacity, comparer);
        }
        #endregion
        /// 
        /// Returns an enumerator that iterates through the .
        /// 
        /// The enumerator.
        public IEnumerator> GetEnumerator() {
            CleanIfNeeded();
            var enumerable = inner.Select(pair => new KeyValuePair(pair.Key, (TValue) pair.Value.Target))
                .Where(pair => pair.Value != null);
            return enumerable.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
        void ICollection>.Add(KeyValuePair item) {
            Add(item.Key, item.Value);
        }
        /// 
        /// Removes all keys and values from the .
        /// 
        public void Clear() {
            inner.Clear();
        }
        bool ICollection>.Contains(KeyValuePair item) {
            TValue value;
            if (!TryGetValue(item.Key, out value))
                return false;
            return value == item.Value;
        }
        void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) {
            if (array == null)
                throw new ArgumentNullException("array");
            if (arrayIndex < 0 || arrayIndex >= array.Length)
                throw new ArgumentOutOfRangeException("arrayIndex");
            if ((arrayIndex + Count) > array.Length)
                throw new ArgumentException(
                    "The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array.");
            this.ToArray().CopyTo(array, arrayIndex);
        }
        bool ICollection>.Remove(KeyValuePair item) {
            TValue value;
            if (!TryGetValue(item.Key, out value))
                return false;
            if (value != item.Value)
                return false;
            return inner.Remove(item.Key);
        }
        /// 
        /// Gets the number of key/value pairs contained in the .
        /// 
        /// 
        /// Since the items in the dictionary are held by weak reference, the count value
        /// cannot be relied upon to guarantee the number of objects that would be discovered via
        /// enumeration. Treat the Count as an estimate only.
        /// 
        public int Count {
            get {
                CleanIfNeeded();
                return inner.Count;
            }
        }
        bool ICollection>.IsReadOnly {
            get { return false; }
        }
        /// 
        /// Adds the specified key and value to the dictionary.
        /// 
        /// The key of the element to add.
        /// The value of the element to add. The value can be null for reference types.
        public void Add(TKey key, TValue value) {
            CleanIfNeeded();
            inner.Add(key, new WeakReference(value));
        }
        /// 
        /// Determines whether the  contains the specified key.
        /// 
        /// The key to locate in the .
        /// 
        public bool ContainsKey(TKey key) {
            TValue dummy;
            return TryGetValue(key, out dummy);
        }
        /// 
        /// Removes the value with the specified key from the .
        /// 
        /// The key of the element to remove.
        /// true if the element is successfully found and removed; otherwise, false. This method returns false if key is not found in the .
        public bool Remove(TKey key) {
            CleanIfNeeded();
            return inner.Remove(key);
        }
        /// 
        /// Gets the value associated with the specified key.
        /// 
        /// The key of the value to get.
        /// 
        /// When this method returns, contains the value associated with the specified key, 
        /// if the key is found; otherwise, the default value for the type of the value parameter.
        /// This parameter is passed uninitialized.
        /// true if the  contains an element with the specified key; otherwise, false.
        public bool TryGetValue(TKey key, out TValue value) {
            CleanIfNeeded();
            WeakReference wr;
            if (!inner.TryGetValue(key, out wr)) {
                value = null;
                return false;
            }
            var result = (TValue) wr.Target;
            if (result == null) {
                inner.Remove(key);
                value = null;
                return false;
            }
            value = result;
            return true;
        }
        /// 
        /// Gets or sets the value associated with the specified key.
        /// 
        /// The key of the value to get or set.
        /// 
        /// The value associated with the specified key. If the specified key is not found, a get operation throws a , 
        /// and a set operation creates a new element with the specified key.
        /// 
        public TValue this[TKey key] {
            get {
                TValue result;
                if (!TryGetValue(key, out result))
                    throw new KeyNotFoundException();
                return result;
            }
            set {
                CleanIfNeeded();
                inner[key] = new WeakReference(value);
            }
        }
        /// 
        /// Gets a collection containing the keys in the .
        /// 
        public ICollection Keys {
            get { return inner.Keys; }
        }
        /// 
        /// Gets a collection containing the values in the .
        /// 
        public ICollection Values {
            get { return new ValueCollection(this); }
        }
        #region Inner Types
        private sealed class ValueCollection : ICollection {
            private readonly WeakValueDictionary inner;
            public ValueCollection(WeakValueDictionary dictionary) {
                inner = dictionary;
            }
            public IEnumerator GetEnumerator() {
                return inner.Select(pair => pair.Value).GetEnumerator();
            }
            IEnumerator IEnumerable.GetEnumerator() {
                return GetEnumerator();
            }
            void ICollection.Add(TValue item) {
                throw new NotSupportedException();
            }
            void ICollection.Clear() {
                throw new NotSupportedException();
            }
            bool ICollection.Contains(TValue item) {
                return inner.Any(pair => pair.Value == item);
            }
            public void CopyTo(TValue[] array, int arrayIndex) {
                if (array == null)
                    throw new ArgumentNullException("array");
                if (arrayIndex < 0 || arrayIndex >= array.Length)
                    throw new ArgumentOutOfRangeException("arrayIndex");
                if ((arrayIndex + Count) > array.Length)
                    throw new ArgumentException(
                        "The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array.");
                this.ToArray().CopyTo(array, arrayIndex);
            }
            bool ICollection.Remove(TValue item) {
                throw new NotSupportedException();
            }
            public int Count {
                get { return inner.Count; }
            }
            bool ICollection.IsReadOnly {
                get { return true; }
            }
        }
        #endregion
    }
}