namespace Caliburn.Micro {
    using System.Collections.Specialized;
    using System.Linq;
    using System.Windows;
    using System.Windows.Interactivity;
    using Caliburn.Micro.Core;
    /// 
    /// A collection that can exist as part of a behavior.
    /// 
    /// The type of item in the attached collection.
    public class AttachedCollection : FreezableCollection, IAttachedObject
        where T : DependencyObject, IAttachedObject {
        DependencyObject associatedObject;
        /// 
        /// Creates an instance of 
        /// 
        public AttachedCollection() {
            ((INotifyCollectionChanged)this).CollectionChanged += OnCollectionChanged;
        }
        /// 
        /// Attached the collection.
        /// 
        /// The dependency object to attach the collection to.
        public void Attach(DependencyObject dependencyObject) {
            WritePreamble();
            associatedObject = dependencyObject;
            WritePostscript();
            this.Apply(x => x.Attach(associatedObject));
        }
        /// 
        /// Detaches the collection.
        /// 
        public void Detach() {
            this.Apply(x => x.Detach());
            WritePreamble();
            associatedObject = null;
            WritePostscript();
        }
        DependencyObject IAttachedObject.AssociatedObject {
            get { return associatedObject; }
        }
        /// 
        /// Called when an item is added from the collection.
        /// 
        /// The item that was added.
        protected virtual void OnItemAdded(T item) {
            if (associatedObject != null)
                item.Attach(associatedObject);
        }
        /// 
        /// Called when an item is removed from the collection.
        /// 
        /// The item that was removed.
        protected virtual void OnItemRemoved(T item) {
            if(item.AssociatedObject != null)
                item.Detach();
        }
        void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
            switch(e.Action) {
                case NotifyCollectionChangedAction.Add:
                    e.NewItems.OfType().Where(x => !Contains(x)).Apply(OnItemAdded);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    e.OldItems.OfType().Apply(OnItemRemoved);
                    break;
                case NotifyCollectionChangedAction.Replace:
                    e.OldItems.OfType().Apply(OnItemRemoved);
                    e.NewItems.OfType().Where(x => !Contains(x)).Apply(OnItemAdded);
                    break;
                case NotifyCollectionChangedAction.Reset:
                    this.Apply(OnItemRemoved);
                    this.Apply(OnItemAdded);
                    break;
            }
        }
    }
}