// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

namespace System.Data.Entity.Core.Objects
{
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data.Entity.Core.Objects.DataClasses;

    // <summary>
    // Manages a binding list constructed from an EntityCollection.
    // </summary>
    // <typeparam name="TViewElement"> Type of the elements in the binding list. </typeparam>
    // <typeparam name="TItemElement"> Type of element in the underlying EntityCollection. </typeparam>
    // <remarks>
    // The binding list is initialized from the EntityCollection,
    // and is synchronized with changes made to the EntityCollection membership.
    // This class always allows additions and removals from the binding list.
    // </remarks>
    internal sealed class ObjectViewEntityCollectionData<TViewElement, TItemElement> : IObjectViewData<TViewElement>
        where TItemElement : class
        where TViewElement : TItemElement
    {
        private readonly List<TViewElement> _bindingList;

        private readonly EntityCollection<TItemElement> _entityCollection;

        private readonly bool _canEditItems;

        // <summary>
        // <b>True</b> if item that was added to binding list but not underlying entity collection
        // is now being committed to the collection.
        // Otherwise <b>false</b>.
        // Used by CommitItemAt and OnCollectionChanged methods to coordinate addition
        // of new item to underlying entity collection.
        // </summary>
        private bool _itemCommitPending;

        // <summary>
        // Construct a new instance of the ObjectViewEntityCollectionData class using the supplied entityCollection.
        // </summary>
        // <param name="entityCollection"> EntityCollection used to populate the binding list. </param>
        internal ObjectViewEntityCollectionData(EntityCollection<TItemElement> entityCollection)
        {
            _entityCollection = entityCollection;

            _canEditItems = true;

            // Allow deferred loading to occur when initially populating the collection
            _bindingList = new List<TViewElement>(entityCollection.Count);
            foreach (TViewElement entity in entityCollection)
            {
                _bindingList.Add(entity);
            }
        }

        #region IObjectViewData<TViewElement> Members

        public IList<TViewElement> List
        {
            get { return _bindingList; }
        }

        public bool AllowNew
        {
            get { return !_entityCollection.IsReadOnly; }
        }

        public bool AllowEdit
        {
            get { return _canEditItems; }
        }

        public bool AllowRemove
        {
            get { return !_entityCollection.IsReadOnly; }
        }

        public bool FiresEventOnAdd
        {
            get { return true; }
        }

        public bool FiresEventOnRemove
        {
            get { return true; }
        }

        public bool FiresEventOnClear
        {
            get { return true; }
        }

        public void EnsureCanAddNew()
        {
            // nop
        }

        public int Add(TViewElement item, bool isAddNew)
        {
            if (isAddNew)
            {
                // Item is added to bindingList, but pending addition to entity collection.
                _bindingList.Add(item);
            }
            else
            {
                _entityCollection.Add(item);
                // OnCollectionChanged will be fired, where the binding list will be updated.
            }

            return _bindingList.Count - 1;
        }

        public void CommitItemAt(int index)
        {
            var item = _bindingList[index];

            try
            {
                _itemCommitPending = true;

                _entityCollection.Add(item);
                // OnCollectionChanged will be fired, where the binding list will be updated.
            }
            finally
            {
                _itemCommitPending = false;
            }
        }

        public void Clear()
        {
            if (0 < _bindingList.Count)
            {
                var _deletionList = new List<object>();

                foreach (object item in _bindingList)
                {
                    _deletionList.Add(item);
                }

                _entityCollection.BulkDeleteAll(_deletionList);
                // EntityCollection will fire change event which this instance will use to clean up the binding list.
            }
        }

        public bool Remove(TViewElement item, bool isCancelNew)
        {
            bool removed;

            if (isCancelNew)
            {
                // Item was previously added to binding list, but not entity collection.
                removed = _bindingList.Remove(item);
            }
            else
            {
                removed = _entityCollection.RemoveInternal(item);
                // OnCollectionChanged will be fired, where the binding list will be updated.
            }

            return removed;
        }

        public ListChangedEventArgs OnCollectionChanged(object sender, CollectionChangeEventArgs e, ObjectViewListener listener)
        {
            ListChangedEventArgs changeArgs = null;

            switch (e.Action)
            {
                case CollectionChangeAction.Remove:
                    // An Entity is being removed from entity collection, remove it from list.
                    if (e.Element is TViewElement)
                    {
                        var removedItem = (TViewElement)e.Element;

                        var oldIndex = _bindingList.IndexOf(removedItem);
                        if (oldIndex != -1)
                        {
                            _bindingList.Remove(removedItem);

                            // Unhook from events of removed entity.
                            listener.UnregisterEntityEvents(removedItem);

                            changeArgs = new ListChangedEventArgs(ListChangedType.ItemDeleted, oldIndex /* newIndex*/, -1 /* oldIndex*/);
                        }
                    }
                    break;

                case CollectionChangeAction.Add:
                    // Add the entity to our list.
                    if (e.Element is TViewElement)
                    {
                        // Do not process Add events that fire as a result of committing an item to the entity collection.
                        if (!_itemCommitPending)
                        {
                            var addedItem = (TViewElement)e.Element;

                            _bindingList.Add(addedItem);

                            // Register to its events.
                            listener.RegisterEntityEvents(addedItem);

                            changeArgs = new ListChangedEventArgs(
                                ListChangedType.ItemAdded, _bindingList.Count - 1 /* newIndex*/, -1 /* oldIndex*/);
                        }
                    }
                    break;

                case CollectionChangeAction.Refresh:
                    foreach (var entity in _bindingList)
                    {
                        listener.UnregisterEntityEvents(entity);
                    }

                    _bindingList.Clear();

                    foreach (TViewElement entity in _entityCollection.GetInternalEnumerable())
                    {
                        _bindingList.Add(entity);

                        listener.RegisterEntityEvents(entity);
                    }

                    changeArgs = new ListChangedEventArgs(ListChangedType.Reset, -1 /*newIndex*/, -1 /*oldIndex*/);
                    break;
            }

            return changeArgs;
        }

        #endregion
    }
}
