// 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.Collections.Generic;           // List<T>
using System.Collections.Specialized;       // NotifyCollectionChangedAction
using System.ComponentModel;                // DesignerSerializationVisibility
using System.Windows.Automation.Peers;      // AutomationPeer
using System.Windows.Controls.Primitives;   // GridViewRowPresenterBase
using System.Windows.Data;                  // Binding
using System.Windows.Input;                 // MouseEventArgs
using System.Windows.Media;                 // SolidColorBrush
using System.Diagnostics;

using MS.Internal;                          // Helper


namespace System.Windows.Controls
{
    /// <summary>
    /// GridViewHeaderRowPresenter is used within the style to denote the headers place
    /// in GridView's visual tree
    /// </summary>
    [StyleTypedProperty(Property = "ColumnHeaderContainerStyle", StyleTargetType = typeof(GridViewColumnHeader))]
    public class GridViewHeaderRowPresenter : GridViewRowPresenterBase
    {
        //-------------------------------------------------------------------
        //
        //  Constructors
        //
        //-------------------------------------------------------------------

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

        //-------------------------------------------------------------------
        //
        //  Public Properties
        //
        //-------------------------------------------------------------------

        #region Public Properties

        #region ColumnHeaderContainerStyle

        /// <summary>
        /// ColumnHeaderContainerStyleProperty DependencyProperty
        /// </summary>
        public static readonly DependencyProperty ColumnHeaderContainerStyleProperty =
                GridView.ColumnHeaderContainerStyleProperty.AddOwner(
                    typeof(GridViewHeaderRowPresenter),
                    new FrameworkPropertyMetadata(
                        new PropertyChangedCallback(PropertyChanged))
                    );

        /// <summary>
        /// header container's style
        /// </summary>
        public Style ColumnHeaderContainerStyle
        {
            get { return (Style)GetValue(ColumnHeaderContainerStyleProperty); }
            set { SetValue(ColumnHeaderContainerStyleProperty, value); }
        }

        #endregion // ColumnHeaderContainerStyle

        #region ColumnHeaderTemplate

        /// <summary>
        /// ColumnHeaderTemplate DependencyProperty
        /// </summary>
        public static readonly DependencyProperty ColumnHeaderTemplateProperty =
            GridView.ColumnHeaderTemplateProperty.AddOwner(
                typeof(GridViewHeaderRowPresenter),
                new FrameworkPropertyMetadata(
                    new PropertyChangedCallback(PropertyChanged))
            );

        /// <summary>
        /// column header template
        /// </summary>
        public DataTemplate ColumnHeaderTemplate
        {
            get { return (DataTemplate)GetValue(ColumnHeaderTemplateProperty); }
            set { SetValue(ColumnHeaderTemplateProperty, value); }
        }

        #endregion  ColumnHeaderTemplate

        #region ColumnHeaderTemplateSelector

        /// <summary>
        /// ColumnHeaderTemplateSelector DependencyProperty
        /// </summary>
        public static readonly DependencyProperty ColumnHeaderTemplateSelectorProperty =
            GridView.ColumnHeaderTemplateSelectorProperty.AddOwner(
                typeof(GridViewHeaderRowPresenter),
                new FrameworkPropertyMetadata(
                    new PropertyChangedCallback(PropertyChanged))
            );

        /// <summary>
        /// header template selector
        /// </summary>
        /// <remarks>
        ///     This property is ignored if <seealso cref="ColumnHeaderTemplate"/> is set.
        /// </remarks>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public DataTemplateSelector ColumnHeaderTemplateSelector
        {
            get { return (DataTemplateSelector)GetValue(ColumnHeaderTemplateSelectorProperty); }
            set { SetValue(ColumnHeaderTemplateSelectorProperty, value); }
        }

        #endregion ColumnHeaderTemplateSelector

        #region ColumnHeaderStringFormat

        /// <summary>
        /// ColumnHeaderStringFormat DependencyProperty
        /// </summary>
        public static readonly DependencyProperty ColumnHeaderStringFormatProperty =
            GridView.ColumnHeaderStringFormatProperty.AddOwner(
                typeof(GridViewHeaderRowPresenter),
                new FrameworkPropertyMetadata(
                    new PropertyChangedCallback(PropertyChanged))
            );

        /// <summary>
        /// header template selector
        /// </summary>
        /// <remarks>
        ///     This property is ignored if <seealso cref="ColumnHeaderTemplate"/> is set.
        /// </remarks>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public String ColumnHeaderStringFormat
        {
            get { return (String)GetValue(ColumnHeaderStringFormatProperty); }
            set { SetValue(ColumnHeaderStringFormatProperty, value); }
        }

        #endregion ColumnHeaderStringFormat

        #region AllowsColumnReorder

        /// <summary>
        /// AllowsColumnReorder DependencyProperty
        /// </summary>
        public static readonly DependencyProperty AllowsColumnReorderProperty =
            GridView.AllowsColumnReorderProperty.AddOwner(
                typeof(GridViewHeaderRowPresenter)
            );

        /// <summary>
        /// Allow column re-order or not
        /// </summary>
        public bool AllowsColumnReorder
        {
            get { return (bool)GetValue(AllowsColumnReorderProperty); }
            set { SetValue(AllowsColumnReorderProperty, value); }
        }

        #endregion  AllowsColumnReorder

        #region ColumnHeaderContextMenu

        /// <summary>
        /// ColumnHeaderContextMenu DependencyProperty
        /// </summary>
        public static readonly DependencyProperty ColumnHeaderContextMenuProperty =
            GridView.ColumnHeaderContextMenuProperty.AddOwner(
                typeof(GridViewHeaderRowPresenter),
                new FrameworkPropertyMetadata(
                    new PropertyChangedCallback(PropertyChanged))
            );

        /// <summary>
        /// ColumnHeaderContextMenu
        /// </summary>
        public ContextMenu ColumnHeaderContextMenu
        {
            get { return (ContextMenu)GetValue(ColumnHeaderContextMenuProperty); }
            set { SetValue(ColumnHeaderContextMenuProperty, value); }
        }

        #endregion  ColumnHeaderContextMenu

        #region ColumnHeaderToolTip

        /// <summary>
        /// ColumnHeaderToolTip DependencyProperty
        /// </summary>
        public static readonly DependencyProperty ColumnHeaderToolTipProperty =
            GridView.ColumnHeaderToolTipProperty.AddOwner(
                typeof(GridViewHeaderRowPresenter),
                new FrameworkPropertyMetadata(
                    new PropertyChangedCallback(PropertyChanged))
            );

        /// <summary>
        /// ColumnHeaderToolTip
        /// </summary>
        public object ColumnHeaderToolTip
        {
            get { return GetValue(ColumnHeaderToolTipProperty); }
            set { SetValue(ColumnHeaderToolTipProperty, value); }
        }

