// 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.


using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Controls.Primitives;   // IItemContainerGenerator
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Markup; // IAddChild, ContentPropertyAttribute
using System.Windows.Threading;
using MS.Internal;
using MS.Internal.Controls;
using MS.Internal.KnownBoxes;
using MS.Internal.PresentationFramework;
using MS.Utility;

namespace System.Windows.Controls
{
    /// <summary>
    ///     Base class for all layout panels.
    /// </summary>
    [Localizability(LocalizationCategory.Ignore)]
    [ContentProperty("Children")]
    public abstract class Panel : FrameworkElement, IAddChild
    {
        //-------------------------------------------------------------------
        //
        //  Constructors
        //
        //-------------------------------------------------------------------

        #region Constructors

        /// <summary>
        ///     Default DependencyObject constructor
        /// </summary>
        /// <remarks>
        ///     Automatic determination of current Dispatcher. Use alternative constructor
        ///     that accepts a Dispatcher for best performance.
        /// </remarks>
        protected Panel() : base()
        {
            _zConsonant = (int)ZIndexProperty.GetDefaultValue(DependencyObjectType);
        }

        #endregion

        //-------------------------------------------------------------------
        //
        //  Public Methods
        //
        //-------------------------------------------------------------------

        #region Public Methods

        /// <summary>
        ///     Fills in the background based on the Background property.
        /// </summary>
        protected override void OnRender(DrawingContext dc)
        {
            Brush background = Background;
            if (background != null)
            {
                // Using the Background brush, draw a rectangle that fills the
                // render bounds of the panel.
                Size renderSize = RenderSize;
                dc.DrawRectangle(background,
                                 null,
                                 new Rect(0.0, 0.0, renderSize.Width, renderSize.Height));
            }
        }

        ///<summary>
        /// This method is called to Add the object as a child of the Panel.  This method is used primarily
        /// by the parser.
        ///</summary>
        ///<param name="value">
        /// The object to add as a child; it must be a UIElement.
        ///</param>
        /// <ExternalAPI/>
        void IAddChild.AddChild (Object value)
        {
            ArgumentNullException.ThrowIfNull(value);
            if (IsItemsHost)
            {
                throw new InvalidOperationException(SR.Panel_BoundPanel_NoChildren);
            }

            UIElement uie = value as UIElement;

            if (uie == null)
            {
                throw new ArgumentException(SR.Format(SR.UnexpectedParameterType, value.GetType(), typeof(UIElement)), "value");
            }

            Children.Add(uie);
        }

        ///<summary>
        /// This method is called by the parser when text appears under the tag in markup.
        /// As default Panels do not support text, calling this method has no effect.
        ///</summary>
        ///<param name="text">
        /// Text to add as a child.
        ///</param>
        void IAddChild.AddText (string text)
        {
            XamlSerializerUtil.ThrowIfNonWhiteSpaceInAddText(text, this);
        }

        #endregion

        //-------------------------------------------------------------------
        //
        //  Public Properties + Avalon Dependency ID's
        //
        //-------------------------------------------------------------------

        #region Public Properties

        /// <summary>
        /// The Background property defines the brush used to fill the area between borders.
        /// </summary>
        public Brush Background
        {
            get { return (Brush) GetValue(BackgroundProperty); }
            set { SetValue(BackgroundProperty, value); }
        }

        /// <summary>
        /// DependencyProperty for <see cref="Background" /> property.
        /// </summary>
        [CommonDependencyProperty]
        public static readonly DependencyProperty BackgroundProperty =
                DependencyProperty.Register("Background",
                        typeof(Brush),
                        typeof(Panel),
                        new FrameworkPropertyMetadata((Brush)null,
                                FrameworkPropertyMetadataOptions.AffectsRender |
                                FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender));

        /// <summary>
        /// Returns enumerator to logical children.
        /// </summary>
        protected internal override IEnumerator LogicalChildren
        {
            get
            {
                if ((this.VisualChildrenCount == 0) || IsItemsHost)
                {
                    // empty panel or a panel being used as the items
                    // host has *no* logical children; give empty enumerator
                    return EmptyEnumerator.Instance;
                }

                // otherwise, its logical children is its visual children
                return this.Children.GetEnumerator();
            }
        }

