// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

//
// Description: Contains the UIElementCollection base class.
//

using MS.Internal;
using System;
using System.Collections;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Markup;

namespace System.Windows.Controls
{
    /// <summary>
    /// A UIElementCollection is a ordered collection of UIElements.
    /// </summary>
    /// <remarks>
    /// A UIElementCollection has implied context affinity. It is a violation to access
    /// the collection from a different context than that of the owning Panel.
    /// </remarks>
    /// <seealso cref="System.Windows.Media.VisualCollection" />
    public class UIElementCollection : IList
    {
        /// <summary>
        ///     The colleciton is the children collection of the visualParent. The logicalParent 
        ///     is used to do logical parenting. The flags is used to invalidate 
        ///     the resource properties in the child tree, if an Application object exists. 
        /// </summary>
        /// <param name="visualParent">The element of whom this is a children collection</param>
        /// <param name="logicalParent">The logicalParent of the elements of this collection. 
        /// if overriding Panel.CreateUIElementCollection, pass the logicalParent parameter of that method here.
        /// </param>
        public UIElementCollection(UIElement visualParent, FrameworkElement logicalParent)
        {
            if (visualParent == null)
            {
                throw new ArgumentNullException(SR.Format(SR.Panel_NoNullVisualParent, "visualParent", this.GetType()));
            }

            _visualChildren = new VisualCollection(visualParent);
            _visualParent = visualParent;
            _logicalParent = logicalParent;
        }


        /// <summary>
        /// Gets the number of elements in the collection.
        /// </summary>
        public virtual int Count
        {
            get { return _visualChildren.Count; }
        }

        /// <summary>
        /// Gets a value indicating whether access to the ICollection is synchronized (thread-safe).
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        public virtual bool IsSynchronized
        {
            get { return _visualChildren.IsSynchronized; }
        }


        /// <summary>
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        public virtual object SyncRoot
        {
            get { return _visualChildren.SyncRoot; }
        }

        /// <summary>
        /// Copies the collection into the Array.
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        public virtual void CopyTo(Array array, int index)
        {
            _visualChildren.CopyTo(array, index);
        }

        /// <summary>
        /// Strongly typed version of CopyTo
        /// Copies the collection into the Array.
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        public virtual void CopyTo(UIElement[] array, int index)
        {
            _visualChildren.CopyTo(array, index);
        }

        /// <summary>
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        public virtual int Capacity
        {
            get { return _visualChildren.Capacity; }
            set
            {
                VerifyWriteAccess();

                _visualChildren.Capacity = value;
            }
        }

        /// <summary>
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        public virtual UIElement this[int index]
        {
            get { return _visualChildren[index] as UIElement; }
            set
            {
                VerifyWriteAccess();
                ValidateElement(value);
                
                VisualCollection vc = _visualChildren;
                
                //if setting new element into slot or assigning null, 
                //remove previously hooked element from the logical tree
                if (vc[index] != value)
                {
                    UIElement e = vc[index] as UIElement;
                    if (e != null)
                        ClearLogicalParent(e);
                
                    vc[index] = value; 
  
                    SetLogicalParent(value);
                
                    _visualParent.InvalidateMeasure();
                }
            }
        }

        // Warning: this method is very dangerous because it does not prevent adding children 
        // into collection populated by generator. This may cause crashes if used incorrectly.
        // Don't call this unless you are deriving a panel that is populating the collection 
        // in cooperation with the generator
        internal void SetInternal(int index, UIElement item)
        {
            ValidateElement(item);
            
            VisualCollection vc = _visualChildren;
            
            if(vc[index] != item)
            {
                vc[index] = null; // explicitly disconnect the existing visual;
                vc[index] = item; 
            
                _visualParent.InvalidateMeasure();
            }        
        }

        
        /// <summary>
        /// Adds the element to the UIElementCollection
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        public virtual int Add(UIElement element)
        {
            VerifyWriteAccess();

            return AddInternal(element);
        }

        // Warning: this method is very dangerous because it does not prevent adding children 
        // into collection populated by generator. This may cause crashes if used incorrectly.
        // Don't call this unless you are deriving a panel that is populating the collection 
        // in cooperation with the generator
        internal int AddInternal(UIElement element)
        {
            ValidateElement(element);

            SetLogicalParent(element);
            int retVal = _visualChildren.Add(element);

            // invalidate measure on visual parent
            _visualParent.InvalidateMeasure();

            return retVal;
        }