        private static void PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            GridViewHeaderRowPresenter presenter = (GridViewHeaderRowPresenter)d;

            if (e.Property == ColumnHeaderTemplateProperty || e.Property == ColumnHeaderTemplateSelectorProperty)
            {
                // Check to prevent Template and TemplateSelector at the same time
                Helper.CheckTemplateAndTemplateSelector("GridViewHeaderRowPresenter", ColumnHeaderTemplateProperty, ColumnHeaderTemplateSelectorProperty, presenter);
            }

            presenter.UpdateAllHeaders(e.Property);
        }

        #endregion  ColumnHeaderToolTip

        #endregion

        //-------------------------------------------------------------------
        //
        // Protected Methods
        //
        //-------------------------------------------------------------------

        #region Protected Methods

        /// <summary>
        /// Override of <seealso cref="FrameworkElement.MeasureOverride" />.
        /// </summary>
        /// <param name="constraint">Constraint size is an "upper limit" that the return value should not exceed.</param>
        /// <returns>The GridViewHeaderRowPresenter's desired size.</returns>
        protected override Size MeasureOverride(Size constraint)
        {
            GridViewColumnCollection columns = Columns;

            UIElementCollection children = InternalChildren;

            double maxHeight = 0.0;           // Max height of children.
            double accumulatedWidth = 0.0;    // Total width consumed by children.
            double constraintHeight = constraint.Height;
            bool desiredWidthListEnsured = false;

            if (columns != null)
            {
                // Measure working headers
                for (int i = 0; i < columns.Count; ++i)
                {
                    UIElement child = children[GetVisualIndex(i)];
                    if (child == null) { continue; }

                    double childConstraintWidth = Math.Max(0.0, constraint.Width - accumulatedWidth);

                    GridViewColumn column = columns[i];

                    if (column.State == ColumnMeasureState.Init)
                    {
                        if (!desiredWidthListEnsured)
                        {
                            EnsureDesiredWidthList();
                            LayoutUpdated += new EventHandler(OnLayoutUpdated);
                            desiredWidthListEnsured = true;
                        }

                        child.Measure(new Size(childConstraintWidth, constraintHeight));

                        DesiredWidthList[column.ActualIndex] = column.EnsureWidth(child.DesiredSize.Width);

                        accumulatedWidth += column.DesiredWidth;
                    }
                    else if (column.State == ColumnMeasureState.Headered
                        || column.State == ColumnMeasureState.Data)
                    {
                        childConstraintWidth = Math.Min(childConstraintWidth, column.DesiredWidth);

                        child.Measure(new Size(childConstraintWidth, constraintHeight));

                        accumulatedWidth += column.DesiredWidth;
                    }
                    else // ColumnMeasureState.SpecificWidth
                    {
                        childConstraintWidth = Math.Min(childConstraintWidth, column.Width);

                        child.Measure(new Size(childConstraintWidth, constraintHeight));

                        accumulatedWidth += column.Width;
                    }

                    maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
                }
            }

            // Measure padding header
            Debug.Assert(_paddingHeader != null, "padding header is null");
            _paddingHeader.Measure(new Size(0.0, constraintHeight));
            maxHeight = Math.Max(maxHeight, _paddingHeader.DesiredSize.Height);

            // reserve space for padding header next to the last column
            accumulatedWidth += c_PaddingHeaderMinWidth;

            // Measure indicator & floating header in re-ordering
            if (_isHeaderDragging)
            {
                Debug.Assert(_indicator != null, "_indicator is null");
                Debug.Assert(_floatingHeader != null, "_floatingHeader is null");

                // Measure indicator
                _indicator.Measure(constraint);

                // Measure floating header
                _floatingHeader.Measure(constraint);
            }

            return (new Size(accumulatedWidth, maxHeight));
        }

        /// <summary>
        /// GridViewHeaderRowPresenter computes the position of its children inside each child's Margin and calls Arrange
        /// on each child.
        /// </summary>
        /// <param name="arrangeSize">Size the GridViewHeaderRowPresenter will assume.</param>
        protected override Size ArrangeOverride(Size arrangeSize)
        {
            GridViewColumnCollection columns = Columns;

            UIElementCollection children = InternalChildren;

            double accumulatedWidth = 0.0;
            double remainingWidth = arrangeSize.Width;
            Rect rect;

            HeadersPositionList.Clear();

            if (columns != null)
            {
                // Arrange working headers
                for (int i = 0; i < columns.Count; ++i)
                {
                    UIElement child = children[GetVisualIndex(i)];
                    if (child == null) { continue; }

                    GridViewColumn column = columns[i];

                    // has a given value or 'auto'
                    double childArrangeWidth = Math.Min(remainingWidth, ((column.State == ColumnMeasureState.SpecificWidth) ? column.Width : column.DesiredWidth));

                    // calculate the header rect
                    rect = new Rect(accumulatedWidth, 0.0, childArrangeWidth, arrangeSize.Height);

                    // arrange header
                    child.Arrange(rect);

                    //Store rect in HeadersPositionList as i-th column position
                    HeadersPositionList.Add(rect);

                    remainingWidth -= childArrangeWidth;
                    accumulatedWidth += childArrangeWidth;
                }

                // check width to hide previous header's right half gripper, from the first working header to padding header
                // only happens after column delete, insert, move
                if (_isColumnChangedOrCreated)
                {
                    for (int i = 0; i < columns.Count; ++i)
                    {
                        GridViewColumnHeader header = children[GetVisualIndex(i)] as GridViewColumnHeader;

                        header.CheckWidthForPreviousHeaderGripper();
                    }

                    _paddingHeader.CheckWidthForPreviousHeaderGripper();

                    _isColumnChangedOrCreated = false;
                }
            }

            // Arrange padding header
            Debug.Assert(_paddingHeader != null, "padding header is null");
            rect = new Rect(accumulatedWidth, 0.0, Math.Max(remainingWidth, 0.0), arrangeSize.Height);
            _paddingHeader.Arrange(rect);
            HeadersPositionList.Add(rect);

            // if re-order started, arrange floating header & indicator
            if (_isHeaderDragging)
            {
                _floatingHeader.Arrange(new Rect(new Point(_currentPos.X - _relativeStartPos.X, 0), HeadersPositionList[_startColumnIndex].Size));

                Point pos = FindPositionByIndex(_desColumnIndex);
                _indicator.Arrange(new Rect(pos, new Size(_indicator.DesiredSize.Width, arrangeSize.Height)));
            }

            return arrangeSize;
        }

        /// <summary>
        /// This is the method that responds to the MouseButtonEvent event.
        /// </summary>
        /// <param name="e">Event arguments</param>
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            GridViewColumnHeader header = e.Source as GridViewColumnHeader;

