// 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.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using MS.Internal;

namespace System.Windows.Controls.Primitives
{
    /// <summary>
    ///     A control that will be responsible for generating column headers.
    ///     This control is meant to be specified within the template of the DataGrid.
    ///
    ///     It typically isn't in the subtree of the main ScrollViewer for the DataGrid.
    ///     It thus handles scrolling the column headers horizontally.  For this to work
    ///     it needs to be able to find the ScrollViewer -- this is done by setting the
    ///     SourceScrollViewerName property.
    /// </summary>
    [TemplatePart(Name = "PART_FillerColumnHeader", Type = typeof(DataGridColumnHeader))]
    public class DataGridColumnHeadersPresenter : ItemsControl
    {
        #region Constants

        private const string ElementFillerColumnHeader = "PART_FillerColumnHeader";

        #endregion

        static DataGridColumnHeadersPresenter()
        {
            Type ownerType = typeof(DataGridColumnHeadersPresenter);

            DefaultStyleKeyProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(ownerType));
            FocusableProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(false));

            FrameworkElementFactory factory = new FrameworkElementFactory(typeof(DataGridCellsPanel));
            ItemsPanelProperty.OverrideMetadata(ownerType, new FrameworkPropertyMetadata(new ItemsPanelTemplate(factory)));