        /// <summary>
        /// Returns a UIElementCollection of children for user to add/remove children manually
        /// Returns read-only collection if Panel is data-bound (no manual control of children is possible,
        /// the associated ItemsControl completely overrides children)
        /// Note: the derived Panel classes should never use this collection for
        /// internal purposes like in their MeasureOverride or ArrangeOverride.
        /// They should use InternalChildren instead, because InternalChildren
        /// is always present and either is a mirror of public Children collection (in case of Direct Panel)
        /// or is generated from data binding.
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public UIElementCollection Children
        {
            get
            {
                //When we will change from UIElementCollection to IList<UIElement>, we might
                //consider returning a wrapper IList here which coudl be read-only for mutating methods
                //while INternalChildren could be R/W even in case of Generator attached.
                return InternalChildren;
            }
        }

        /// <summary>
        /// This method is used by TypeDescriptor to determine if this property should
        /// be serialized.
        /// </summary>
        // Should serialize property Children only if it is non empty
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool ShouldSerializeChildren()
        {
            if (!IsItemsHost)
            {
                if (Children != null && Children.Count > 0)
                {
                    return true;
                }
            }

            return false;
        }


        /// <summary>
        ///     The DependencyProperty for the IsItemsHost property.
        ///     Flags:              NotDataBindable
        ///     Default Value:      false
        /// </summary>
        public static readonly DependencyProperty IsItemsHostProperty =
                DependencyProperty.Register(
                        "IsItemsHost",
                        typeof(bool),
                        typeof(Panel),
                        new FrameworkPropertyMetadata(
                                BooleanBoxes.FalseBox, // defaultValue
                                FrameworkPropertyMetadataOptions.NotDataBindable,
                                new PropertyChangedCallback(OnIsItemsHostChanged)));

        /// <summary>
        ///     IsItemsHost is set to true to indicate that the panel
        ///     is the container for UI generated for the items of an
        ///     ItemsControl.  It is typically set in a style for an ItemsControl.
        /// </summary>
        [Bindable(false), Category("Behavior")]
        public bool IsItemsHost
        {
            get { return (bool) GetValue(IsItemsHostProperty); }
            set { SetValue(IsItemsHostProperty, BooleanBoxes.Box(value)); }
        }

        private static void OnIsItemsHostChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Panel panel = (Panel) d;

            panel.OnIsItemsHostChanged((bool) e.OldValue, (bool) e.NewValue);
        }

        /// <summary>
        ///     This method is invoked when the IsItemsHost property changes.
        /// </summary>
        /// <param name="oldIsItemsHost">The old value of the IsItemsHost property.</param>
        /// <param name="newIsItemsHost">The new value of the IsItemsHost property.</param>
        protected virtual void OnIsItemsHostChanged(bool oldIsItemsHost, bool newIsItemsHost)
        {
            // GetItemsOwner will check IsItemsHost first, so we don't have
            // to check that IsItemsHost == true before calling it.
            DependencyObject parent = ItemsControl.GetItemsOwnerInternal(this);
            ItemsControl itemsControl = parent as ItemsControl;
            Panel oldItemsHost = null;

            if (itemsControl != null)
            {
                // ItemsHost should be the "root" element which has
                // IsItemsHost = true on it.  In the case of grouping,
                // IsItemsHost is true on all panels which are generating
                // content.  Thus, we care only about the panel which
                // is generating content for the ItemsControl.
                IItemContainerGenerator generator = itemsControl.ItemContainerGenerator as IItemContainerGenerator;
                if (generator != null && generator == generator.GetItemContainerGeneratorForPanel(this))
                {
                    oldItemsHost = itemsControl.ItemsHost;
                    itemsControl.ItemsHost = this;
                }
            }
            else
            {
                GroupItem groupItem = parent as GroupItem;
                if (groupItem != null)
                {
                    IItemContainerGenerator generator = groupItem.Generator as IItemContainerGenerator;
                    if (generator != null && generator == generator.GetItemContainerGeneratorForPanel(this))
                    {
                        oldItemsHost = groupItem.ItemsHost;
                        groupItem.ItemsHost = this;
                    }
                }
            }

            if (oldItemsHost != null && oldItemsHost != this)
            {
                // when changing ItemsHost panels, disconnect the old one
                oldItemsHost.VerifyBoundState();
            }

            VerifyBoundState();
        }

        /// <summary>
        ///     This is the public accessor for protected property LogicalOrientation.
        /// </summary>
        public Orientation LogicalOrientationPublic
        {
            get { return LogicalOrientation; }
        }