        /// <summary>
        /// Returns the index of the element in the UIElementCollection
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        public virtual int IndexOf(UIElement element)
        {
            return _visualChildren.IndexOf(element);
        }

        /// <summary>
        /// Removes the specified element from the UIElementCollection.
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        public virtual void Remove(UIElement element)
        {
            VerifyWriteAccess();

            RemoveInternal(element);
        }

        internal void RemoveInternal(UIElement element)
        {
            _visualChildren.Remove(element);
            ClearLogicalParent(element);
            _visualParent.InvalidateMeasure();
        }

        /// <summary>
        /// Removes the specified element from the UIElementCollection.
        /// Used only by ItemsControl and by VirtualizingStackPanel
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        internal virtual void RemoveNoVerify(UIElement element)
        {
            _visualChildren.Remove(element);
        }

        /// <summary>
        /// Determines whether a element is in the UIElementCollection.
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        public virtual bool Contains(UIElement element)
        {
            return _visualChildren.Contains(element);
        }

        /// <summary>
        /// Removes all elements from the UIElementCollection.
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        public virtual void Clear()
        {
            VerifyWriteAccess();

            ClearInternal();
        }


        // Warning: this method is very dangerous because it does not prevent adding children 
        // into collection populated by generator. This may cause crashes if used incorrectly.
        // Don't call this unless you are deriving a panel that is populating the collection 
        // in cooperation with the generator
        internal void ClearInternal()
        {
            VisualCollection vc = _visualChildren;
            int cnt = vc.Count;

            if (cnt > 0)
            {
                // copy children in VisualCollection so that we can clear the visual link first, 
                // followed by the logical link
                Visual[] visuals = new Visual[cnt];
                for (int i = 0; i < cnt; i++)
                {
                    visuals[i] = vc[i];
                }

                vc.Clear();

                //disconnect from logical tree
                for (int i = 0; i < cnt; i++)
                {
                    UIElement e = visuals[i] as UIElement;
                    if (e != null)
                    {
                        ClearLogicalParent(e);
                    }
                }

                _visualParent.InvalidateMeasure();
            }
        }

        /// <summary>
        /// Inserts an element into the UIElementCollection at the specified index.
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        public virtual void Insert(int index, UIElement element)
        {
            VerifyWriteAccess();

            InsertInternal(index, element);
        }

        // Warning: this method is very dangerous because it does not prevent adding children 
        // into collection populated by generator. This may cause crashes if used incorrectly.
        // Don't call this unless you are deriving a panel that is populating the collection 
        // in cooperation with the generator
        internal void InsertInternal(int index, UIElement element)
        {
            ValidateElement(element);

            SetLogicalParent(element);
            _visualChildren.Insert(index, element);
            _visualParent.InvalidateMeasure();
        }

        /// <summary>
        /// Removes the UIElement at the specified index.
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        public virtual void RemoveAt(int index)
        {
            VerifyWriteAccess();

            VisualCollection vc = _visualChildren;

            //disconnect from logical tree
            UIElement e = vc[index] as UIElement;

            vc.RemoveAt(index);

            if (e != null)
                ClearLogicalParent(e);

            _visualParent.InvalidateMeasure();
        }


        /// <summary>
        /// Removes a range of Visuals from the VisualCollection.
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        public virtual void RemoveRange(int index, int count)
        {
            VerifyWriteAccess();

            RemoveRangeInternal(index, count);
        }

        // Warning: this method is very dangerous because it does not prevent adding children 
        // into collection populated by generator. This may cause crashes if used incorrectly.
        // Don't call this unless you are deriving a panel that is populating the collection 
        // in cooperation with the generator
        internal void RemoveRangeInternal(int index, int count)
        {
            VisualCollection vc = _visualChildren;
            int cnt = vc.Count;
            if (count > (cnt - index))
            {
                count = cnt - index;
            }

            if (count > 0)
            {
                // copy children in VisualCollection so that we can clear the visual link first, 
                // followed by the logical link
                Visual[] visuals = new Visual[count];
                int i = index;
                for (int loop = 0; loop < count; i++, loop++)
                {
                    visuals[loop] = vc[i];
                }

                vc.RemoveRange(index, count);

                //disconnect from logical tree
                for (i = 0; i < count; i++)
                {
                    UIElement e = visuals[i] as UIElement;
                    if (e != null)
                    {
                        ClearLogicalParent(e);
                    }
                }

                _visualParent.InvalidateMeasure();
            }
        }