            VirtualizingPanel.IsVirtualizingProperty.OverrideMetadata(
                ownerType,
                new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsVirtualizingPropertyChanged), new CoerceValueCallback(OnCoerceIsVirtualizingProperty)));

            VirtualizingPanel.VirtualizationModeProperty.OverrideMetadata(
                ownerType,
                new FrameworkPropertyMetadata(VirtualizationMode.Recycling));
        }

        #region Initialization

        /// <summary>
        ///     Tells the row owner about this element.
        /// </summary>
        public override void OnApplyTemplate()
        {
            // If a new template has just been generated then 
            // be sure to clear any stale ItemsHost references
            if (InternalItemsHost != null && !this.IsAncestorOf(InternalItemsHost))
            {
                InternalItemsHost = null;
            }
            
            base.OnApplyTemplate();

            // Find the columns collection and set the ItemsSource.
            DataGrid grid = ParentDataGrid;

            if (grid != null)
            {
                ItemsSource = new DataGridColumnHeaderCollection(grid.Columns);
                grid.ColumnHeadersPresenter = this;
                DataGridHelper.TransferProperty(this, VirtualizingPanel.IsVirtualizingProperty);

                DataGridColumnHeader fillerColumnHeader = GetTemplateChild(ElementFillerColumnHeader) as DataGridColumnHeader;
                if (fillerColumnHeader != null)
                {
                    DataGridHelper.TransferProperty(fillerColumnHeader, DataGridColumnHeader.StyleProperty);
                    DataGridHelper.TransferProperty(fillerColumnHeader, DataGridColumnHeader.HeightProperty);
                }
            }
            else
            {
                ItemsSource = null;
            }
        }

        #endregion

        #region Automation

        protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
        {
            return new System.Windows.Automation.Peers.DataGridColumnHeadersPresenterAutomationPeer(this);
        }

        #endregion

        #region Layout

        /// <summary>
        ///     Measure
        /// </summary>
        protected override Size MeasureOverride(Size availableSize)
        {
            Size desiredSize;
            Size childConstraint = availableSize;
            childConstraint.Width = Double.PositiveInfinity;

            desiredSize = base.MeasureOverride(childConstraint);

            Size indicatorSize;
            if (_columnHeaderDragIndicator != null && _isColumnHeaderDragging)
            {
                _columnHeaderDragIndicator.Measure(childConstraint);
                indicatorSize = _columnHeaderDragIndicator.DesiredSize;
                desiredSize.Width = Math.Max(desiredSize.Width, indicatorSize.Width);
                desiredSize.Height = Math.Max(desiredSize.Height, indicatorSize.Height);
            }

            if (_columnHeaderDropLocationIndicator != null && _isColumnHeaderDragging)
            {
                _columnHeaderDropLocationIndicator.Measure(availableSize);
                indicatorSize = _columnHeaderDropLocationIndicator.DesiredSize;
                desiredSize.Width = Math.Max(desiredSize.Width, indicatorSize.Width);
                desiredSize.Height = Math.Max(desiredSize.Height, indicatorSize.Height);
            }

            desiredSize.Width = Math.Min(availableSize.Width, desiredSize.Width);

            return desiredSize;
        }

        /// <summary>
        ///     Arrange
        /// </summary>
        /// <param name="finalSize">Arrange size</param>
        protected override Size ArrangeOverride(Size finalSize)
        {
            UIElement child = (VisualTreeHelper.GetChildrenCount(this) > 0) ? VisualTreeHelper.GetChild(this, 0) as UIElement : null;

            if (child != null)
            {
                Rect childRect = new Rect(finalSize);
                DataGrid dataGrid = ParentDataGrid;
                if (dataGrid != null)
                {
                    childRect.X = -dataGrid.HorizontalScrollOffset;
                    childRect.Width = Math.Max(finalSize.Width, dataGrid.CellsPanelActualWidth);
                }

                child.Arrange(childRect);
            }

            if (_columnHeaderDragIndicator != null && _isColumnHeaderDragging)
            {
                _columnHeaderDragIndicator.Arrange(new Rect(
                    new Point(_columnHeaderDragCurrentPosition.X - _columnHeaderDragStartRelativePosition.X, 0),
                    new Size(_columnHeaderDragIndicator.Width, _columnHeaderDragIndicator.Height)));
            }

            if (_columnHeaderDropLocationIndicator != null && _isColumnHeaderDragging)
            {
                Point point = FindColumnHeaderPositionByCurrentPosition(_columnHeaderDragCurrentPosition, true);
                double dropIndicatorWidth = _columnHeaderDropLocationIndicator.Width;
                point.X -= dropIndicatorWidth * 0.5;
                _columnHeaderDropLocationIndicator.Arrange(new Rect(point, new Size(dropIndicatorWidth, _columnHeaderDropLocationIndicator.Height)));
            }

            return finalSize;
        }

        /// <summary>
        ///     Override of UIElement.GetLayoutClip().  This is a tricky way to ensure we always clip regardless of the value of ClipToBounds.
        /// </summary>
        protected override Geometry GetLayoutClip(Size layoutSlotSize)
        {
            RectangleGeometry clip = new RectangleGeometry(new Rect(RenderSize));
            clip.Freeze();
            return clip;
        }

        #endregion

        #region Column Header Generation

        /// <summary>
        ///     Instantiates an instance of a container.
        /// </summary>
        /// <returns>A new DataGridColumnHeader.</returns>
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new DataGridColumnHeader();
        }

        /// <summary>
        ///     Determines if an item is its own container.
        /// </summary>
        /// <param name="item">The item to test.</param>
        /// <returns>true if the item is a DataGridColumnHeader, false otherwise.</returns>
        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is DataGridColumnHeader;
        }

        /// <summary>
        ///     Method which returns the result of IsItemItsOwnContainerOverride to be used internally
        /// </summary>
        internal bool IsItemItsOwnContainerInternal(object item)
        {
            return IsItemItsOwnContainerOverride(item);
        }

        /// <summary>
        ///     Prepares a new container for a given item.
        /// </summary>
        /// <remarks>We do not want to call base.PrepareContainerForItemOverride in this override because it will set local values on the header</remarks>
        /// <param name="element">The new container.</param>
        /// <param name="item">The item that the container represents.</param>
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            DataGridColumnHeader header = element as DataGridColumnHeader;

            if (header != null)
            {
                DataGridColumn column = ColumnFromContainer(header);
                Debug.Assert(column != null, "We shouldn't have generated this column header if we don't have a column.");

                if (header.Column == null)
                {
                    // A null column means this is a fresh container.  PrepareContainer will also be called simply if the column's
                    // Header property has changed and this container needs to be given a new item.  In that case it'll already be tracked.
                    header.Tracker.Debug_AssertNotInList(_headerTrackingRoot);
                    header.Tracker.StartTracking(ref _headerTrackingRoot);
                }

                header.Tracker.Debug_AssertIsInList(_headerTrackingRoot);

                header.PrepareColumnHeader(item, column);
            }
        }

        /// <summary>
        ///     Clears a container of references.
        /// </summary>
        /// <param name="element">The container being cleared.</param>
        /// <param name="item">The data item that the container represented.</param>
        protected override void ClearContainerForItemOverride(DependencyObject element, object item)
        {
            DataGridColumnHeader header = element as DataGridColumnHeader;

            base.ClearContainerForItemOverride(element, item);

            if (header != null)
            {
                header.Tracker.StopTracking(ref _headerTrackingRoot);
                header.ClearHeader();
            }
        }

        private DataGridColumn ColumnFromContainer(DataGridColumnHeader container)
        {
            Debug.Assert(HeaderCollection != null, "This is a helper method for preparing and clearing a container; if it's called we must have a valid ItemSource");

            int index = ItemContainerGenerator.IndexFromContainer(container);
            return HeaderCollection.ColumnFromIndex(index);
        }

        #endregion

        #region Notification Propagation

        /// <summary>
        ///     General notification for DependencyProperty changes from the grid.
        /// </summary>
        internal void NotifyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e, DataGridNotificationTarget target)
        {
            NotifyPropertyChanged(d, string.Empty, e, target);
        }

        /// <summary>
        ///     Notification for column header-related DependencyProperty changes from the grid or from columns.
        /// </summary>
        internal void NotifyPropertyChanged(DependencyObject d, string propertyName, DependencyPropertyChangedEventArgs e, DataGridNotificationTarget target)
        {
            DataGridColumn column = d as DataGridColumn;
            if (DataGridHelper.ShouldNotifyColumnHeadersPresenter(target))
            {
                if (e.Property == DataGridColumn.WidthProperty ||
                    e.Property == DataGridColumn.DisplayIndexProperty)
                {
                    if (column.IsVisible)
                    {
                        InvalidateDataGridCellsPanelMeasureAndArrange();
                    }
                }
                else if (e.Property == DataGrid.FrozenColumnCountProperty ||
                    e.Property == DataGridColumn.VisibilityProperty ||
                    e.Property == DataGrid.CellsPanelHorizontalOffsetProperty ||
                    string.Compare(propertyName, "ViewportWidth", StringComparison.Ordinal) == 0 ||
                    string.Compare(propertyName, "DelayedColumnWidthComputation", StringComparison.Ordinal) == 0)
                {
                    InvalidateDataGridCellsPanelMeasureAndArrange();
                }
                else if (e.Property == DataGrid.HorizontalScrollOffsetProperty)
                {
                    InvalidateArrange();
                    InvalidateDataGridCellsPanelMeasureAndArrange();
                }
                else if (string.Compare(propertyName, "RealizedColumnsBlockListForNonVirtualizedRows", StringComparison.Ordinal) == 0)
                {
                    InvalidateDataGridCellsPanelMeasureAndArrange(/* withColumnVirtualization */ false);
                }
                else if (string.Compare(propertyName, "RealizedColumnsBlockListForVirtualizedRows", StringComparison.Ordinal) == 0)
                {
                    InvalidateDataGridCellsPanelMeasureAndArrange(/* withColumnVirtualization */ true);
                }
                else if (e.Property == DataGrid.CellsPanelActualWidthProperty)
                {
                    InvalidateArrange();
                }
                else if (e.Property == DataGrid.EnableColumnVirtualizationProperty)
                {
                    DataGridHelper.TransferProperty(this, VirtualizingPanel.IsVirtualizingProperty);
                }
            }

            if (DataGridHelper.ShouldNotifyColumnHeaders(target))
            {
                if (e.Property == DataGridColumn.HeaderProperty)
                {
                    if (HeaderCollection != null)
                    {
                        HeaderCollection.NotifyHeaderPropertyChanged(column, e);
                    }
                }
                else
                {
                    // Notify the DataGridColumnHeader objects about property changes
                    ContainerTracking<DataGridColumnHeader> tracker = _headerTrackingRoot;

                    while (tracker != null)
                    {
                        tracker.Container.NotifyPropertyChanged(d, e);
                        tracker = tracker.Next;
                    }

                    // Handle Style & Height change notification for PART_FillerColumnHeader.
                    if (d is DataGrid &&
                        (e.Property == DataGrid.ColumnHeaderStyleProperty || e.Property == DataGrid.ColumnHeaderHeightProperty) )
                    {
                        DataGridColumnHeader fillerColumnHeader = GetTemplateChild(ElementFillerColumnHeader) as DataGridColumnHeader;
                        if (fillerColumnHeader != null)
                        {
                            fillerColumnHeader.NotifyPropertyChanged(d, e);
                        }
                    }
                }
            }
        }

        #endregion

        #region Column Virtualization

        /// <summary>
        ///     Property changed callback for VirtualizingStackPanel.IsVirtualizing property
        /// </summary>
        private static void OnIsVirtualizingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DataGridColumnHeadersPresenter headersPresenter = (DataGridColumnHeadersPresenter)d;
            DataGridHelper.TransferProperty(headersPresenter, VirtualizingPanel.IsVirtualizingProperty);
            if (e.OldValue != headersPresenter.GetValue(VirtualizingPanel.IsVirtualizingProperty))
            {
                headersPresenter.InvalidateDataGridCellsPanelMeasureAndArrange();
            }
        }

        /// <summary>
        ///     Coercion callback for VirtualizingStackPanel.IsVirtualizing property
        /// </summary>
        private static object OnCoerceIsVirtualizingProperty(DependencyObject d, object baseValue)
        {
            var headersPresenter = d as DataGridColumnHeadersPresenter;
            return DataGridHelper.GetCoercedTransferPropertyValue(
                headersPresenter,
                baseValue,
                VirtualizingPanel.IsVirtualizingProperty,
                headersPresenter.ParentDataGrid,
                DataGrid.EnableColumnVirtualizationProperty);
        }

        /// <summary>
        ///     Helper method which invalidate the underlying itemshost's measure and arrange
        /// </summary>
        private void InvalidateDataGridCellsPanelMeasureAndArrange()
        {
            if (_internalItemsHost != null)
            {
                _internalItemsHost.InvalidateMeasure();
                _internalItemsHost.InvalidateArrange();
            }
        }

        /// <summary>
        ///     Helper method which invalidate the underlying itemshost's measure and arrange
        /// </summary>
        /// <param name="withColumnVirtualization">
        ///     True to invalidate only when virtualization is on.
        ///     False to invalidate only when virtualization is off.
        /// </param>
        private void InvalidateDataGridCellsPanelMeasureAndArrange(bool withColumnVirtualization)
        {
            // Invalidates measure and arrange if the flag and the virtualization
            // are either both true or both false.
            if (withColumnVirtualization == VirtualizingPanel.GetIsVirtualizing(this))
            {
                InvalidateDataGridCellsPanelMeasureAndArrange();
            }
        }

        /// <summary>
        ///     Workaround for not being able to access the panel instance of
        ///     itemscontrol directly
        /// </summary>
        internal Panel InternalItemsHost
        {
            get { return _internalItemsHost; }
            set { _internalItemsHost = value; }
        }

        #endregion

        #region Column Reordering

        /// <summary>
        ///     Override of VisualChildrenCount which accomodates the indicators as visual children
        /// </summary>
        protected override int VisualChildrenCount
        {
            get
            {
                int visualChildrenCount = base.VisualChildrenCount;
                if (_columnHeaderDragIndicator != null)
                {
                    visualChildrenCount++;
                }

                if (_columnHeaderDropLocationIndicator != null)
                {
                    visualChildrenCount++;
                }

                return visualChildrenCount;
            }
        }

        /// <summary>
        ///     Override of GetVisualChild which accomodates the indicators as visual children
        /// </summary>
        protected override Visual GetVisualChild(int index)
        {
            int visualChildrenCount = base.VisualChildrenCount;
            if (index == visualChildrenCount)
            {
                if (_columnHeaderDragIndicator != null)
                {
                    return _columnHeaderDragIndicator;
                }
                else if (_columnHeaderDropLocationIndicator != null)
                {
                    return _columnHeaderDropLocationIndicator;
                }
            }

            if (index == visualChildrenCount + 1)
            {
                if (_columnHeaderDragIndicator != null && _columnHeaderDropLocationIndicator != null)
                {
                    return _columnHeaderDropLocationIndicator;
                }
            }

            return base.GetVisualChild(index);
        }

        /// <summary>
        ///     Gets called on mouse left button down of child header, and ensures preparation for column header drag
        /// </summary>
        internal void OnHeaderMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            if (ParentDataGrid == null)
            {
                return;
            }

            if (_columnHeaderDragIndicator != null)
            {
                RemoveVisualChild(_columnHeaderDragIndicator);
                _columnHeaderDragIndicator = null;
            }

            if (_columnHeaderDropLocationIndicator != null)
            {
                RemoveVisualChild(_columnHeaderDropLocationIndicator);
                _columnHeaderDropLocationIndicator = null;
            }

            Point mousePosition = e.GetPosition(this);
            DataGridColumnHeader header = FindColumnHeaderByPosition(mousePosition);

            if (header != null)
            {
                DataGridColumn column = header.Column;

                if (ParentDataGrid.CanUserReorderColumns && column.CanUserReorder)
                {
                    PrepareColumnHeaderDrag(header, e.GetPosition(this), e.GetPosition(header));
                }
            }
            else
            {
                _isColumnHeaderDragging = false;
                _prepareColumnHeaderDragging = false;
                _draggingSrcColumnHeader = null;
                InvalidateArrange();
            }
        }

        /// <summary>
        ///     Gets called on mouse move of child header, and ensures column header drag
        /// </summary>
        internal void OnHeaderMouseMove(MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                if (_prepareColumnHeaderDragging)
                {
                    _columnHeaderDragCurrentPosition = e.GetPosition(this);

                    if (!_isColumnHeaderDragging)
                    {
                        if (CheckStartColumnHeaderDrag(_columnHeaderDragCurrentPosition, _columnHeaderDragStartPosition))
                        {
                            StartColumnHeaderDrag();
                        }
                    }
                    else
                    {
                        bool shouldDisplayDragIndicator = IsMousePositionValidForColumnDrag(2.0);
                        Visibility dragIndicatorVisibility = shouldDisplayDragIndicator ? Visibility.Visible : Visibility.Collapsed;

                        if (_columnHeaderDragIndicator != null)
                        {
                            _columnHeaderDragIndicator.Visibility = dragIndicatorVisibility;
                        }

                        if (_columnHeaderDropLocationIndicator != null)
                        {
                            _columnHeaderDropLocationIndicator.Visibility = dragIndicatorVisibility;
                        }

                        InvalidateArrange();

                        DragDeltaEventArgs dragDeltaEventArgs = new DragDeltaEventArgs(
                            _columnHeaderDragCurrentPosition.X - _columnHeaderDragStartPosition.X,
                            _columnHeaderDragCurrentPosition.Y - _columnHeaderDragStartPosition.Y);

                        _columnHeaderDragStartPosition = _columnHeaderDragCurrentPosition;
                        ParentDataGrid.OnColumnHeaderDragDelta(dragDeltaEventArgs);
                    }
                }
            }
        }

        /// <summary>
        ///     Gets called on mouse left button up of child header, and ensures reordering of columns on successful completion of drag
        /// </summary>
        internal void OnHeaderMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            if (_isColumnHeaderDragging)
            {
                _columnHeaderDragCurrentPosition = e.GetPosition(this);
                FinishColumnHeaderDrag(false);
            }
            else
            {
                ClearColumnHeaderDragInfo();
            }
        }

        /// <summary>
        ///     Gets called on mouse lost capture of child header and ensures that when capture gets lost
        ///     the drag ends in appropriate state. In this case it restore the drag state to
        ///     the start of the operation by finishing the drag with cancel flag
        /// </summary>
        internal void OnHeaderLostMouseCapture(MouseEventArgs e)
        {
            if (_isColumnHeaderDragging &&
                Mouse.LeftButton == MouseButtonState.Pressed)
            {
                FinishColumnHeaderDrag(true);
            }
        }

        /// <summary>
        ///     Helper method which clears the header drag state
        /// </summary>
        private void ClearColumnHeaderDragInfo()
        {
            _isColumnHeaderDragging = false;
            _prepareColumnHeaderDragging = false;
            _draggingSrcColumnHeader = null;
            if (_columnHeaderDragIndicator != null)
            {
                RemoveVisualChild(_columnHeaderDragIndicator);
                _columnHeaderDragIndicator = null;
            }

            if (_columnHeaderDropLocationIndicator != null)
            {
                RemoveVisualChild(_columnHeaderDropLocationIndicator);
                _columnHeaderDropLocationIndicator = null;
            }
        }

        /// <summary>
        ///     Method which prepares the state for the start of column header drag
        /// </summary>
        private void PrepareColumnHeaderDrag(DataGridColumnHeader header, Point pos, Point relativePos)
        {
            _prepareColumnHeaderDragging = true;
            _isColumnHeaderDragging = false;
            _draggingSrcColumnHeader = header;
            _columnHeaderDragStartPosition = pos;
            _columnHeaderDragStartRelativePosition = relativePos;
        }

        /// <summary>
        ///     Method which checks if mouse move is sufficient to start the drag
        /// </summary>
        private static bool CheckStartColumnHeaderDrag(Point currentPos, Point originalPos)
        {
            return DoubleUtil.GreaterThan(Math.Abs(currentPos.X - originalPos.X), SystemParameters.MinimumHorizontalDragDistance);
        }

        /// <summary>
        ///     Method which checks during and after the drag if the position is valid for the drop
        /// </summary>
        private bool IsMousePositionValidForColumnDrag(double dragFactor)
        {
            int nearestDisplayIndex = -1;
            return IsMousePositionValidForColumnDrag(dragFactor, out nearestDisplayIndex);
        }

        /// <summary>
        ///     Method which checks during and after the drag if the position is valid for the drop and returns the drop display index
        /// </summary>
        private bool IsMousePositionValidForColumnDrag(double dragFactor, out int nearestDisplayIndex)
        {
            nearestDisplayIndex = -1;
            bool isDraggingColumnFrozen = false;
            if (_draggingSrcColumnHeader.Column != null)
            {
                isDraggingColumnFrozen = _draggingSrcColumnHeader.Column.IsFrozen;
            }

            int frozenCount = 0;
            if (ParentDataGrid != null)
            {
                frozenCount = ParentDataGrid.FrozenColumnCount;
            }

            nearestDisplayIndex = FindDisplayIndexByPosition(_columnHeaderDragCurrentPosition, true);
            if (isDraggingColumnFrozen && nearestDisplayIndex >= frozenCount)
            {
                return false;
            }

            if (!isDraggingColumnFrozen && nearestDisplayIndex < frozenCount)
            {
                return false;
            }

            double height = 0.0;

            if (_columnHeaderDragIndicator == null)
            {
                height = _draggingSrcColumnHeader.RenderSize.Height;
            }
            else
            {
                height = Math.Max(_draggingSrcColumnHeader.RenderSize.Height, _columnHeaderDragIndicator.Height);
            }

            return DoubleUtil.LessThanOrClose(-height * dragFactor, _columnHeaderDragCurrentPosition.Y) &&
                   DoubleUtil.LessThanOrClose(_columnHeaderDragCurrentPosition.Y, height * (dragFactor + 1));
        }

        /// <summary>
        ///     Method which start the column header drag. Includes raising events and creating default ghosts
        /// </summary>
        private void StartColumnHeaderDrag()
        {
            Debug.Assert(ParentDataGrid != null, "ParentDataGrid is null");

            _columnHeaderDragStartPosition = _columnHeaderDragCurrentPosition;
            DragStartedEventArgs dragStartedEventArgs = new DragStartedEventArgs(_columnHeaderDragStartPosition.X, _columnHeaderDragStartPosition.Y);
            ParentDataGrid.OnColumnHeaderDragStarted(dragStartedEventArgs);

            DataGridColumnReorderingEventArgs reorderingEventArgs = new DataGridColumnReorderingEventArgs(_draggingSrcColumnHeader.Column);

            _columnHeaderDragIndicator = CreateColumnHeaderDragIndicator();
            _columnHeaderDropLocationIndicator = CreateColumnHeaderDropIndicator();

            reorderingEventArgs.DragIndicator = _columnHeaderDragIndicator;
            reorderingEventArgs.DropLocationIndicator = _columnHeaderDropLocationIndicator;
            ParentDataGrid.OnColumnReordering(reorderingEventArgs);

            if (!reorderingEventArgs.Cancel)
            {
                _isColumnHeaderDragging = true;
                _columnHeaderDragIndicator = reorderingEventArgs.DragIndicator;
                _columnHeaderDropLocationIndicator = reorderingEventArgs.DropLocationIndicator;

                if (_columnHeaderDragIndicator != null)
                {
                    SetDefaultsOnDragIndicator();
                    AddVisualChild(_columnHeaderDragIndicator);
                }

                if (_columnHeaderDropLocationIndicator != null)
                {
                    SetDefaultsOnDropIndicator();
                    AddVisualChild(_columnHeaderDropLocationIndicator);
                }

                _draggingSrcColumnHeader.SuppressClickEvent = true;
                InvalidateMeasure();
            }
            else
            {
                FinishColumnHeaderDrag(true);
            }
        }

        /// <summary>
        ///     Method which returns a default control for column header drag indicator
        /// </summary>
        private Control CreateColumnHeaderDragIndicator()
        {
            Debug.Assert(_draggingSrcColumnHeader != null, "Dragging header is null");

            DataGridColumnFloatingHeader floatingHeader = new DataGridColumnFloatingHeader();
            floatingHeader.ReferenceHeader = _draggingSrcColumnHeader;
            return floatingHeader;
        }

        /// <summary>
        ///     Method which set the default values on drag indicator
        /// </summary>
        private void SetDefaultsOnDragIndicator()
        {
            Debug.Assert(_columnHeaderDragIndicator != null, "Drag indicator is null");
            Debug.Assert(_draggingSrcColumnHeader != null, "Dragging header is null");
            DataGridColumn column = _draggingSrcColumnHeader.Column;
            Style style = null;
            if (column != null)
            {
                style = column.DragIndicatorStyle;
            }

            _columnHeaderDragIndicator.Style = style;
            _columnHeaderDragIndicator.CoerceValue(WidthProperty);
            _columnHeaderDragIndicator.CoerceValue(HeightProperty);
        }

        /// <summary>
        ///     Method which returns the default control for the column header drop indicator
        /// </summary>
        private Control CreateColumnHeaderDropIndicator()
        {
            Debug.Assert(_draggingSrcColumnHeader != null, "Dragging header is null");

            DataGridColumnDropSeparator indicator = new DataGridColumnDropSeparator();
            indicator.ReferenceHeader = _draggingSrcColumnHeader;
            return indicator;
        }

        /// <summary>
        ///     Method which sets the default values on drop indicator
        /// </summary>
        private void SetDefaultsOnDropIndicator()
        {
            Debug.Assert(_columnHeaderDropLocationIndicator != null, "Drag indicator is null");
            Debug.Assert(_draggingSrcColumnHeader != null, "Dragging header is null");
            Style style = null;
            if (ParentDataGrid != null)
            {
                style = ParentDataGrid.DropLocationIndicatorStyle;
            }

            _columnHeaderDropLocationIndicator.Style = style;
            _columnHeaderDropLocationIndicator.CoerceValue(WidthProperty);
            _columnHeaderDropLocationIndicator.CoerceValue(HeightProperty);
        }

        /// <summary>
        ///     Method which completes the column header drag. Includes raising of events and changing column display index if needed.
        /// </summary>
        private void FinishColumnHeaderDrag(bool isCancel)
        {
            Debug.Assert(ParentDataGrid != null, "ParentDataGrid is null");
            _prepareColumnHeaderDragging = false;
            _isColumnHeaderDragging = false;

            _draggingSrcColumnHeader.SuppressClickEvent = false;

            if (_columnHeaderDragIndicator != null)
            {
                _columnHeaderDragIndicator.Visibility = Visibility.Collapsed;
                DataGridColumnFloatingHeader floatingHeader = _columnHeaderDragIndicator as DataGridColumnFloatingHeader;
                if (floatingHeader != null)
                {
                    floatingHeader.ClearHeader();
                }

                RemoveVisualChild(_columnHeaderDragIndicator);
            }

            if (_columnHeaderDropLocationIndicator != null)
            {
                _columnHeaderDropLocationIndicator.Visibility = Visibility.Collapsed;
                DataGridColumnDropSeparator separator = _columnHeaderDropLocationIndicator as DataGridColumnDropSeparator;
                if (separator != null)
                {
                    separator.ReferenceHeader = null;
                }

                RemoveVisualChild(_columnHeaderDropLocationIndicator);
            }

            DragCompletedEventArgs dragCompletedEventArgs = new DragCompletedEventArgs(
                _columnHeaderDragCurrentPosition.X - _columnHeaderDragStartPosition.X,
                _columnHeaderDragCurrentPosition.Y - _columnHeaderDragStartPosition.Y,
                isCancel);

            ParentDataGrid.OnColumnHeaderDragCompleted(dragCompletedEventArgs);
            _draggingSrcColumnHeader.InvalidateArrange();

            if (!isCancel)
            {
                int newDisplayIndex = -1;
                bool dragEndPositionValid = IsMousePositionValidForColumnDrag(
                    2.0,
                    out newDisplayIndex);

                DataGridColumn column = _draggingSrcColumnHeader.Column;
                if (column != null && dragEndPositionValid && newDisplayIndex != column.DisplayIndex)
                {
                    column.DisplayIndex = newDisplayIndex;

                    DataGridColumnEventArgs columnEventArgs = new DataGridColumnEventArgs(_draggingSrcColumnHeader.Column);
                    ParentDataGrid.OnColumnReordered(columnEventArgs);
                }
            }

            _draggingSrcColumnHeader = null;
            _columnHeaderDragIndicator = null;
            _columnHeaderDropLocationIndicator = null;
        }

        /// <summary>
        ///     Helper method to determine the display index based on the given position
        /// </summary>
        private int FindDisplayIndexByPosition(Point startPos, bool findNearestColumn)
        {
            Point headerPos;
            int displayIndex;
            DataGridColumnHeader header;
            FindDisplayIndexAndHeaderPosition(startPos, findNearestColumn, out displayIndex, out headerPos, out header);
            return displayIndex;
        }

        /// <summary>
        ///     Helper method to determine the column header based on the given position
        /// </summary>
        private DataGridColumnHeader FindColumnHeaderByPosition(Point startPos)
        {
            Point headerPos;
            int displayIndex;
            DataGridColumnHeader header;
            FindDisplayIndexAndHeaderPosition(startPos, false, out displayIndex, out headerPos, out header);
            return header;
        }

        /// <summary>
        ///     Helper method to determine the position of drop indicator based on the given mouse position
        /// </summary>
        private Point FindColumnHeaderPositionByCurrentPosition(Point startPos, bool findNearestColumn)
        {
            Point headerPos;
            int displayIndex;
            DataGridColumnHeader header;
            FindDisplayIndexAndHeaderPosition(startPos, findNearestColumn, out displayIndex, out headerPos, out header);
            return headerPos;
        }

        /// <summary>
        ///     Helper method which estimates the column width
        /// </summary>
        private static double GetColumnEstimatedWidth(DataGridColumn column, double averageColumnWidth)
        {
            double columnEstimatedWidth = column.Width.DisplayValue;
            if (double.IsNaN(columnEstimatedWidth))
            {
                columnEstimatedWidth = Math.Max(averageColumnWidth, column.MinWidth);
                columnEstimatedWidth = Math.Min(columnEstimatedWidth, column.MaxWidth);
            }

            return columnEstimatedWidth;
        }

        /// <summary>
        ///     Helper method to find display index, header and header start position based on given mouse position
        /// </summary>
        private void FindDisplayIndexAndHeaderPosition(Point startPos, bool findNearestColumn, out int displayIndex, out Point headerPos, out DataGridColumnHeader header)
        {
            Debug.Assert(ParentDataGrid != null, "ParentDataGrid is null");

            Point originPoint = new Point(0, 0);
            headerPos = originPoint;
            displayIndex = -1;
            header = null;

            if (startPos.X < 0.0)
            {
                if (findNearestColumn)
                {
                    displayIndex = 0;
                }

                return;
            }

            double headerStartX = 0.0;
            double headerEndX = 0.0;
            int i = 0;
            DataGrid dataGrid = ParentDataGrid;
            double averageColumnWidth = dataGrid.InternalColumns.AverageColumnWidth;
            bool firstVisibleNonFrozenColumnHandled = false;
            for (i = 0; i < dataGrid.Columns.Count; i++)
            {
                displayIndex++;
                DataGridColumnHeader currentHeader = dataGrid.ColumnHeaderFromDisplayIndex(i);
                if (currentHeader == null)
                {
                    DataGridColumn column = dataGrid.ColumnFromDisplayIndex(i);
                    if (!column.IsVisible)
                    {
                        continue;
                    }
                    else
                    {
                        headerStartX = headerEndX;
                        if (i >= dataGrid.FrozenColumnCount &&
                            !firstVisibleNonFrozenColumnHandled)
                        {
                            headerStartX -= dataGrid.HorizontalScrollOffset;
                            firstVisibleNonFrozenColumnHandled = true;
                        }

                        headerEndX = headerStartX + GetColumnEstimatedWidth(column, averageColumnWidth);
                    }
                }
                else
                {
                    GeneralTransform transform = currentHeader.TransformToAncestor(this);
                    headerStartX = transform.Transform(originPoint).X;
                    headerEndX = headerStartX + currentHeader.RenderSize.Width;
                }

                if (DoubleUtil.LessThanOrClose(startPos.X, headerStartX))
                {
                    break;
                }

                if (DoubleUtil.GreaterThanOrClose(startPos.X, headerStartX) &&
                    DoubleUtil.LessThanOrClose(startPos.X, headerEndX))
                {
                    if (findNearestColumn)
                    {
                        double headerMidX = (headerStartX + headerEndX) * 0.5;
                        if (DoubleUtil.GreaterThanOrClose(startPos.X, headerMidX))
                        {
                            headerStartX = headerEndX;
                            displayIndex++;
                        }

                        if (_draggingSrcColumnHeader != null && _draggingSrcColumnHeader.Column != null && _draggingSrcColumnHeader.Column.DisplayIndex < displayIndex)
                        {
                            displayIndex--;
                        }
                    }
                    else
                    {
                        header = currentHeader;
                    }

                    break;
                }
            }

            if (i == dataGrid.Columns.Count)
            {
                displayIndex = dataGrid.Columns.Count - 1;
                headerStartX = headerEndX;
            }

            headerPos.X = headerStartX;
            return;
        }

        #endregion

        #region Helpers

        private DataGridColumnHeaderCollection HeaderCollection
        {
            get
            {
                return ItemsSource as DataGridColumnHeaderCollection;
            }
        }

        internal DataGrid ParentDataGrid
        {
            get
            {
                if (_parentDataGrid == null)
                {
                    _parentDataGrid = DataGridHelper.FindParent<DataGrid>(this);
                }

                return _parentDataGrid;
            }
        }

        internal ContainerTracking<DataGridColumnHeader> HeaderTrackingRoot
        {
            get
            {
                return _headerTrackingRoot;
            }
        }

        #endregion

        #region Data

        private ContainerTracking<DataGridColumnHeader> _headerTrackingRoot;

        private DataGrid _parentDataGrid = null;

        private bool _prepareColumnHeaderDragging;
        private bool _isColumnHeaderDragging;
        private DataGridColumnHeader _draggingSrcColumnHeader;
        private Point _columnHeaderDragStartPosition;
        private Point _columnHeaderDragStartRelativePosition;
        private Point _columnHeaderDragCurrentPosition;
        private Control _columnHeaderDropLocationIndicator;
        private Control _columnHeaderDragIndicator;

        private Panel _internalItemsHost;

        #endregion
    }
}