        /// <summary>
        ///     Orientation of the panel if its layout is in one dimension.
        /// Otherwise HasLogicalOrientation is false and LogicalOrientation should be ignored
        /// </summary>
        protected internal virtual Orientation LogicalOrientation
        {
            get { return Orientation.Vertical; }
        }

        /// <summary>
        ///     This is the public accessor for protected property HasLogicalOrientation.
        /// </summary>
        public bool HasLogicalOrientationPublic
        {
            get { return HasLogicalOrientation; }
        }

        /// <summary>
        ///     HasLogicalOrientation is true in case the panel layout is only one dimension (Stack panel).
        /// </summary>
        protected internal virtual bool HasLogicalOrientation
        {
            get { return false; }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Returns a UIElementCollection of children - added by user or generated from data binding.
        /// Panel-derived classes should use this collection for all internal purposes, including
        /// MeasureOverride/ArrangeOverride overrides.
        /// </summary>
        protected internal UIElementCollection InternalChildren
        {
            get
            {
                VerifyBoundState();

                if (IsItemsHost)
                {
                    EnsureGenerator();
                }
                else
                {
                    if (_uiElementCollection == null)
                    {
                        // First access on a regular panel
                        EnsureEmptyChildren(/* logicalParent = */ this);
                    }
                }

                return _uiElementCollection;
            }
        }

        /// <summary>
        /// Gets the Visual children count.
        /// </summary>
        protected override int VisualChildrenCount
        {
            get
            {
                if (_uiElementCollection == null)
                {
                    return 0;
                }
                else
                {
                    return _uiElementCollection.Count;
                }
            }
        }

        /// <summary>
        /// Gets the Visual child at the specified index.
        /// </summary>
        protected override Visual GetVisualChild(int index)
        {
            if (_uiElementCollection == null)
            {
                throw new ArgumentOutOfRangeException("index", index, SR.Visual_ArgumentOutOfRange);
            }

            if (IsZStateDirty) { RecomputeZState(); }
            int visualIndex = _zLut != null ? _zLut[index] : index;
            return _uiElementCollection[visualIndex];
        }

        /// <summary>
        /// Creates a new UIElementCollection. Panel-derived class can create its own version of
        /// UIElementCollection -derived class to add cached information to every child or to
        /// intercept any Add/Remove actions (for example, for incremental layout update)
        /// </summary>
        protected virtual UIElementCollection CreateUIElementCollection(FrameworkElement logicalParent)
        {
            return new UIElementCollection(this, logicalParent);
        }

        /// <summary>
        ///     The generator associated with this panel.
        /// </summary>
        internal IItemContainerGenerator Generator
        {
            get
            {
                return _itemContainerGenerator;
            }
        }

        #endregion

        #region Internal Properties

        //
        // Bool field used by VirtualizingStackPanel
        //
        internal bool VSP_IsVirtualizing
        {
            get
            {
                return GetBoolField(BoolField.IsVirtualizing);
            }

            set
            {
                SetBoolField(BoolField.IsVirtualizing, value);
            }
        }

        //
        // Bool field used by VirtualizingStackPanel
        //
        internal bool VSP_HasMeasured
        {
            get
            {
                return GetBoolField(BoolField.HasMeasured);
            }

            set
            {
                SetBoolField(BoolField.HasMeasured, value);
            }
        }


        //
        // Bool field used by VirtualizingStackPanel
        //
        internal bool VSP_MustDisableVirtualization
        {
            get
            {
                return GetBoolField(BoolField.MustDisableVirtualization);
            }

            set
            {
                SetBoolField(BoolField.MustDisableVirtualization, value);
            }
        }

        //
        // Bool field used by VirtualizingStackPanel
        //
        internal bool VSP_IsPixelBased
        {
            get
            {
                return GetBoolField(BoolField.IsPixelBased);
            }

            set
            {
                SetBoolField(BoolField.IsPixelBased, value);
            }
        }

        //
        // Bool field used by VirtualizingStackPanel
        //
        internal bool VSP_InRecyclingMode
        {
            get
            {
                return GetBoolField(BoolField.InRecyclingMode);
            }

            set
            {
                SetBoolField(BoolField.InRecyclingMode, value);
            }
        }

        //
        // Bool field used by VirtualizingStackPanel
        //
        internal bool VSP_MeasureCaches
        {
            get
            {
                return GetBoolField(BoolField.MeasureCaches);
            }

            set
            {
                SetBoolField(BoolField.MeasureCaches, value);
            }
        }

        #endregion

        #region Private Methods

        private bool VerifyBoundState()
        {
            // If the panel becomes "unbound" while attached to a generator, this
            // method detaches it and makes it really behave like "unbound."  This
            // can happen because of a style change, a theme change, etc. It returns
            // the correct "bound" state, after the dust has settled.
            //
            // This is really a workaround for a more general problem that the panel
            // needs to release resources (an event handler) when it is "out of the tree."
            // Currently, there is no good notification for when this happens.

            bool isItemsHost = (ItemsControl.GetItemsOwnerInternal(this) != null);

            if (isItemsHost)
            {
                if (_itemContainerGenerator == null)
                {
                    // Transitioning from being unbound to bound
                    ClearChildren();
                }

                return (_itemContainerGenerator != null);
            }
            else
            {
                if (_itemContainerGenerator != null)
                {
                    // Transitioning from being bound to unbound
                    DisconnectFromGenerator();
                    ClearChildren();
                }

                return false;
            }
        }

        //"actually data-bound and using generator" This is true if Panel is
        //not only marked as IsItemsHost but actually has requested Generator to
        //generate items and thus "owns" those items.
        //In this case, Children collection becomes read-only
        //Cases when it is not true include "direct" usage - IsItemsHost=false and
        //usages when panel is data-bound but derived class avoid accessing InternalChildren or Children
        //and rather calls CreateUIElementCollection and then drives Generator itself.
        internal bool IsDataBound
        {
            get
            {
                return IsItemsHost && _itemContainerGenerator != null;
            }
        }

        /// <summary> Used by subclasses to decide whether to call through a profiling stub </summary>
        internal static bool IsAboutToGenerateContent(Panel panel)
        {
            return panel.IsItemsHost && panel._itemContainerGenerator == null;
        }

        private void ConnectToGenerator()
        {
            Debug.Assert(_itemContainerGenerator == null, "Attempted to connect to a generator when Panel._itemContainerGenerator is non-null.");

            ItemsControl itemsOwner = ItemsControl.GetItemsOwner(this);
            if (itemsOwner == null)
            {
                // This can happen if IsItemsHost=true, but the panel is not nested in an ItemsControl
                throw new InvalidOperationException(SR.Panel_ItemsControlNotFound);
            }

            IItemContainerGenerator itemsOwnerGenerator = itemsOwner.ItemContainerGenerator;
            if (itemsOwnerGenerator != null)
            {
                _itemContainerGenerator = itemsOwnerGenerator.GetItemContainerGeneratorForPanel(this);
                if (_itemContainerGenerator != null)
                {
                    _itemContainerGenerator.ItemsChanged += new ItemsChangedEventHandler(OnItemsChanged);
                    ((IItemContainerGenerator)_itemContainerGenerator).RemoveAll();
                }
            }
        }

        private void DisconnectFromGenerator()
        {
            Debug.Assert(_itemContainerGenerator != null, "Attempted to disconnect from a generator when Panel._itemContainerGenerator is null.");

            _itemContainerGenerator.ItemsChanged -= new ItemsChangedEventHandler(OnItemsChanged);
            ((IItemContainerGenerator)_itemContainerGenerator).RemoveAll();
            _itemContainerGenerator = null;
        }

        private void EnsureEmptyChildren(FrameworkElement logicalParent)
        {
            if ((_uiElementCollection == null) || (_uiElementCollection.LogicalParent != logicalParent))
            {
                _uiElementCollection = CreateUIElementCollection(logicalParent);
            }
            else
            {
                ClearChildren();
            }
        }

        internal void EnsureGenerator()
        {
            Debug.Assert(IsItemsHost, "Should be invoked only on an ItemsHost panel");

            if (_itemContainerGenerator == null)
            {
                // First access on an items presenter panel
                ConnectToGenerator();

                // Children of this panel should not have their logical parent reset
                EnsureEmptyChildren(/* logicalParent = */ null);

                GenerateChildren();
            }
        }


        private void ClearChildren()
        {
            if (_itemContainerGenerator != null)
            {
                ((IItemContainerGenerator)_itemContainerGenerator).RemoveAll();
            }

            if ((_uiElementCollection != null) && (_uiElementCollection.Count > 0))
            {
                _uiElementCollection.ClearInternal();
                OnClearChildrenInternal();
            }
        }

        internal virtual void OnClearChildrenInternal()
        {
        }

        internal virtual void GenerateChildren()
        {
            // This method is typically called during layout, which suspends the dispatcher.
            // Firing an assert causes an exception "Dispatcher processing has been suspended, but messages are still being processed."
            // Besides, the asserted condition can actually arise in practice, and the
            // code responds harmlessly.
            //Debug.Assert(_itemContainerGenerator != null, "Encountered a null _itemContainerGenerator while being asked to generate children.");

            IItemContainerGenerator generator = (IItemContainerGenerator)_itemContainerGenerator;
            if (generator != null)
            {
                using (generator.StartAt(new GeneratorPosition(-1, 0), GeneratorDirection.Forward))
                {
                    UIElement child;
                    while ((child = generator.GenerateNext() as UIElement) != null)
                    {
                        _uiElementCollection.AddInternal(child);
                        generator.PrepareItemContainer(child);
                    }
                }
            }
        }

        private void OnItemsChanged(object sender, ItemsChangedEventArgs args)
        {
            if (VerifyBoundState())
            {
                Debug.Assert(_itemContainerGenerator != null, "Encountered a null _itemContainerGenerator while receiving an ItemsChanged from a generator.");

                bool affectsLayout = OnItemsChangedInternal(sender, args);

                if (affectsLayout)
                {
                    InvalidateMeasure();
                }
            }
        }

        // This method returns a bool to indicate if or not the panel layout is affected by this collection change
        internal virtual bool OnItemsChangedInternal(object sender, ItemsChangedEventArgs args)
        {
            switch (args.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    AddChildren(args.Position, args.ItemCount);
                    break;
                case NotifyCollectionChangedAction.Remove:
                    RemoveChildren(args.Position, args.ItemUICount);
                    break;
                case NotifyCollectionChangedAction.Replace:
                    ReplaceChildren(args.Position, args.ItemCount, args.ItemUICount);
                    break;
                case NotifyCollectionChangedAction.Move:
                    MoveChildren(args.OldPosition, args.Position, args.ItemUICount);
                    break;

                case NotifyCollectionChangedAction.Reset:
                    ResetChildren();
                    break;
            }

            return true;
        }

        private void AddChildren(GeneratorPosition pos, int itemCount)
        {
            Debug.Assert(_itemContainerGenerator != null, "Encountered a null _itemContainerGenerator while receiving an Add action from a generator.");

            IItemContainerGenerator generator = (IItemContainerGenerator)_itemContainerGenerator;
            using (generator.StartAt(pos, GeneratorDirection.Forward))
            {
                for (int i = 0; i < itemCount; i++)
                {
                    UIElement e = generator.GenerateNext() as UIElement;
                    if(e != null)
                    {
                        _uiElementCollection.InsertInternal(pos.Index + 1 + i, e);
                        generator.PrepareItemContainer(e);
                    }
                    else
                    {
                        _itemContainerGenerator.Verify();
                    }
                }
            }
        }

        private void RemoveChildren(GeneratorPosition pos, int containerCount)
        {
            // If anything is wrong, I think these collections should do parameter checking
            _uiElementCollection.RemoveRangeInternal(pos.Index, containerCount);
        }

        private void ReplaceChildren(GeneratorPosition pos, int itemCount, int containerCount)
        {
            Debug.Assert(itemCount == containerCount, "Panel expects Replace to affect only realized containers");
            Debug.Assert(_itemContainerGenerator != null, "Encountered a null _itemContainerGenerator while receiving an Replace action from a generator.");

            IItemContainerGenerator generator = (IItemContainerGenerator)_itemContainerGenerator;
            using (generator.StartAt(pos, GeneratorDirection.Forward, true))
            {
                for (int i = 0; i < itemCount; i++)
                {
                    bool isNewlyRealized;
                    UIElement e = generator.GenerateNext(out isNewlyRealized) as UIElement;

                    Debug.Assert(e != null && !isNewlyRealized, "Panel expects Replace to affect only realized containers");
                    if(e != null && !isNewlyRealized)
                    {
                        _uiElementCollection.SetInternal(pos.Index + i, e);
                        generator.PrepareItemContainer(e);
                    }
                    else
                    {
                        _itemContainerGenerator.Verify();
                    }
                }
            }
        }

        private void MoveChildren(GeneratorPosition fromPos, GeneratorPosition toPos, int containerCount)
        {
            if (fromPos == toPos)
                return;

            Debug.Assert(_itemContainerGenerator != null, "Encountered a null _itemContainerGenerator while receiving an Move action from a generator.");

            IItemContainerGenerator generator = (IItemContainerGenerator)_itemContainerGenerator;
            int toIndex = generator.IndexFromGeneratorPosition(toPos);

            UIElement[] elements = new UIElement[containerCount];

            for (int i = 0; i < containerCount; i++)
                elements[i] = _uiElementCollection[fromPos.Index + i];

            _uiElementCollection.RemoveRangeInternal(fromPos.Index, containerCount);

            for (int i = 0; i < containerCount; i++)
            {
                _uiElementCollection.InsertInternal(toIndex + i, elements[i]);
            }
        }

        private void ResetChildren()
        {
            EnsureEmptyChildren(null);
            GenerateChildren();
        }

        private bool GetBoolField(BoolField field)
        {
            return (_boolFieldStore & field) != 0;
        }

        private void SetBoolField(BoolField field, bool value)
        {
            if (value)
            {
                 _boolFieldStore |= field;
            }
            else
            {
                 _boolFieldStore &= (~field);
            }
        }

        //
        //  This property
        //  1. Finds the correct initial size for the _effectiveValues store on the current DependencyObject
        //  2. This is a performance optimization
        //
        internal override int EffectiveValuesInitialSize
        {
            get { return 9; }
        }

        #endregion

        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------

        #region Private Fields

        [System.Flags]
        private enum BoolField : byte
        {
            IsZStateDirty                               = 0x01,   //  "1" when Z state needs to be recomputed
            IsZStateDiverse                             = 0x02,   //  "1" when children have different ZIndexProperty values
            IsVirtualizing                              = 0x04,   //  Used by VirtualizingStackPanel
            HasMeasured                                 = 0x08,   //  Used by VirtualizingStackPanel
            IsPixelBased                                = 0x10,   //  Used by VirtualizingStackPanel
            InRecyclingMode                             = 0x20,   //  Used by VirtualizingStackPanel
            MustDisableVirtualization                   = 0x40,   //  Used by VirtualizingStackPanel
            MeasureCaches                               = 0x80,   //  Used by VirtualizingStackPanel
        }

        private UIElementCollection _uiElementCollection;
        private ItemContainerGenerator _itemContainerGenerator;
        private BoolField _boolFieldStore;

        private const int c_zDefaultValue = 0;              //  default ZIndexProperty value
        private int _zConsonant;                            //  iff (_boolFieldStore.IsZStateDiverse == 0) then this is the value all children have
        private int[] _zLut;                                //  look up table for converting from logical to visual indices

        #endregion Private Fields

        #region ZIndex Support

        /// <summary>
        /// <see cref="Visual.OnVisualChildrenChanged"/>
        /// </summary>
        protected internal override void OnVisualChildrenChanged(
            DependencyObject visualAdded,
            DependencyObject visualRemoved)
        {
            if (!IsZStateDirty)
            {
                if (IsZStateDiverse)
                {
                    //  if children have different ZIndex values,
                    //  then _zLut have to be recomputed
                    IsZStateDirty = true;
                }
                else if (visualAdded != null)
                {
                    //  if current children have consonant ZIndex values,
                    //  then _zLut have to be recomputed, only if the new
                    //  child makes z state diverse
                    int zNew = (int)visualAdded.GetValue(ZIndexProperty);
                    if (zNew != _zConsonant)
                    {
                        IsZStateDirty = true;
                    }
                }
            }

            base.OnVisualChildrenChanged(visualAdded, visualRemoved);
            // Recompute the zLut array and invalidate children rendering order.
            if (IsZStateDirty)
            {
                RecomputeZState();
                InvalidateZState();
            }
        }

        /// <summary>
        /// ZIndex property is an attached property. Panel reads it to alter the order
        /// of children rendering. Children with greater values will be rendered on top of
        /// children with lesser values.
        /// In case of two children with the same ZIndex property value, order of rendering
        /// is determined by their order in Panel.Children collection.
        /// </summary>
        public static readonly DependencyProperty ZIndexProperty =
                DependencyProperty.RegisterAttached(
                        "ZIndex",
                        typeof(int),
                        typeof(Panel),
                        new FrameworkPropertyMetadata(
                                c_zDefaultValue,
                                new PropertyChangedCallback(OnZIndexPropertyChanged)));

        /// <summary>
        /// Helper for setting ZIndex property on a UIElement.
        /// </summary>
        /// <param name="element">UIElement to set ZIndex property on.</param>
        /// <param name="value">ZIndex property value.</param>
        public static void SetZIndex(UIElement element, int value)
        {
            ArgumentNullException.ThrowIfNull(element);

            element.SetValue(ZIndexProperty, value);
        }

        /// <summary>
        /// Helper for reading ZIndex property from a UIElement.
        /// </summary>
        /// <param name="element">UIElement to read ZIndex property from.</param>
        /// <returns>ZIndex property value.</returns>
        public static int GetZIndex(UIElement element)
        {
            ArgumentNullException.ThrowIfNull(element);

            return ((int)element.GetValue(ZIndexProperty));
        }

        /// <summary>
        /// <see cref="PropertyMetadata.PropertyChangedCallback"/>
        /// </summary>
        private static void OnZIndexPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            int oldValue = (int)e.OldValue;
            int newValue = (int)e.NewValue;

            if (oldValue == newValue)
                return;

            UIElement child = d as UIElement;
            if (child == null)
                return;

            Panel panel = child.InternalVisualParent as Panel;
            if (panel == null)
                return;


            panel.InvalidateZState();
        }