        /// <summary>
        /// Method that forwards to VisualCollection.Move
        /// </summary>
        /// <param name="visual"></param>
        /// <param name="destination"></param>
        internal void MoveVisualChild(Visual visual, Visual destination)
        {
            _visualChildren.Move(visual, destination);
        }
		
        private UIElement Cast(object value)
        {
            if (value == null)
                throw new System.ArgumentException(SR.Format(SR.Collection_NoNull, "UIElementCollection"));

            UIElement element = value as UIElement;

            if (element == null)
                throw new System.ArgumentException(SR.Format(SR.Collection_BadType, "UIElementCollection", value.GetType().Name, "UIElement"));

            return element;
        }
		
        #region IList Members

        /// <summary>
        /// Adds an element to the UIElementCollection
        /// </summary>
        int IList.Add(object value)
        {
            return Add(Cast(value));
        }

        /// <summary>
        /// Determines whether an element is in the UIElementCollection.
        /// </summary>
        bool IList.Contains(object value)
        {
            return Contains(value as UIElement);
        }

        /// <summary>
        /// Returns the index of the element in the UIElementCollection
        /// </summary>
        int IList.IndexOf(object value)
        {
            return IndexOf(value as UIElement);
        }

        /// <summary>
        /// Inserts an element into the UIElementCollection
        /// </summary>
        void IList.Insert(int index, object value)
        {
            Insert(index, Cast(value));
        }

        /// <summary>
        /// </summary>
        bool IList.IsFixedSize
        {
            get { return false; }
        }

        /// <summary>
        /// </summary>
        bool IList.IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// Removes an element from the UIElementCollection
        /// </summary>
        void IList.Remove(object value)
        {
            Remove(value as UIElement);
        }

        /// <summary>
        /// For more details, see <see cref="System.Windows.Media.VisualCollection" />
        /// </summary>
        object IList.this[int index]
        {
            get
            {
                return this[index];
            }
            set
            {
                this[index] = Cast(value);
            }
        }

        #endregion


        // ----------------------------------------------------------------
        // IEnumerable Interface 
        // ----------------------------------------------------------------


        /// <summary>
        /// Returns an enumerator that can iterate through the collection.
        /// </summary>
        /// <returns>Enumerator that enumerates the collection in order.</returns>
        public virtual IEnumerator GetEnumerator()
        {
            return _visualChildren.GetEnumerator();
        }

        /// <summary>
        ///     This method does logical parenting of the given element.
        /// </summary>
        /// <param name="element"></param>
        protected void SetLogicalParent(UIElement element)
        {
            if (_logicalParent != null)
            {
                _logicalParent.AddLogicalChild(element);
            }
        }

        /// <summary>
        ///     This method removes logical parenting when element goes away from the collection.
        /// </summary>
        /// <param name="element"></param>
        protected void ClearLogicalParent(UIElement element)
        {
            if (_logicalParent != null)
            {
                _logicalParent.RemoveLogicalChild(element);
            }
        }

        /// <summary>
        /// Provides access to visual parent.
        /// </summary>
        internal UIElement VisualParent
        {
            get { return (_visualParent); }
        }

        // Helper function to validate element; will throw exceptions if problems are detected.
        private void ValidateElement(UIElement element)
        {
            if (element == null)
            {
                throw new ArgumentNullException(SR.Format(SR.Panel_NoNullChildren, this.GetType()));
            }
        }

        private void VerifyWriteAccess()
        {
            Panel p = _visualParent as Panel;
            if (p != null && p.IsDataBound)
            {
                throw new InvalidOperationException(SR.Panel_BoundPanel_NoChildren);
            }
        }

        internal FrameworkElement LogicalParent
        {
            get { return _logicalParent; }
        }

        private readonly VisualCollection _visualChildren;
        private readonly UIElement _visualParent;
        private readonly FrameworkElement _logicalParent;
    }
}