            if (header != null && AllowsColumnReorder)
            {
                PrepareHeaderDrag(header, e.GetPosition(this), e.GetPosition(header), false);

                MakeParentItemsControlGotFocus();
            }

            e.Handled = true;

            base.OnMouseLeftButtonDown(e);
        }

        /// <summary>
        /// This is the method that responds to the MouseButtonEvent event.
        /// </summary>
        /// <param name="e">Event arguments</param>
        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            // Important to clean the prepare dragging state
            _prepareDragging = false;

            if (_isHeaderDragging)
            {
                FinishHeaderDrag(false);
            }

            e.Handled = true;

            base.OnMouseLeftButtonUp(e);
        }

        /// <summary>
        /// This is the method that responds to the MouseEvent event.
        /// </summary>
        /// <param name="e">Event arguments</param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (e.LeftButton == MouseButtonState.Pressed)
            {
                // if prepare re-order or already re-order
                if (_prepareDragging)
                {
                    Debug.Assert(_draggingSrcHeader != null, "_draggingSrcHeader is null");

                    _currentPos = e.GetPosition(this);
                    _desColumnIndex = FindIndexByPosition(_currentPos, true);

                    if (!_isHeaderDragging)
                    {
                        // Re-order begins only if horizontal move exceeds threshold
                        if (CheckStartHeaderDrag(_currentPos, _startPos))
                        {
                            // header dragging start
                            StartHeaderDrag();

                            // need to measure indicator because floating header is updated
                            InvalidateMeasure();
                        }
                    }
                    else // NOTE: Not-Dragging/Dragging should be divided into two stages in MouseMove
                    {
                        // Check floating & indicator visibility
                        // Display floating header if vertical move not exceeds header.Height * 2
                        bool isDisplayingFloatingHeader = IsMousePositionValid(_floatingHeader, _currentPos, 2.0);

                        // floating header and indicator are visibile/invisible at the same time
                        _indicator.Visibility = _floatingHeader.Visibility = isDisplayingFloatingHeader ? Visibility.Visible : Visibility.Hidden;

                        InvalidateArrange();
                    }
                }
            }

            e.Handled = true;
        }

        /// <summary>
        /// Cancel header dragging if we lost capture
        /// </summary>
        /// <param name="e"></param>
        protected override void OnLostMouseCapture(MouseEventArgs e)
        {
            base.OnLostMouseCapture(e);

            // OnLostMouseCapture is invoked before OnMouseLeftButtonUp, so we need to distinguish
            // the cause of capture lose
            //      if LeftButton is pressed when lost mouse capture, we treat it as cancel
            //      Because GridViewHeaderRowPresenter never capture Mouse (GridViewColumnHeader did this),
            //      the Mouse.Captured is always null
            if (e.LeftButton == MouseButtonState.Pressed && _isHeaderDragging)
            {
                FinishHeaderDrag(true);
            }

            // Important to clean the prepare dragging state
            _prepareDragging = false;
        }

        #endregion Protected Methods

        //-------------------------------------------------------------------
        //
        // Internal Methods
        //
        //-------------------------------------------------------------------

        #region Internal Methods

        /// <summary>
        /// create the headers tree
        /// </summary>
        internal override void OnPreApplyTemplate()
        {
            //  +-- GridViewHeaderRowPresenter ----------------------------+
            //  |                                                          |
            //  |  +- Header1 ---+ +- Header2 ---+ +- PaddingHeader -+     |
            //  |  |         +--------+      +--------+            |       |
            //  |  |         +Gripper +      +Gripper +            |  ...  |
            //  |  |         +--------+      +--------+            |       |
            //  |  +-------------+ +-------------+ +---------------+       |
            //  +----------------------------------------------------------+

            //
            //  Given a column collection with 3 columns
            //
            //      { A, B, C },
            //
            //  Visually, GridViewHeaderRowPresenter will them present as:
            //
            //      { A, B, C, Padding }
            //
            //  If in Reorder-Mode, there will be 2 more be visible:
            //
            //      { A, [Indctr],     B,      C,    Padding    }
            //      {           [-- Float --]                 }
            //
            //  And internally the visual child collection is in order of:
            //
            //      { Padding, C, B, A, Indicator, Float}
            //
            //  Method GetVisualIndex() is for coverting a Columns based
            //  index to visual child collection based index.
            //
            //  E.g., Columns based index of column A is 0, while
            //  and it lives 3rd in the visual collection, so
            //
            //      GetVisualIndex(0) = 3.
            //

            base.OnPreApplyTemplate();

            if (NeedUpdateVisualTree)
            {
                // build the whole collection from draft.

                // IMPORTANT!
                // The correct sequence to build the VisualTree in Z-order:
                // 1. Padding header
                // 2. The working Column header (if any)
                // 3. Indicator
                // 4. Floating header
                //

                UIElementCollection children = InternalChildren;
                GridViewColumnCollection columns = Columns;

                // renew ScrollViewer, ScrollChanged event, ItemsControl and KeyDown event
                RenewEvents();

                if (children.Count == 0)
                {
                    // Build and add the padding header, even if no GridViewColumn is defined
                    AddPaddingColumnHeader();

                    // Create and add indicator
                    AddIndicator();

                    // Create and add floating header
                    AddFloatingHeader(null);
                }
                else if (children.Count > 3)
                {
                    // clear column headers left from last view.
                    int count = children.Count - 3;
                    for (int i = 0; i < count; i++)
                    {
                        RemoveHeader(null, 1);
                    }
                }

                UpdatePaddingHeader(_paddingHeader);

                //
                // Build the column header.
                // The interesting thing is headers must be built from right to left,
                // in order to make the left header's gripper overlay the right header
                //
                if (columns != null)
                {
                    int visualIndex = 1;

                    for (int columnIndex = columns.Count - 1; columnIndex >= 0; columnIndex--)
                    {
                        GridViewColumn column = columns[columnIndex];

                        GridViewColumnHeader header = CreateAndInsertHeader(column, visualIndex++);
                    }
                }

                // Link headers
                BuildHeaderLinks();

                NeedUpdateVisualTree = false;

                _isColumnChangedOrCreated = true;
            }
        }

        /// <summary>
        /// Override column's PropertyChanged event handler. Update  correspondent
        /// property if change is of Width / Header /
        /// HeaderContainerStyle / Template / Selector.
        /// </summary>
        internal override void OnColumnPropertyChanged(GridViewColumn column, string propertyName)
        {
            Debug.Assert(column != null);
            if (column.ActualIndex >= 0)
            {
                GridViewColumnHeader header = FindHeaderByColumn(column);
                if (header != null)
                {
                    if (GridViewColumn.WidthProperty.Name.Equals(propertyName)
                        || GridViewColumn.c_ActualWidthName.Equals(propertyName))
                    {
                        InvalidateMeasure();
                    }
                    else if (GridViewColumn.HeaderProperty.Name.Equals(propertyName))
                    {
                        if (!header.IsInternalGenerated /* the old header is its own container */
                            || column.Header is GridViewColumnHeader /* the new header is its own container */)
                        {
                            // keep the header index in Children collection
                            int i = InternalChildren.IndexOf(header);

                            // Remove the old header
                            RemoveHeader(header, -1);

                            // Insert a (the) new header
                            GridViewColumnHeader newHeader = CreateAndInsertHeader(column, i);

                            // Link headers
                            BuildHeaderLinks();
                        }
                        else
                        {
                            UpdateHeaderContent(header);
                        }
                    }
                    else
                    {
                        DependencyProperty columnDP = GetColumnDPFromName(propertyName);

                        if (columnDP != null)
                        {
                            UpdateHeaderProperty(header, columnDP);
                        }
                    }
                }
            }
        }

        internal override void OnColumnCollectionChanged(GridViewColumnCollectionChangedEventArgs e)
        {
            base.OnColumnCollectionChanged(e);

            int index;
            GridViewColumnHeader header;
            UIElementCollection children = InternalChildren;
            GridViewColumn column;

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Move:
                    int start = GetVisualIndex(e.OldStartingIndex);
                    int end = GetVisualIndex(e.NewStartingIndex);

                    header = (GridViewColumnHeader)children[start];
                    children.RemoveAt(start);
                    children.InsertInternal(end, header);

                    break;

                case NotifyCollectionChangedAction.Add:
                    index = GetVisualIndex(e.NewStartingIndex);
                    column = (GridViewColumn)(e.NewItems[0]);

                    CreateAndInsertHeader(column, index + 1); // index + 1 because visual index is reversed from column index

                    break;

                case NotifyCollectionChangedAction.Remove:
                    RemoveHeader(null, GetVisualIndex(e.OldStartingIndex));

                    break;

                case NotifyCollectionChangedAction.Replace:
                    index = GetVisualIndex(e.OldStartingIndex);
                    RemoveHeader(null, index);

                    column = (GridViewColumn)(e.NewItems[0]);
                    CreateAndInsertHeader(column, index);

                    break;

                case NotifyCollectionChangedAction.Reset:
                    int count = e.ClearedColumns.Count;
                    for (int i = 0; i < count; i++)
                    {
                        RemoveHeader(null, 1);
                    }

                    break;
            }

            // Link headers
            BuildHeaderLinks();
            _isColumnChangedOrCreated = true;
        }

        // Make the parent got focus if it's ItemsControl
        // make this method internal, so GVCH can call it when header is invoked through access key
        internal void MakeParentItemsControlGotFocus()
        {
            if (_itemsControl != null && !_itemsControl.IsKeyboardFocusWithin)
            {
                // send focus to item.
                ListBox parent = _itemsControl as ListBox;
                if (parent != null && parent.LastActionItem != null)
                {
                    parent.LastActionItem.Focus();
                }
                else
                {
                    _itemsControl.Focus();
                }
            }
        }

        /// <summary>
        /// Refresh a dp of a header. Column is the 1st priority source, GridView 2nd.
        /// </summary>
        /// <param name="header">the header to update</param>
        /// <param name="property">the DP which trigger this update</param>
        internal void UpdateHeaderProperty(GridViewColumnHeader header, DependencyProperty property)
        {
            DependencyProperty gvDP, columnDP, headerDP;
            GetMatchingDPs(property, out gvDP, out columnDP, out headerDP);

            UpdateHeaderProperty(header, headerDP, columnDP, gvDP);
        }

        #endregion Internal Methods

        //-------------------------------------------------------------------
        //
        //  Accessibility
        //
        //-------------------------------------------------------------------

        #region Accessibility

        /// <summary>
        /// Creates AutomationPeer (<see cref="UIElement.OnCreateAutomationPeer"/>)
        /// </summary>
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return new GridViewHeaderRowPresenterAutomationPeer(this);
        }

        #endregion

        //-------------------------------------------------------------------
        //
        // Private Methods
        //
        //-------------------------------------------------------------------

        #region Private Methods

        private void OnLayoutUpdated(object sender, EventArgs e)
        {
            bool desiredWidthChanged = false; // whether the shared minimum width has been changed since last layout

            GridViewColumnCollection columns = Columns;
            if (columns != null)
            {
                foreach (GridViewColumn column in columns)
                {
                    if ((column.State != ColumnMeasureState.SpecificWidth))
                    {
                        if (column.State == ColumnMeasureState.Init)
                        {
                            column.State = ColumnMeasureState.Headered;
                        }

                        if (DesiredWidthList == null || column.ActualIndex >= DesiredWidthList.Count)
                        {
                            // How can this happen?
                            // Between the last measure was called and this update is called, there can be a
                            // change done to the ColumnCollection and result in DesiredWidthList out of sync
                            // with the columnn collection. What can we do is end this call asap and the next
                            // measure will fix it.
                            desiredWidthChanged = true;
                            break;
                        }

                        if (!DoubleUtil.AreClose(column.DesiredWidth, DesiredWidthList[column.ActualIndex]))
                        {
                            // Update the record because collection operation latter on might
                            // need to verified this list again, e.g. insert an 'auto'
                            // column, so that we won't trigger unnecessary update due to
                            // inconsistency of this column.
                            DesiredWidthList[column.ActualIndex] = column.DesiredWidth;

                            desiredWidthChanged = true;
                        }
                    }
                }
            }

            if (desiredWidthChanged)
            {
                InvalidateMeasure();
            }

            LayoutUpdated -= new EventHandler(OnLayoutUpdated);
        }

        // Map column collection index to header collection index in visual tree
        private int GetVisualIndex(int columnIndex)
        {
            // Elements in visual tree: working headers, padding header, indicator, and floating header
            int index = InternalChildren.Count - 3 - columnIndex;
            Debug.Assert(index >= 0 && index < InternalChildren.Count, "Error index when GetVisualIndex");

            return index;
        }

        // Link headers from right to left
        private void BuildHeaderLinks()
        {
            GridViewColumnHeader lastHeader = null;

            if (Columns != null)
            {
                // link working headers.
                for (int i = 0; i < Columns.Count; i++)
                {
                    GridViewColumnHeader header = (GridViewColumnHeader)InternalChildren[GetVisualIndex(i)];
                    header.PreviousVisualHeader = lastHeader;
                    lastHeader = header;
                }
            }

            // link padding header to last header
            if (_paddingHeader != null)
            {
                _paddingHeader.PreviousVisualHeader = lastHeader;
            }
        }

        //
        // This method will do following tasks:
        //  1. Disconnect header from visual parent and logical parent, if any.
        //  2. Create a new header or use the header directly if the GridViewColumn.Header property
        //      is qualify for its own container;
        //  3. Insert the header in the InternalChildren collection
        //  4. Perform routine update and hookup jobs
        //
        private GridViewColumnHeader CreateAndInsertHeader(GridViewColumn column, int index)
        {
            object header = column.Header;
            GridViewColumnHeader headerContainer = header as GridViewColumnHeader;

            //
            // NOTE: when theme chagned, all properties, templates and styles will be reevaluated.
            // But there are 2 cases we need to handle:
            //
            //  1: header property is a qualified container for itself
            //    <GridViewColumn>
            //        <GridViewColumnHeader />
            //    </GridViewColumn>
            //
            //  2: header property is a Visual element
            //    <GridViewColumn>
            //        <Button />
            //    </GridViewColumn>
            //
            // In both cases, we need to diconnect them from the visual and logical tree before
            // they got inserted into the new tree.
            //
            if (header != null)
            {
                DependencyObject d = header as DependencyObject;

                if (d != null)
                {
                    // disconnect from visual tree
                    Visual headerAsVisual = d as Visual;

                    if (headerAsVisual != null)
                    {
                        Visual parent = VisualTreeHelper.GetParent(headerAsVisual) as Visual;

                        if (parent != null)
                        {
                            if (headerContainer != null)
                            {
                                // case 1
                                GridViewHeaderRowPresenter parentAsGVHRP = parent as GridViewHeaderRowPresenter;
                                if (parentAsGVHRP != null)
                                {
                                    parentAsGVHRP.InternalChildren.RemoveNoVerify(headerContainer);
                                }
                                else
                                {
                                    Debug.Assert(false, "Head is container for itself, but parent is neither GridViewHeaderRowPresenter nor null.");
                                }
                            }
                            else
                            {
                                // case 2
                                GridViewColumnHeader parentAsGVCH = parent as GridViewColumnHeader;
                                if (parentAsGVCH != null)
                                {
                                    parentAsGVCH.ClearValue(ContentControl.ContentProperty);
                                }
                            }
                        }
                    }

                    // disconnect from logical tree
                    DependencyObject logicalParent = LogicalTreeHelper.GetParent(d);

                    if (logicalParent != null)
                    {
                        LogicalTreeHelper.RemoveLogicalChild(logicalParent, header);
                    }
                }
            }

            if (headerContainer == null)
            {
                headerContainer = new GridViewColumnHeader();
                headerContainer.IsInternalGenerated = true;
            }

            // Pass column reference to GridViewColumnHeader
            headerContainer.SetValue(GridViewColumnHeader.ColumnPropertyKey, column);

            // Hookup _itemsControl.KeyDown event for canceling resizing if user press 'Esc'.
            HookupItemsControlKeyboardEvent(headerContainer);

            InternalChildren.InsertInternal(index, headerContainer);

            // NOTE: the order here is important!
            // Need to add headerContainer into visual tree first, then apply Style, Template etc.
            UpdateHeader(headerContainer);

            _gvHeadersValid = false;

            return headerContainer;
        }

        private void RemoveHeader(GridViewColumnHeader header, int index)
        {
            Debug.Assert(header != null || index != -1);

            _gvHeadersValid = false;

            if (header != null)
            {
                InternalChildren.Remove(header);
            }
            else
            {
                header = (GridViewColumnHeader)InternalChildren[index];
                InternalChildren.RemoveAt(index);
            }

            UnhookItemsControlKeyboardEvent(header);
        }

        // find needed elements and hook up events
        private void RenewEvents()
        {
            ScrollViewer oldHeaderSV = _headerSV;
            _headerSV = Parent as ScrollViewer;
            if (oldHeaderSV != _headerSV)
            {
                if (oldHeaderSV != null)
                {
                    oldHeaderSV.ScrollChanged -= new ScrollChangedEventHandler(OnHeaderScrollChanged);
                }
                if (_headerSV != null)
                {
                    _headerSV.ScrollChanged += new ScrollChangedEventHandler(OnHeaderScrollChanged);
                }
            }

            ScrollViewer oldSV = _mainSV; // backup the old value
            _mainSV = TemplatedParent as ScrollViewer;

            if (oldSV != _mainSV)
            {
                if (oldSV != null)
                {
                    oldSV.ScrollChanged -= new ScrollChangedEventHandler(OnMasterScrollChanged);
                }

                if (_mainSV != null)
                {
                    _mainSV.ScrollChanged += new ScrollChangedEventHandler(OnMasterScrollChanged);
                }
            }

            // hook up key down event from ItemsControl,
            // because GridViewColumnHeader and GridViewHeaderRowPresenter can not get focus
            ItemsControl oldIC = _itemsControl; // backup the old value
            _itemsControl = FindItemsControlThroughTemplatedParent(this);

            if (oldIC != _itemsControl)
            {
                if (oldIC != null)
                {
                    // NOTE: headers have unhooked the KeyDown event in RemoveHeader.

                    oldIC.KeyDown -= new KeyEventHandler(OnColumnHeadersPresenterKeyDown);
                }

                if (_itemsControl != null)
                {
                    // register to HeadersPresenter to cancel dragging
                    _itemsControl.KeyDown += new KeyEventHandler(OnColumnHeadersPresenterKeyDown);

                    // NOTE: headers will hookup the KeyDown event latter in CreateAndInsertHeader.
                }
            }

            //Set GridViewHeaderRowPresenter to ListView
            ListView lv = _itemsControl as ListView;
            if (lv != null && lv.View != null && lv.View is GridView)
            {
                ((GridView)lv.View).HeaderRowPresenter = this;
            }
        }

        private void UnhookItemsControlKeyboardEvent(GridViewColumnHeader header)
        {
            Debug.Assert(header != null);
            if (_itemsControl != null)
            {
                _itemsControl.KeyDown -= new KeyEventHandler(header.OnColumnHeaderKeyDown);
            }
        }

        private void HookupItemsControlKeyboardEvent(GridViewColumnHeader header)
        {
            Debug.Assert(header != null);
            if (_itemsControl != null)
            {
                _itemsControl.KeyDown += new KeyEventHandler(header.OnColumnHeaderKeyDown);
            }
        }

        // The following two scroll changed methods will not be called recursively and lead to dead loop.
        // When scrolling _masterSV, OnMasterScrollChanged will be called, so _headerSV also scrolled
        // to the same offset. Then, OnHeaderScrollChanged be called, and try to scroll _masterSV, but
        // it's already scrolled to that offset, so OnMasterScrollChanged will not be called.

        // When master scroll viewer changed its offset, change header scroll viewer accordingly
        private void OnMasterScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            if (_headerSV != null && _mainSV == e.OriginalSource)
            {
                _headerSV.ScrollToHorizontalOffset(e.HorizontalOffset);
            }
        }

        // When header scroll viewer changed its offset, change master scroll viewer accordingly
        private void OnHeaderScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            if (_mainSV != null && _headerSV == e.OriginalSource)
            {
                _mainSV.ScrollToHorizontalOffset(e.HorizontalOffset);
            }
        }

        // Create the last padding column header in GridViewHeaderRowPresenter
        private void AddPaddingColumnHeader()
        {
            GridViewColumnHeader paddingHeader = new GridViewColumnHeader();
            paddingHeader.IsInternalGenerated = true;
            paddingHeader.SetValue(GridViewColumnHeader.RolePropertyKey, GridViewColumnHeaderRole.Padding);

            paddingHeader.Content = null;
            paddingHeader.ContentTemplate = null;
            paddingHeader.ContentTemplateSelector = null;
            paddingHeader.MinWidth = 0;
            paddingHeader.Padding = new Thickness(0.0);
            paddingHeader.Width = Double.NaN;
            paddingHeader.HorizontalAlignment = HorizontalAlignment.Stretch;

            if (!AccessibilitySwitches.UseNetFx472CompatibleAccessibilityFeatures)
            {
                // There is no point in this padding column header being focusable.
                paddingHeader.Focusable = false;
            }

            InternalChildren.AddInternal(paddingHeader);
            _paddingHeader = paddingHeader;
        }

        // Create the indicator for column re-ordering
        private void AddIndicator()
        {
            Separator indicator = new Separator();
            indicator.Visibility = Visibility.Hidden;

            // Indicator style:
            //
            // <Setter Property="Margin" Value="0" />
            // <Setter Property="Width" Value="2" />
            // <Setter Property="Template">
            //   <Setter.Value>
            //     <ControlTemplate TargetType="{x:Type Separator}">
            //        <Border Background="#FF000080"/>
            //     </ControlTemplate>
            //   </Setter.Value>
            // </Setter>

            indicator.Margin = new Thickness(0);
            indicator.Width = 2.0;

            FrameworkElementFactory border = new FrameworkElementFactory(typeof(Border));
            border.SetValue(Border.BackgroundProperty, new SolidColorBrush(Color.FromUInt32(0xFF000080)));

            ControlTemplate template = new ControlTemplate(typeof(Separator));
            template.VisualTree = border;
            template.Seal();

            indicator.Template = template;

            InternalChildren.AddInternal(indicator);
            _indicator = indicator;
        }

        // Create the floating header
        private void AddFloatingHeader(GridViewColumnHeader srcHeader)
        {
            GridViewColumnHeader header;

            // #1426973: Because users may put subclassed header as the column header,
            //           we need to create the same type per source header as the floating one
            // Get source header's type
            Type headerType = (srcHeader != null ? srcHeader.GetType() : typeof(GridViewColumnHeader));

            try
            {
                // Instantiate the same type for floating header
                header = Activator.CreateInstance(headerType) as GridViewColumnHeader;
            }
            catch (MissingMethodException e)
            {
                throw new ArgumentException(SR.Format(SR.ListView_MissingParameterlessConstructor, headerType), e);
            }

            Debug.Assert(header != null, "Cannot instantiate GridViewColumnHeader in AddFloatingHeader");

            header.IsInternalGenerated = true;
            header.SetValue(GridViewColumnHeader.RolePropertyKey, GridViewColumnHeaderRole.Floating);
            header.Visibility = Visibility.Hidden;

            InternalChildren.AddInternal(header);
            _floatingHeader = header;
        }

        // Fill necessary properties in floating header
        private void UpdateFloatingHeader(GridViewColumnHeader srcHeader)
        {
            Debug.Assert(srcHeader != null, "srcHeader is null");
            Debug.Assert(_floatingHeader != null, "floating header is null");

            _floatingHeader.Style = srcHeader.Style;

            _floatingHeader.FloatSourceHeader = srcHeader;
            _floatingHeader.Width = srcHeader.ActualWidth;
            _floatingHeader.Height = srcHeader.ActualHeight;
            _floatingHeader.SetValue(GridViewColumnHeader.ColumnPropertyKey, srcHeader.Column);
            _floatingHeader.Visibility = Visibility.Hidden;

            // override floating header's MinWidth/MinHeight to disable users to change floating header's Width/Height
            _floatingHeader.MinWidth = srcHeader.MinWidth;
            _floatingHeader.MinHeight = srcHeader.MinHeight;

            object template = srcHeader.ReadLocalValue(GridViewColumnHeader.ContentTemplateProperty);
            if ((template != DependencyProperty.UnsetValue) && (template != null))
            {
                _floatingHeader.ContentTemplate = srcHeader.ContentTemplate;
            }

            object selector = srcHeader.ReadLocalValue(GridViewColumnHeader.ContentTemplateSelectorProperty);
            if ((selector != DependencyProperty.UnsetValue) && (selector != null))
            {
                _floatingHeader.ContentTemplateSelector = srcHeader.ContentTemplateSelector;
            }

            if (!(srcHeader.Content is Visual))
            {
                _floatingHeader.Content = srcHeader.Content;
            }
        }

        /// <summary>
        /// This method is invoked when mouse move and left button is pressed.
        /// This method judges if the mouse move exceeds a threshold to start re-order.
        /// </summary>
        /// <param name="currentPos">The current mouse position, relative to GridViewHeaderRowPresenter</param>
        /// <param name="originalPos">The original start position, relative to GridViewHeaderRowPresenter</param>
        /// <returns></returns>
        private bool CheckStartHeaderDrag(Point currentPos, Point originalPos)
        {
            return (DoubleUtil.GreaterThan(Math.Abs(currentPos.X - originalPos.X), c_thresholdX));
        }

        // Find ItemsControl through TemplatedParent
        private static ItemsControl FindItemsControlThroughTemplatedParent(GridViewHeaderRowPresenter presenter)
        {
            FrameworkElement fe = presenter.TemplatedParent as FrameworkElement;
            ItemsControl itemsControl = null;

            while (fe != null)
            {
                itemsControl = fe as ItemsControl;
                if (itemsControl != null)
                {
                    break;
                }
                fe = fe.TemplatedParent as FrameworkElement;
            }

            return itemsControl;
        }

        private void OnColumnHeadersPresenterKeyDown(object sender, KeyEventArgs e)
        {
            // if press Escape when re-ordering, cancel re-order
            if (e.Key == Key.Escape && _isHeaderDragging)
            {
                // save the source header b/c FinishHeaderDrag will clear it
                GridViewColumnHeader srcHeader = _draggingSrcHeader;

                FinishHeaderDrag(true);
                PrepareHeaderDrag(srcHeader, _currentPos, _relativeStartPos, true);
                InvalidateArrange();
            }
        }

        // Find the column header from the visual tree by column
        private GridViewColumnHeader FindHeaderByColumn(GridViewColumn column)
        {
            GridViewColumnCollection columns = Columns;
            UIElementCollection children = InternalChildren;

            if (columns != null && children.Count > columns.Count)
            {
                int index = columns.IndexOf(column);

                if (index != -1)
                {
                    // Becuase column headers is generated from right to left
                    int visualIndex = GetVisualIndex(index);

                    GridViewColumnHeader header = children[visualIndex] as GridViewColumnHeader;

                    if (header.Column != column)
                    {
                        // NOTE: if user change the GridViewColumn.Header/HeaderStyle
                        // in the event handler of column move. And in such case, the header
                        // we found by the algorithm above will be fail. So we turn to below
                        // a more reliable one.

                        for (int i = 1; i < children.Count; i++)
                        {
                            header = children[i] as GridViewColumnHeader;

                            if (header != null && header.Column == column)
                            {
                                return header;
                            }
                        }
                    }
                    else
                    {
                        return header;
                    }
                }
            }

            return null;
        }

        // Find logic column index by position
        // If parameter 'findNearestColumn' is true, find the nearest column relative to mouse position
        private int FindIndexByPosition(Point startPos, bool findNearestColumn)
        {
            int index = -1;

            if (startPos.X < 0.0)
            {
                return 0;
            }

            for (int i = 0; i < HeadersPositionList.Count; i++)
            {
                index++;

                Rect rect = HeadersPositionList[i];
                double startX = rect.X;
                double endX = startX + rect.Width;

                if (DoubleUtil.GreaterThanOrClose(startPos.X, startX) &&
                    DoubleUtil.LessThanOrClose(startPos.X, endX))
                {
                    if (findNearestColumn)
                    {
                        double midX = (startX + endX) * 0.5;
                        if (DoubleUtil.GreaterThanOrClose(startPos.X, midX))
                        {
                            // if not the padding header
                            if (i != HeadersPositionList.Count - 1)
                            {
                                index++;
                            }
                        }
                    }

                    break;
                }
            }

            return index;
        }

        // Find position by logic column index
        private Point FindPositionByIndex(int index)
        {
            Debug.Assert(index >= 0 && index < HeadersPositionList.Count, "wrong index");
            return new Point(HeadersPositionList[index].X, 0);
        }

        // Update header Content, Style, ContentTemplate, ContentTemplateSelector, ContentStringFormat, ContextMenu and ToolTip
        private void UpdateHeader(GridViewColumnHeader header)
        {
            UpdateHeaderContent(header);

            for (int i = 0, n = s_DPList[0].Length; i < n; i++)
            {
                UpdateHeaderProperty(header, s_DPList[2][i] /* header */,
                    s_DPList[1][i] /* column */, s_DPList[0][i] /* GV */);
            }
        }

        // Update Content of GridViewColumnHeader
        private void UpdateHeaderContent(GridViewColumnHeader header)
        {
            if (header != null && header.IsInternalGenerated)
            {
                GridViewColumn column = header.Column;
                if (column != null)
                {
                    if (column.Header == null)
                    {
                        header.ClearValue(ContentControl.ContentProperty);
                    }
                    else
                    {
                        header.Content = column.Header;
                    }
                }
            }
        }

        // Update Style, ContextMenu and ToolTip properties for padding header.
        // GridView.ColumnHeaderTemplate(Selector) and GridView.ColumnHeaderStringFormat
        // don't work on padding header.
        private void UpdatePaddingHeader(GridViewColumnHeader header)
        {
            UpdateHeaderProperty(header, ColumnHeaderContainerStyleProperty);

            UpdateHeaderProperty(header, ColumnHeaderContextMenuProperty);

            UpdateHeaderProperty(header, ColumnHeaderToolTipProperty);
        }

        private void UpdateAllHeaders(DependencyProperty dp)
        {
            DependencyProperty gvDP, columnDP, headerDP;
            GetMatchingDPs(dp, out gvDP, out columnDP, out headerDP);

            int iStart, iEnd;
            GetIndexRange(dp, out iStart, out iEnd);

            UIElementCollection children = InternalChildren;
            for (int i = iStart; i <= iEnd; i++)
            {
                GridViewColumnHeader header = children[i] as GridViewColumnHeader;
                if (header != null)
                {
                    UpdateHeaderProperty(header, headerDP, columnDP, gvDP);
                }
            }
        }

        /// <summary>
        /// get index range of header for need update
        /// </summary>
        /// <param name="dp">the GridView DP in this update</param>
        /// <param name="iStart">starting index of header need update</param>
        /// <param name="iEnd">ending index of header need update</param>
        private void GetIndexRange(DependencyProperty dp, out int iStart, out int iEnd)
        {
            // whether or not include the padding header
            iStart = (dp == ColumnHeaderTemplateProperty ||
                        dp == ColumnHeaderTemplateSelectorProperty ||
                        dp == ColumnHeaderStringFormatProperty)
                    ? 1 : 0;

            iEnd = InternalChildren.Count - 3;  // skip the floating header and the indicator.
        }

        private void UpdateHeaderProperty(
            GridViewColumnHeader header,    // the header need to update
            DependencyProperty targetDP,    // the target DP on header
            DependencyProperty columnDP,    // the DP on Column as 1st source, can be Null
            DependencyProperty gvDP         // the DP on GridView as 2nd source
            )
        {
            if (gvDP == ColumnHeaderContainerStyleProperty
                && header.Role == GridViewColumnHeaderRole.Padding)
            {
                // Because padding header has no chance to be instantiated by a sub-classed GridViewColumnHeader,
                // we ignore GridView.ColumnHeaderContainerStyle silently if its TargetType is not GridViewColumnHeader (or parent)
                // I.e. for padding header, only accept the GridViewColumnHeader as TargetType
                Style style = ColumnHeaderContainerStyle;
                if (style != null && !style.TargetType.IsAssignableFrom(typeof(GridViewColumnHeader)))
                {
                    // use default style for padding header in this case
                    header.Style = null;
                    return;
                }
            }

            GridViewColumn column = header.Column;

            object value = null;

            if (column != null /* not the padding one */
                && columnDP != null /* Column doesn't has ContextMenu property*/)
            {
                value = column.GetValue(columnDP);
            }

            if (value == null)
            {
                value = this.GetValue(gvDP);
            }

            header.UpdateProperty(targetDP, value);
        }

        // Prepare column header re-ordering
        private void PrepareHeaderDrag(GridViewColumnHeader header, Point pos, Point relativePos, bool cancelInvoke)
        {
            if (header.Role == GridViewColumnHeaderRole.Normal)
            {
                _prepareDragging = true;
                _isHeaderDragging = false;
                _draggingSrcHeader = header;

                _startPos = pos;
                _relativeStartPos = relativePos;

                if (!cancelInvoke)
                {
                    _startColumnIndex = FindIndexByPosition(_startPos, false);
                }
            }
        }

        // Start header drag
        private void StartHeaderDrag()
        {
            _startPos = _currentPos;
            _isHeaderDragging = true;

            // suppress src header's click event
            _draggingSrcHeader.SuppressClickEvent = true;

            // lock Columns during header dragging
            if (Columns != null)
            {
                Columns.BlockWrite();
            }

            // Remove the old floating header,
            // then create & add the new one per the source header's type
            InternalChildren.Remove(_floatingHeader);
            AddFloatingHeader(_draggingSrcHeader);

            UpdateFloatingHeader(_draggingSrcHeader);
        }

        // Finish header drag
        private void FinishHeaderDrag(bool isCancel)
        {
            // clear related fields
            _prepareDragging = false;
            _isHeaderDragging = false;

            // restore src header's click event
            _draggingSrcHeader.SuppressClickEvent = false;

            _floatingHeader.Visibility = Visibility.Hidden;
            _floatingHeader.ResetFloatingHeaderCanvasBackground();

            _indicator.Visibility = Visibility.Hidden;

            // unlock Columns during header dragging
            if (Columns != null)
            {
                Columns.UnblockWrite();
            }

            // if cancelled, do nothing
            if (!isCancel)
            {
                // Display floating header if vertical move not exceeds header.Height * 2
                bool isMoveHeader = IsMousePositionValid(_floatingHeader, _currentPos, 2.0);

                Debug.Assert(Columns != null, "Columns is null in OnHeaderDragCompleted");

                // Revise the destinate column index
                int newColumnIndex = (_startColumnIndex >= _desColumnIndex) ? _desColumnIndex : _desColumnIndex - 1;

                if (isMoveHeader)
                {
                    Columns.Move(_startColumnIndex, newColumnIndex);
                }
            }
        }

        // check if the Mouse position is in the given valid area
        private static bool IsMousePositionValid(FrameworkElement floatingHeader, Point currentPos, double arrange)
        {
            // valid area: - height * arrange <= currentPos.Y <= height * ( arrange + 1)
            return DoubleUtil.LessThanOrClose(-floatingHeader.Height * arrange, currentPos.Y) &&
                   DoubleUtil.LessThanOrClose(currentPos.Y, floatingHeader.Height * (arrange + 1));
        }

        #endregion Private Methods

        //-------------------------------------------------------------------
        //
        // Private Class / Properties / Fields
        //
        //-------------------------------------------------------------------

        #region Private Class / Properties / Fields

        //Return the actual column headers array
        internal List<GridViewColumnHeader> ActualColumnHeaders
        {
            get
            {
                if (_gvHeaders == null || !_gvHeadersValid)
                {
                    _gvHeadersValid = true;
                    _gvHeaders = new List<GridViewColumnHeader>();
                    if (Columns != null)
                    {
                        UIElementCollection children = InternalChildren;

                        for (int i = 0, count = Columns.Count; i < count; ++i)
                        {
                            GridViewColumnHeader header = children[GetVisualIndex(i)] as GridViewColumnHeader;
                            if (header != null)
                            {
                                _gvHeaders.Add(header);
                            }
                        }
                    }
                }

                return _gvHeaders;
            }
        }

        private bool _gvHeadersValid;
        private List<GridViewColumnHeader> _gvHeaders;

        // Store the column header's position in visual tree
        // including the padding header
        private List<Rect> HeadersPositionList
        {
            get
            {
                if (_headersPositionList == null)
                {
                    _headersPositionList = new List<Rect>();
                }

                return _headersPositionList;
            }
        }

        private List<Rect> _headersPositionList;

        private ScrollViewer _mainSV;
        private ScrollViewer _headerSV;
        private GridViewColumnHeader _paddingHeader;
        private GridViewColumnHeader _floatingHeader;
        private Separator _indicator;

        // parent ItemsControl
        private ItemsControl _itemsControl;

        // source header when header dragging
        private GridViewColumnHeader _draggingSrcHeader;

        // start position when dragging (position relative to GridViewHeaderRowPresenter)
        private Point _startPos;

        // relative start position when dragging (position relative to Header)
        private Point _relativeStartPos;

        // current mouse position (position relative to GridViewHeaderRowPresenter)
        private Point _currentPos;

        // start column index when begin dragging
        private int _startColumnIndex;

        // destination column index when finish dragging
        private int _desColumnIndex;

        // indicating if header is dragging
        private bool _isHeaderDragging;

        // indicating column is changed or created for the first
        private bool _isColumnChangedOrCreated;

        // indicating a mouse down, ready to drag the header
        private bool _prepareDragging;

        // the threshold for horizontal move when header dragging
        private const double c_thresholdX = 4.0;

        #endregion Private Properties

        #region DP resolve helper

        // resolve dp from name
        private static DependencyProperty GetColumnDPFromName(string dpName)
        {
            foreach (DependencyProperty dp in s_DPList[1])
            {
                if ((dp != null) && dpName.Equals(dp.Name))
                {
                    return dp;
                }
            }

            return null;
        }

        // resolve matching DPs from one
        private static void GetMatchingDPs(DependencyProperty indexDP,
            out DependencyProperty gvDP, out DependencyProperty columnDP, out DependencyProperty headerDP)
        {
            for (int i = 0; i < s_DPList.Length; i++)
            {
                for (int j = 0; j < s_DPList[i].Length; j++)
                {
                    if (indexDP == s_DPList[i][j])
                    {
                        gvDP = s_DPList[0][j];
                        columnDP = s_DPList[1][j];
                        headerDP = s_DPList[2][j];

                        goto found;
                    }
                }
            }

            gvDP = columnDP = headerDP = null;

        found: ;
        }

        private static readonly DependencyProperty[][] s_DPList = new DependencyProperty[][]
        {
            // DPs on GridViewHeaderRowPresenter
            new DependencyProperty[] {
                ColumnHeaderContainerStyleProperty,
                ColumnHeaderTemplateProperty,
                ColumnHeaderTemplateSelectorProperty,
                ColumnHeaderStringFormatProperty,
                ColumnHeaderContextMenuProperty,
                ColumnHeaderToolTipProperty,
            },

            // DPs on GridViewColumn
            new DependencyProperty[] {
                GridViewColumn.HeaderContainerStyleProperty,
                GridViewColumn.HeaderTemplateProperty,
                GridViewColumn.HeaderTemplateSelectorProperty,
                GridViewColumn.HeaderStringFormatProperty,
                null,
                null,
            },

            // DPs on GridViewColumnHeader
            new DependencyProperty[] {
                GridViewColumnHeader.StyleProperty,
                GridViewColumnHeader.ContentTemplateProperty,
                GridViewColumnHeader.ContentTemplateSelectorProperty,
                GridViewColumnHeader.ContentStringFormatProperty,
                GridViewColumnHeader.ContextMenuProperty,
                GridViewColumnHeader.ToolTipProperty,
            }
        };

        #endregion
    }
}