        /// <summary>
        /// Sets the Z state to be dirty
        /// </summary>
        internal void InvalidateZState()
        {
            if (!IsZStateDirty
             && _uiElementCollection != null)
            {
                InvalidateZOrder();
            }

            IsZStateDirty = true;
        }

        private bool IsZStateDirty
        {
            get { return GetBoolField(BoolField.IsZStateDirty); }
            set { SetBoolField(BoolField.IsZStateDirty, value); }
        }

        private bool IsZStateDiverse
        {
            get { return GetBoolField(BoolField.IsZStateDiverse); }
            set { SetBoolField(BoolField.IsZStateDiverse, value); }
        }

        //  Helper method to update this panel's state related to children rendering order handling
        private void RecomputeZState()
        {
            int count = (_uiElementCollection != null) ? _uiElementCollection.Count : 0;
            bool isDiverse = false;
            bool lutRequired = false;
            int zIndexDefaultValue = (int)ZIndexProperty.GetDefaultValue(DependencyObjectType);
            int consonant = zIndexDefaultValue;
            System.Collections.Generic.List<Int64> stableKeyValues = null;

            if (count > 0)
            {
                if (_uiElementCollection[0] != null)
                {
                    consonant = (int)_uiElementCollection[0].GetValue(ZIndexProperty);
                }

                if (count > 1)
                {
                    stableKeyValues = new System.Collections.Generic.List<Int64>(count);
                    stableKeyValues.Add((Int64)consonant << 32);

                    int prevZ = consonant;

                    int i = 1;
                    do
                    {
                        int z = _uiElementCollection[i] != null
                            ? (int)_uiElementCollection[i].GetValue(ZIndexProperty)
                            : zIndexDefaultValue;

                        //  this way of calculating values of stableKeyValues required to
                        //  1)  workaround the fact that Array.Sort is not stable (does not preserve the original
                        //      order of elements if the keys are equal)
                        //  2)  avoid O(N^2) performance of Array.Sort, which is QuickSort, which is known to become O(N^2)
                        //      on sorting N eqial keys
                        stableKeyValues.Add(((Int64)z << 32) + i);
                        //  look-up-table is required iff z's are not monotonically increasing function of index.
                        //  in other words if stableKeyValues[i] >= stableKeyValues[i-1] then calculated look-up-table
                        //  is guaranteed to be degenerated...
                        lutRequired |= z < prevZ;
                        prevZ = z;

                        isDiverse |= (z != consonant);
                    } while (++i < count);
                }
            }

            if (lutRequired)
            {
                stableKeyValues.Sort();

                if (_zLut == null || _zLut.Length != count)
                {
                    _zLut = new int[count];
                }

                for (int i = 0; i < count; ++i)
                {
                    _zLut[i] = (int)(stableKeyValues[i] & 0xffffffff);
                }
            }
            else
            {
                _zLut = null;
            }

            IsZStateDiverse = isDiverse;
            _zConsonant = consonant;
            IsZStateDirty = false;
        }

        #endregion ZIndex Support
    }
}
