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


using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using MS.Internal;

namespace System.Windows.Controls
{
    /// <summary>
    ///     Panel that lays out both cells and column headers. This stacks cells in the horizontal direction and communicates with the
    ///     relevant DataGridColumn to ensure all rows give cells in a given column the same size.
    ///     It is hardcoded against DataGridCell and DataGridColumnHeader.
    /// </summary>
    public class DataGridCellsPanel : VirtualizingPanel
    {
        #region Constructors

        static DataGridCellsPanel()
        {
            KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(DataGridCellsPanel), new FrameworkPropertyMetadata(KeyboardNavigationMode.Local));
        }

        public DataGridCellsPanel()
        {
            IsVirtualizing = false;
            InRecyclingMode = false;
        }

        #endregion

        #region Measure

        /// <summary>
        ///     Measure
        ///
        ///     The logic is to see determinition of realized blocks is needed and do it.
        ///     If not, iterate over the realized block list and for each block generate the
        ///     children and measure them.
        /// </summary>
        /// <param name="constraint">Size constraint</param>
        /// <returns></returns>
        protected override Size MeasureOverride(Size constraint)
        {
            Size measureSize = new Size();

            DetermineVirtualizationState();

            // Acessing this property will initilize things appropriately,
            // both for virtualization and non-virtualization cases
            EnsureRealizedChildren();
            IList children = RealizedChildren;

            if (RebuildRealizedColumnsBlockList)
            {
                measureSize = DetermineRealizedColumnsBlockList(constraint);
            }
            else
            {
                // This is the case of remaining cells panels which directly use the list
                // in parent rows presenter for virtualization, rather than doing the
                // computation themselves.
                measureSize = GenerateAndMeasureChildrenForRealizedColumns(constraint);
            }

            // Disconnect any left over recycled children
            if (IsVirtualizing && InRecyclingMode)
            {
                DisconnectRecycledContainers();
            }

            if (!DoubleUtil.AreClose(this.DesiredSize, measureSize) && MeasureDuringArrange)
            {
                // This makes sure that the ItemsPresenter and the DatagridCellsPresenter is invalidated even if this is an arrange pass.
                this.ParentPresenter.InvalidateMeasure();
                UIElement parent =  VisualTreeHelper.GetParent(this) as UIElement;
                if (parent != null)
                    parent.InvalidateMeasure();
            }

            return measureSize;
        }

        /// <summary>
        ///     Method which measures a given child based on its column width
        ///
        ///     For auto kind columns it may actually end up measuring twice
        ///     once with positive infinity to determine its actual desired width
        ///     and then again with the available space constraints
        /// </summary>
        private static void MeasureChild(UIElement child, Size constraint)
        {
            IProvideDataGridColumn cell = child as IProvideDataGridColumn;
            bool isColumnHeader = (child is DataGridColumnHeader);
            Size childMeasureConstraint = new Size(double.PositiveInfinity, constraint.Height);

            double desiredWidth = 0.0;
            bool remeasure = false;

            // Allow the column to affect the constraint.
            if (cell != null)
            {
                // For auto kind columns measure with infinity to find the actual desired width of the cell.
                DataGridColumn column = cell.Column;
                DataGridLength width = column.Width;
                if (width.IsAuto ||
                    (width.IsSizeToHeader && isColumnHeader) ||
                    (width.IsSizeToCells && !isColumnHeader))
                {
                    child.Measure(childMeasureConstraint);
                    desiredWidth = child.DesiredSize.Width;
                    remeasure = true;
                }

                childMeasureConstraint.Width = column.GetConstraintWidth(isColumnHeader);
            }

            if (DoubleUtil.AreClose(desiredWidth, 0.0))
            {
                child.Measure(childMeasureConstraint);
            }

            Size childDesiredSize = child.DesiredSize;

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

                // Allow the column to process the desired size
                column.UpdateDesiredWidthForAutoColumn(
                    isColumnHeader,
                    DoubleUtil.AreClose(desiredWidth, 0.0) ? childDesiredSize.Width : desiredWidth);

                // For auto kind columns measure again with display value if
                // the desired width is greater than display value.
                DataGridLength width = column.Width;
                if (remeasure &&
                    !double.IsNaN(width.DisplayValue) &&
                    DoubleUtil.GreaterThan(desiredWidth, width.DisplayValue))
                {
                    childMeasureConstraint.Width = width.DisplayValue;
                    child.Measure(childMeasureConstraint);
                }
            }
        }

        /// <summary>
        ///     Generates children and measures them based on RealizedColumnList of
        ///     ancestor rows presenter.
        /// </summary>
        private Size GenerateAndMeasureChildrenForRealizedColumns(Size constraint)
        {
            double measureWidth = 0.0;
            double measureHeight = 0.0;
            DataGrid parentDataGrid = ParentDataGrid;
            double averageColumnWidth = parentDataGrid.InternalColumns.AverageColumnWidth;
            IItemContainerGenerator generator = ItemContainerGenerator;

            List<RealizedColumnsBlock> blockList = RealizedColumnsBlockList;

            Debug.Assert(blockList != null, "RealizedColumnsBlockList shouldn't be null at this point.");

            // Virtualize the children which are not necessary
            VirtualizeChildren(blockList, generator);

            // Realize the required children
            if (blockList.Count > 0)
            {
                for (int i = 0, count = blockList.Count; i < count; i++)
                {
                    RealizedColumnsBlock rcb = blockList[i];
                    Size blockMeasureSize = GenerateChildren(
                        generator,
                        rcb.StartIndex,
                        rcb.EndIndex,
                        constraint);

                    measureWidth += blockMeasureSize.Width;
                    measureHeight = Math.Max(measureHeight, blockMeasureSize.Height);

                    if (i != count - 1)
                    {
                        RealizedColumnsBlock nextRcb = blockList[i + 1];
                        measureWidth += GetColumnEstimatedMeasureWidthSum(rcb.EndIndex + 1, nextRcb.StartIndex - 1, averageColumnWidth);
                    }
                }

                measureWidth += GetColumnEstimatedMeasureWidthSum(0, blockList[0].StartIndex - 1, averageColumnWidth);
                measureWidth += GetColumnEstimatedMeasureWidthSum(blockList[blockList.Count - 1].EndIndex + 1, parentDataGrid.Columns.Count - 1, averageColumnWidth);
            }
            else
            {
                measureWidth = 0.0;
            }

            return new Size(measureWidth, measureHeight);
        }

        /// <summary>
        ///     Method which determines the realized columns list and
        ///     stores it in ancestor rowpresenter which is to be used
        ///     by other cellpanels to virtualize without recomputation.
        ///     Simultaneously measures the children of this panel.
        ///
        ///     If the datagrid has star columns, then all cells for all
        ///     realized rows are generated.
        ///
        ///     For remaining case the logic is to iterate over columns of
        ///     datagrid in DisplayIndex order, as if one is actually arranging
        ///     them, to determine which of them actually fall in viewport.
        /// </summary>
        private Size DetermineRealizedColumnsBlockList(Size constraint)
        {
            List<int> realizedColumnIndices = new List<int>();
            List<int> realizedColumnDisplayIndices = new List<int>();
            Size measureSize = new Size();

            DataGrid parentDataGrid = ParentDataGrid;
            if (parentDataGrid == null)
            {
                return measureSize;
            }

            double horizontalOffset = parentDataGrid.HorizontalScrollOffset;
            double cellsPanelOffset = parentDataGrid.CellsPanelHorizontalOffset;    // indicates cellspanel's offset in a row
            double nextFrozenCellStart = horizontalOffset;                // indicates the start position for next frozen cell
            double nextNonFrozenCellStart = -cellsPanelOffset;            // indicates the start position for next non-frozen cell
            double viewportStartX = horizontalOffset - cellsPanelOffset;  // indicates the start of viewport with respect to coordinate system of cell panel
            int firstVisibleNonFrozenDisplayIndex = -1;
            int lastVisibleNonFrozenDisplayIndex = -1;

            double totalAvailableSpace = GetViewportWidth() - cellsPanelOffset;
            double allocatedSpace = 0.0;

            if (IsVirtualizing && DoubleUtil.LessThan(totalAvailableSpace, 0.0))
            {
                return measureSize;
            }

            bool hasStarColumns = parentDataGrid.InternalColumns.HasVisibleStarColumns;
            double averageColumnWidth = parentDataGrid.InternalColumns.AverageColumnWidth;
            bool invalidAverage = DoubleUtil.AreClose(averageColumnWidth, 0.0);
            bool notVirtualizing = !IsVirtualizing;
            bool generateAll = invalidAverage || hasStarColumns || notVirtualizing;
            int frozenColumnCount = parentDataGrid.FrozenColumnCount;
            int previousColumnIndex = -1;

            bool redeterminationNeeded = false;
            Size childSize;

            IItemContainerGenerator generator = ItemContainerGenerator;
            IDisposable generatorState = null;
            int childIndex = 0;

            try
            {
                for (int i = 0, count = parentDataGrid.Columns.Count; i < count; i++)
                {
                    DataGridColumn column = parentDataGrid.ColumnFromDisplayIndex(i);

                    if (!column.IsVisible)
                    {
                        continue;
                    }

                    // Dispose the generator state if the child generation is not in
                    // sequence either because of gaps in childs to be generated or
                    // due to mismatch in the order of column index and displayindex
                    int columnIndex = parentDataGrid.ColumnIndexFromDisplayIndex(i);
                    if (columnIndex != childIndex || previousColumnIndex != (columnIndex - 1))
                    {
                        childIndex = columnIndex;
                        if (generatorState != null)
                        {
                            generatorState.Dispose();
                            generatorState = null;
                        }
                    }
                    previousColumnIndex = columnIndex;

                    // Generate the child if the all the children are to be generated,
                    // initialize the child size.
                    if (generateAll)
                    {
                        if (null == GenerateChild(generator, constraint, column, ref generatorState, ref childIndex, out childSize))
                        {
                            break;
                        }
                    }
                    else
                    {
                        childSize = new Size(GetColumnEstimatedMeasureWidth(column, averageColumnWidth), 0.0);
                    }

                    if (notVirtualizing || hasStarColumns || DoubleUtil.LessThan(allocatedSpace, totalAvailableSpace))
                    {
                        // Frozen children are realized provided they are in viewport
                        if (i < frozenColumnCount)
                        {
                            if (!generateAll &&
                                null == GenerateChild(generator, constraint, column, ref generatorState, ref childIndex, out childSize))
                            {
                                break;
                            }

                            realizedColumnIndices.Add(columnIndex);
                            realizedColumnDisplayIndices.Add(i);
                            allocatedSpace += childSize.Width;
                            nextFrozenCellStart += childSize.Width;
                        }
                        else
                        {
                            if (DoubleUtil.LessThanOrClose(nextNonFrozenCellStart, viewportStartX))
                            {
                                // Non-Frozen children to the left of viewport are not realized,
                                // unless we are dealing with star columns.
                                if (DoubleUtil.LessThanOrClose(nextNonFrozenCellStart + childSize.Width, viewportStartX))
                                {
                                    if (generateAll)
                                    {
                                        if (notVirtualizing || hasStarColumns)
                                        {
                                            realizedColumnIndices.Add(columnIndex);
                                            realizedColumnDisplayIndices.Add(i);
                                        }
                                        else if (invalidAverage)
                                        {
                                            redeterminationNeeded = true;
                                        }
                                    }
                                    else if (generatorState != null)
                                    {
                                        generatorState.Dispose();
                                        generatorState = null;
                                    }

                                    nextNonFrozenCellStart += childSize.Width;
                                }
                                else
                                {
                                    // First visible non frozen child is realized
                                    if (!generateAll &&
                                        null == GenerateChild(generator, constraint, column, ref generatorState, ref childIndex, out childSize))
                                    {
                                        break;
                                    }

                                    double cellChoppedWidth = viewportStartX - nextNonFrozenCellStart;
                                    if (DoubleUtil.AreClose(cellChoppedWidth, 0.0))
                                    {
                                        nextNonFrozenCellStart = nextFrozenCellStart + childSize.Width;
                                        allocatedSpace += childSize.Width;
                                    }
                                    else
                                    {
                                        double clipWidth = childSize.Width - cellChoppedWidth;
                                        nextNonFrozenCellStart = nextFrozenCellStart + clipWidth;
                                        allocatedSpace += clipWidth;
                                    }

                                    realizedColumnIndices.Add(columnIndex);
                                    realizedColumnDisplayIndices.Add(i);
                                    firstVisibleNonFrozenDisplayIndex = i;
                                    lastVisibleNonFrozenDisplayIndex = i;
                                }
                            }
                            else
                            {
                                // All the remaining non-frozen children are realized provided they are in viewport
                                if (!generateAll &&
                                    null == GenerateChild(generator, constraint, column, ref generatorState, ref childIndex, out childSize))
                                {
                                    break;
                                }

                                if (firstVisibleNonFrozenDisplayIndex < 0)
                                {
                                    firstVisibleNonFrozenDisplayIndex = i;
                                }

                                lastVisibleNonFrozenDisplayIndex = i;
                                nextNonFrozenCellStart += childSize.Width;
                                allocatedSpace += childSize.Width;
                                realizedColumnIndices.Add(columnIndex);
                                realizedColumnDisplayIndices.Add(i);
                            }
                        }
                    }

                    measureSize.Width += childSize.Width;
                    measureSize.Height = Math.Max(measureSize.Height, childSize.Height);
                }
            }
            finally
            {
                if (generatorState != null)
                {
                    generatorState.Dispose();
                    generatorState = null;
                }
            }

            // If we are virtualizing and datagrid doesnt have any star columns
            // then ensure the focus trail for navigational purposes.
            if (!hasStarColumns && !notVirtualizing)
            {
                bool isColumnHeader = ParentPresenter is DataGridColumnHeadersPresenter;
                if (isColumnHeader)
                {
                    Size headerSize = EnsureAtleastOneHeader(generator, constraint, realizedColumnIndices, realizedColumnDisplayIndices);
                    measureSize.Height = Math.Max(measureSize.Height, headerSize.Height);
                    redeterminationNeeded = true;
                }
                else
                {
                    EnsureFocusTrail(realizedColumnIndices, realizedColumnDisplayIndices, firstVisibleNonFrozenDisplayIndex, lastVisibleNonFrozenDisplayIndex, constraint);
                }
            }

            UpdateRealizedBlockLists(realizedColumnIndices, realizedColumnDisplayIndices, redeterminationNeeded);

            // Virtualize the children which are determined to be unused
            VirtualizeChildren(RealizedColumnsBlockList, generator);

            return measureSize;
        }

        private void UpdateRealizedBlockLists(
            List<int> realizedColumnIndices,
            List<int> realizedColumnDisplayIndices,
            bool redeterminationNeeded)
        {
            realizedColumnIndices.Sort();

            // PERF: An option here is to apply some heuristics and add some indices
            // to optimize the generation by avoiding multiple generation sequences
            // with in a single generation sequence

            // Combine the realized indices into blocks, so that it is easy to use for later purposes
            RealizedColumnsBlockList = BuildRealizedColumnsBlockList(realizedColumnIndices);

            // PERF: Uncomment the statement below if needed once the heuristics
            // mentioned above are implemented
            // realizedColumnDisplayIndices.Sort();

            // Combine the realized disply indices into blocks, so that it is easy to use for later purposes
            RealizedColumnsDisplayIndexBlockList = BuildRealizedColumnsBlockList(realizedColumnDisplayIndices);

            if (!redeterminationNeeded)
            {
                RebuildRealizedColumnsBlockList = false;
            }
        }

        /// <summary>
        ///     Helper method which creates a list of RealizedColumnsBlock struct
        ///     out of a list on integer indices.
        /// </summary>
        private static List<RealizedColumnsBlock> BuildRealizedColumnsBlockList(List<int> indexList)
        {
            List<RealizedColumnsBlock> resultList = new List<RealizedColumnsBlock>();
            if (indexList.Count == 1)
            {
                resultList.Add(new RealizedColumnsBlock(indexList[0], indexList[0], 0));
            }
            else if (indexList.Count > 0)
            {
                int startIndex = indexList[0];
                for (int i = 1, count = indexList.Count; i < count; i++)
                {
                    if (indexList[i] != indexList[i - 1] + 1)
                    {
                        if (resultList.Count == 0)
                        {
                            resultList.Add(new RealizedColumnsBlock(startIndex, indexList[i - 1], 0));
                        }
                        else
                        {
                            RealizedColumnsBlock lastRealizedColumnsBlock = resultList[resultList.Count - 1];
                            int startIndexOffset = lastRealizedColumnsBlock.StartIndexOffset + lastRealizedColumnsBlock.EndIndex - lastRealizedColumnsBlock.StartIndex + 1;
                            resultList.Add(new RealizedColumnsBlock(startIndex, indexList[i - 1], startIndexOffset));
                        }

                        startIndex = indexList[i];
                    }

                    if (i == count - 1)
                    {
                        if (resultList.Count == 0)
                        {
                            resultList.Add(new RealizedColumnsBlock(startIndex, indexList[i], 0));
                        }
                        else
                        {
                            RealizedColumnsBlock lastRealizedColumnsBlock = resultList[resultList.Count - 1];
                            int startIndexOffset = lastRealizedColumnsBlock.StartIndexOffset + lastRealizedColumnsBlock.EndIndex - lastRealizedColumnsBlock.StartIndex + 1;
                            resultList.Add(new RealizedColumnsBlock(startIndex, indexList[i], startIndexOffset));
                        }
                    }
                }
            }

            return resultList;
        }

        /// <summary>
        ///     Helper method to build generator position out to index
        /// </summary>
        private static GeneratorPosition IndexToGeneratorPositionForStart(IItemContainerGenerator generator, int index, out int childIndex)
        {
            GeneratorPosition position = (generator != null) ? generator.GeneratorPositionFromIndex(index) : new GeneratorPosition(-1, index + 1);

            // Determine the position in the children collection for the first
            // generated container.  This assumes that generator.StartAt will be called
            // with direction=Forward and allowStartAtRealizedItem=true.
            childIndex = (position.Offset == 0) ? position.Index : position.Index + 1;

            return position;
        }

        /// <summary>
        ///     Helper method which generates and measures a
        ///     child of given index
        /// </summary>
        private UIElement GenerateChild(
            IItemContainerGenerator generator,
            Size constraint,
            DataGridColumn column,
            ref IDisposable generatorState,
            ref int childIndex,
            out Size childSize)
        {
            if (generatorState == null)
            {
                generatorState = generator.StartAt(IndexToGeneratorPositionForStart(generator, childIndex, out childIndex), GeneratorDirection.Forward, true);
            }

            return GenerateChild(generator, constraint, column, ref childIndex, out childSize);
        }

        /// <summary>
        ///     Helper method which generates and measures a
        ///     child of given index
        /// </summary>
        private UIElement GenerateChild(
            IItemContainerGenerator generator,
            Size constraint,
            DataGridColumn column,
            ref int childIndex,
            out Size childSize)
        {
            bool newlyRealized;
            UIElement child = generator.GenerateNext(out newlyRealized) as UIElement;
            if (child == null)
            {
                childSize = new Size();
                return null;
            }

            AddContainerFromGenerator(childIndex, child, newlyRealized);
            childIndex++;

            MeasureChild(child, constraint);

            DataGridLength width = column.Width;
            childSize = child.DesiredSize;
            if (!double.IsNaN(width.DisplayValue))
            {
                childSize = new Size(width.DisplayValue, childSize.Height);
            }

            return child;
        }

        /// <summary>
        ///     Helper method which generates and measures children of
        ///     a given block of indices
        /// </summary>
        private Size GenerateChildren(
            IItemContainerGenerator generator,
            int startIndex,
            int endIndex,
            Size constraint)
        {
            double measureWidth = 0.0;
            double measureHeight = 0.0;
            int childIndex;
            GeneratorPosition startPos = IndexToGeneratorPositionForStart(generator, startIndex, out childIndex);
            DataGrid parentDataGrid = ParentDataGrid;
            using (generator.StartAt(startPos, GeneratorDirection.Forward, true))
            {
                Size childSize;
                for (int i = startIndex; i <= endIndex; i++)
                {
                    if (!parentDataGrid.Columns[i].IsVisible)
                    {
                        continue;
                    }

                    if (null == GenerateChild(generator, constraint, parentDataGrid.Columns[i], ref childIndex, out childSize))
                    {
                        return new Size(measureWidth, measureHeight);
                    }

                    measureWidth += childSize.Width;
                    measureHeight = Math.Max(measureHeight, childSize.Height);
                }
            }

            return new Size(measureWidth, measureHeight);
        }

        /// <summary>
        ///     Method which adds the given container at the given index
        /// </summary>
        private void AddContainerFromGenerator(int childIndex, UIElement child, bool newlyRealized)
        {
            if (!newlyRealized)
            {
                // Container is either realized or recycled.  If it's realized do nothing; it already exists in the visual
                // tree in the proper place.
                if (InRecyclingMode)
                {
                    IList children = RealizedChildren;

                    if (childIndex >= children.Count || !(children[childIndex] == child))
                    {
                        Debug.Assert(!children.Contains(child), "we incorrectly identified a recycled container");

                        // We have a recycled container (if it was a realized container it would have been returned in the
                        // proper location).  Note also that recycled containers are NOT in the _realizedChildren list.
                        InsertRecycledContainer(childIndex, child);
                        child.Measure(new Size());
                    }
                    else
                    {
                        // previously realized child, so do nothing.
                    }
                }
                else
                {
                    // Not recycling; realized container
                    Debug.Assert(child == InternalChildren[childIndex], "Wrong child was generated");
                }
            }
            else
            {
                InsertNewContainer(childIndex, child);
            }
        }

        /// <summary>
        ///     Inserts a recycled container in the visual tree
        /// </summary>
        private void InsertRecycledContainer(int childIndex, UIElement container)
        {
            InsertContainer(childIndex, container, true);
        }

        /// <summary>
        ///     Inserts a new container in the visual tree
        /// </summary>
        private void InsertNewContainer(int childIndex, UIElement container)
        {
            InsertContainer(childIndex, container, false);
        }

        /// <summary>
        ///     Inserts a container into the Children collection.  The container is either new or recycled.
        /// </summary>
        private void InsertContainer(int childIndex, UIElement container, bool isRecycled)
        {
            Debug.Assert(container != null, "Null container was generated");

            UIElementCollection children = InternalChildren;

            // Find the index in the Children collection where we hope to insert the container.
            // This is done by looking up the index of the container BEFORE the one we hope to insert.
            //
            // We have to do it this way because there could be recycled containers between the container we're looking for and the one before it.
            // By finding the index before the place we want to insert and adding one, we ensure that we'll insert the new container in the
            // proper location.
            //
            // In recycling mode childIndex is the index in the _realizedChildren list, not the index in the
            // Children collection.  We have to convert the index; we'll call the index in the Children collection
            // the visualTreeIndex.
            int visualTreeIndex = 0;

            if (childIndex > 0)
            {
                visualTreeIndex = ChildIndexFromRealizedIndex(childIndex - 1);
                visualTreeIndex++;
            }

            if (isRecycled && visualTreeIndex < children.Count && children[visualTreeIndex] == container)
            {
                // Don't insert if a recycled container is in the proper place already
            }
            else
            {
                if (visualTreeIndex < children.Count)
                {
                    int insertIndex = visualTreeIndex;
                    if (isRecycled && VisualTreeHelper.GetParent(container) != null)
                    {
                        // If the container is recycled we have to remove it from its place in the visual tree and
                        // insert it in the proper location.   We cant use an internal Move api, so we are removing
                        // and inserting the container
                        Debug.Assert(children[visualTreeIndex] != null, "MoveVisualChild interprets a null destination as 'move to end'");
                        int containerIndex = children.IndexOf(container);
                        RemoveInternalChildRange(containerIndex, 1);
                        if (containerIndex < insertIndex)
                        {
                            insertIndex--;
                        }

                        InsertInternalChild(insertIndex, container);
                    }
                    else
                    {
                        InsertInternalChild(insertIndex, container);
                    }
                }
                else
                {
                    if (isRecycled && VisualTreeHelper.GetParent(container) != null)
                    {
                        // Recycled container is still in the tree; move it to the end
                        int originalIndex = children.IndexOf(container);
                        RemoveInternalChildRange(originalIndex, 1);
                        AddInternalChild(container);
                    }
                    else
                    {
                        AddInternalChild(container);
                    }
                }
            }

            // Keep realizedChildren in sync w/ the visual tree.
            if (IsVirtualizing && InRecyclingMode)
            {
                _realizedChildren.Insert(childIndex, container);
            }

            ItemContainerGenerator.PrepareItemContainer(container);
        }

        /// <summary>
        ///     Takes an index from the realized list and returns the corresponding index in the Children collection
        /// </summary>
        private int ChildIndexFromRealizedIndex(int realizedChildIndex)
        {
            // If we're not recycling containers then we're not using a realizedChild index and no translation is necessary
            if (IsVirtualizing && InRecyclingMode)
            {
                if (realizedChildIndex < _realizedChildren.Count)
                {
                    UIElement child = _realizedChildren[realizedChildIndex];
                    UIElementCollection children = InternalChildren;

                    for (int i = realizedChildIndex; i < children.Count; i++)
                    {
                        if (children[i] == child)
                        {
                            return i;
                        }
                    }

                    Debug.Assert(false, "We should have found a child");
                }
            }

            return realizedChildIndex;
        }

        /// <summary>
        ///     Helper method which determines if the given in index
        ///     falls in the given block or in the next block
        /// </summary>
        private static bool InBlockOrNextBlock(List<RealizedColumnsBlock> blockList, int index, ref int blockIndex, ref RealizedColumnsBlock block, out bool pastLastBlock)
        {
            pastLastBlock = false;
            bool exists = true;
            if (index < block.StartIndex)
            {
                exists = false;
            }
            else if (index > block.EndIndex)
            {
                if (blockIndex == blockList.Count - 1)
                {
                    blockIndex++;
                    pastLastBlock = true;
                    exists = false;
                }
                else
                {
                    block = blockList[++blockIndex];
                    if (index < block.StartIndex ||
                        index > block.EndIndex)
                    {
                        exists = false;
                    }
                }
            }

            return exists;
        }

        /// <summary>
        ///     Method which ensures that atleast one column
        ///     header is generated. Such a generation would
        ///     help in determination of the height.
        /// </summary>
        private Size EnsureAtleastOneHeader(IItemContainerGenerator generator,
            Size constraint,
            List<int> realizedColumnIndices,
            List<int> realizedColumnDisplayIndices)
        {
            DataGrid parentDataGrid = ParentDataGrid;
            int columnCount = parentDataGrid.Columns.Count;
            Size childSize = new Size();
            if (RealizedChildren.Count == 0 && columnCount > 0)
            {
                for (int i = 0; i < columnCount; i++)
                {
                    DataGridColumn column = parentDataGrid.Columns[i];
                    if (column.IsVisible)
                    {
                        int childIndex = i;
                        using (generator.StartAt(IndexToGeneratorPositionForStart(generator, childIndex, out childIndex), GeneratorDirection.Forward, true))
                        {
                            UIElement child = GenerateChild(generator, constraint, column, ref childIndex, out childSize);
                            if (child != null)
                            {
                                int displayIndexListIterator = 0;
                                AddToIndicesListIfNeeded(
                                    realizedColumnIndices,
                                    realizedColumnDisplayIndices,
                                    i,
                                    column.DisplayIndex,
                                    ref displayIndexListIterator);
                                return childSize;
                            }
                        }
                    }
                }
            }
            return childSize;
        }

        /// <summary>
        ///     Method which ensures that all the appropriate
        ///     focus trail cells are realized such that tabbing
        ///     works.
        /// </summary>
        private void EnsureFocusTrail(
            List<int> realizedColumnIndices,
            List<int> realizedColumnDisplayIndices,
            int firstVisibleNonFrozenDisplayIndex,
            int lastVisibleNonFrozenDisplayIndex,
            Size constraint)
        {
            if (firstVisibleNonFrozenDisplayIndex < 0)
            {
                // Non frozen columns can never be brought into viewport.
                // Hence tabbing is supported only among visible frozen cells
                // which should already be realized.
                return;
            }

            int frozenColumnCount = ParentDataGrid.FrozenColumnCount;
            int columnCount = Columns.Count;
            ItemsControl parentPresenter = ParentPresenter;
            if (parentPresenter == null)
            {
                return;
            }

            ItemContainerGenerator generator = parentPresenter.ItemContainerGenerator;
            int displayIndexListIterator = 0;
            int previousFocusTrailIndex = -1;

            // Realizing the child for first visible column
            for (int i = 0; i < firstVisibleNonFrozenDisplayIndex; i++)
            {
                if (GenerateChildForFocusTrail(generator, realizedColumnIndices, realizedColumnDisplayIndices, constraint, i, ref displayIndexListIterator))
                {
                    previousFocusTrailIndex = i;
                    break;
                }
            }

            // Realizing the child for first non-frozen column
            if (previousFocusTrailIndex < frozenColumnCount)
            {
                for (int i = frozenColumnCount; i < columnCount; i++)
                {
                    if (GenerateChildForFocusTrail(generator, realizedColumnIndices, realizedColumnDisplayIndices, constraint, i, ref displayIndexListIterator))
                    {
                        previousFocusTrailIndex = i;
                        break;
                    }
                }
            }

            // Realizing the preceding child of first visible non-frozen column
            for (int i = firstVisibleNonFrozenDisplayIndex - 1; i > previousFocusTrailIndex; i--)
            {
                if (GenerateChildForFocusTrail(generator, realizedColumnIndices, realizedColumnDisplayIndices, constraint, i, ref displayIndexListIterator))
                {
                    previousFocusTrailIndex = i;
                    break;
                }
            }

            // Realizing the suceeding child of last visible non-frozen column
            for (int i = lastVisibleNonFrozenDisplayIndex + 1; i < columnCount; i++)
            {
                if (GenerateChildForFocusTrail(generator, realizedColumnIndices, realizedColumnDisplayIndices, constraint, i, ref displayIndexListIterator))
                {
                    previousFocusTrailIndex = i;
                    break;
                }
            }

            // Realizing the child for last column
            for (int i = columnCount - 1; i > previousFocusTrailIndex; i--)
            {
                if (GenerateChildForFocusTrail(generator, realizedColumnIndices, realizedColumnDisplayIndices, constraint, i, ref displayIndexListIterator))
                {
                    break;
                }
            }

            return;
        }

        /// <summary>
        ///     Method which generates the focus trail cell
        ///     if it is not already generated and adds it to
        ///     the block lists appropriately.
        /// </summary>
        private bool GenerateChildForFocusTrail(
            ItemContainerGenerator generator,
            List<int> realizedColumnIndices,
            List<int> realizedColumnDisplayIndices,
            Size constraint,
            int displayIndex,
            ref int displayIndexListIterator)
        {
            DataGrid dataGrid = ParentDataGrid;
            DataGridColumn column = dataGrid.ColumnFromDisplayIndex(displayIndex);
            if (column.IsVisible)
            {
                int columnIndex = dataGrid.ColumnIndexFromDisplayIndex(displayIndex);

                UIElement child = generator.ContainerFromIndex(columnIndex) as UIElement;
                if (child == null)
                {
                    int childIndex = columnIndex;
                    Size childSize;
                    using (((IItemContainerGenerator)generator).StartAt(IndexToGeneratorPositionForStart(generator, childIndex, out childIndex), GeneratorDirection.Forward, true))
                    {
                        child = GenerateChild(generator, constraint, column, ref childIndex, out childSize);
                    }
                }

                if (child != null && DataGridHelper.TreeHasFocusAndTabStop(child))
                {
                    AddToIndicesListIfNeeded(
                        realizedColumnIndices,
                        realizedColumnDisplayIndices,
                        columnIndex,
                        displayIndex,
                        ref displayIndexListIterator);
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        ///     Helper method which adds the generated
        ///     display index to the the block lists if not
        ///     already present.
        /// </summary>
        private static void AddToIndicesListIfNeeded(
            List<int> realizedColumnIndices,
            List<int> realizedColumnDisplayIndices,
            int columnIndex,
            int displayIndex,
            ref int displayIndexListIterator)
        {
            for (int count = realizedColumnDisplayIndices.Count; displayIndexListIterator < count; displayIndexListIterator++)
            {
                if (realizedColumnDisplayIndices[displayIndexListIterator] == displayIndex)
                {
                    return;
                }
                else if (realizedColumnDisplayIndices[displayIndexListIterator] > displayIndex)
                {
                    realizedColumnDisplayIndices.Insert(displayIndexListIterator, displayIndex);
                    realizedColumnIndices.Add(columnIndex);
                    return;
                }
            }

            realizedColumnIndices.Add(columnIndex);
            realizedColumnDisplayIndices.Add(displayIndex);
            return;
        }

        /// <summary>
        ///     Method which virtualizes the children which are determined to be unused.
        ///     Eligible candidates for virtualization are those which are not in block list.
        ///     Some exceptions to the criterion are the cells which are in edit mode, or
        ///     if item is its own container.
        /// </summary>
        private void VirtualizeChildren(List<RealizedColumnsBlock> blockList, IItemContainerGenerator generator)
        {
            DataGrid parentDataGrid = ParentDataGrid;
            ObservableCollection<DataGridColumn> columns = parentDataGrid.Columns;
            int columnCount = columns.Count;
            int columnIterator = 0;
            IList children = RealizedChildren;
            int childrenCount = children.Count;
            if (childrenCount == 0)
            {
                return;
            }

            int blockIndex = 0;
            int blockCount = blockList.Count;
            RealizedColumnsBlock block = (blockCount > 0 ? blockList[blockIndex] : new RealizedColumnsBlock(-1, -1, -1));
            bool pastLastBlock = (blockCount > 0 ? false : true);

            int cleanupRangeStart = -1;
            int cleanupCount = 0;
            int lastVirtualizedColumnIndex = -1;

            ItemsControl parentPresenter = ParentPresenter;
            DataGridCellsPresenter cellsPresenter = parentPresenter as DataGridCellsPresenter;
            DataGridColumnHeadersPresenter headersPresenter = parentPresenter as DataGridColumnHeadersPresenter;

            for (int i = 0; i < childrenCount; i++)
            {
                int columnIndex = i;
                UIElement child = children[i] as UIElement;
                IProvideDataGridColumn columnProvider = child as IProvideDataGridColumn;
                if (columnProvider != null)
                {
                    DataGridColumn column = columnProvider.Column;
                    for (; columnIterator < columnCount; columnIterator++)
                    {
                        if (column == columns[columnIterator])
                        {
                            break;
                        }
                    }

                    columnIndex = columnIterator++;
                    Debug.Assert(columnIndex < columnCount, "columnIndex should be less than column count");
                }

                bool virtualizeChild = pastLastBlock || !InBlockOrNextBlock(blockList, columnIndex, ref blockIndex, ref block, out pastLastBlock);

                DataGridCell cell = child as DataGridCell;
                if ((cell != null && (cell.IsEditing || cell.IsKeyboardFocusWithin || cell == parentDataGrid.FocusedCell)) ||
                    (cellsPresenter != null &&
                    cellsPresenter.IsItemItsOwnContainerInternal(cellsPresenter.Items[columnIndex])) ||
                    (headersPresenter != null &&
                    headersPresenter.IsItemItsOwnContainerInternal(headersPresenter.Items[columnIndex])))
                {
                    virtualizeChild = false;
                }

                if (!columns[columnIndex].IsVisible)
                {
                    virtualizeChild = true;
                }

                if (virtualizeChild)
                {
                    if (cleanupRangeStart == -1)
                    {
                        cleanupRangeStart = i;
                        cleanupCount = 1;
                    }
                    else if (lastVirtualizedColumnIndex == columnIndex - 1)
                    {
                        cleanupCount++;
                    }
                    else
                    {
                        // Meaning that two consecutive children to be virtualized are not corresponding to
                        // two consecutive columns
                        CleanupRange(children, generator, cleanupRangeStart, cleanupCount);
                        childrenCount -= cleanupCount;
                        i -= cleanupCount;
                        cleanupCount = 1;
                        cleanupRangeStart = i;
                    }

                    lastVirtualizedColumnIndex = columnIndex;
                }
                else
                {
                    if (cleanupCount > 0)
                    {
                        CleanupRange(children, generator, cleanupRangeStart, cleanupCount);
                        childrenCount -= cleanupCount;
                        i -= cleanupCount;
                        cleanupCount = 0;
                        cleanupRangeStart = -1;
                    }
                }
            }

            if (cleanupCount > 0)
            {
                CleanupRange(children, generator, cleanupRangeStart, cleanupCount);
            }
        }

        /// <summary>
        ///     Method which cleans up a given range of children
        /// </summary>
        private void CleanupRange(IList children, IItemContainerGenerator generator, int startIndex, int count)
        {
            if (count <= 0)
            {
                return;
            }

            if (IsVirtualizing && InRecyclingMode)
            {
                Debug.Assert(startIndex >= 0);
                Debug.Assert(children == _realizedChildren, "the given child list must be the _realizedChildren list when recycling");

                // Recycle and remove the children from realized list
                GeneratorPosition position = new GeneratorPosition(startIndex, 0);
                ((IRecyclingItemContainerGenerator)generator).Recycle(position, count);
                _realizedChildren.RemoveRange(startIndex, count);
            }
            else
            {
                // Remove the desired range of children
                RemoveInternalChildRange(startIndex, count);
                generator.Remove(new GeneratorPosition(startIndex, 0), count);
            }
        }

        /// <summary>
        ///     Recycled containers still in the InternalChildren collection at the end of Measure should be disconnected
        ///     from the visual tree.  Otherwise they're still visible to things like Arrange, keyboard navigation, etc.
        /// </summary>
        private void DisconnectRecycledContainers()
        {
            int realizedIndex = 0;
            UIElement visualChild;
            UIElement realizedChild = _realizedChildren.Count > 0 ? _realizedChildren[0] : null;
            UIElementCollection children = InternalChildren;

            int removeStartRange = -1;
            int removalCount = 0;
            for (int i = 0; i < children.Count; i++)
            {
                visualChild = children[i];

                if (visualChild == realizedChild)
                {
                    if (removalCount > 0)
                    {
                        RemoveInternalChildRange(removeStartRange, removalCount);
                        i -= removalCount;
                        removalCount = 0;
                        removeStartRange = -1;
                    }

                    realizedIndex++;

                    if (realizedIndex < _realizedChildren.Count)
                    {
                        realizedChild = _realizedChildren[realizedIndex];
                    }
                    else
                    {
                        realizedChild = null;
                    }
                }
                else
                {
                    if (removeStartRange == -1)
                    {
                        removeStartRange = i;
                    }

                    removalCount++;
                }
            }

            if (removalCount > 0)
            {
                RemoveInternalChildRange(removeStartRange, removalCount);
            }
        }

        #endregion

        #region Arrange

        /// <summary>
        ///     Private class used to maintain state between arrange
        ///     of multiple children.
        /// </summary>
        private class ArrangeState
        {
            public ArrangeState()
            {
                FrozenColumnCount = 0;
                ChildHeight = 0.0;
                NextFrozenCellStart = 0.0;
                NextNonFrozenCellStart = 0.0;
                ViewportStartX = 0.0;
                DataGridHorizontalScrollStartX = 0.0;
                OldClippedChild = null;
                NewClippedChild = null;
            }

            public int FrozenColumnCount
            {
                get; set;
            }

            public double ChildHeight
            {
                get; set;
            }

            public double NextFrozenCellStart
            {
                get; set;
            }

            public double NextNonFrozenCellStart
            {
                get; set;
            }

            public double ViewportStartX
            {
                get; set;
            }

            public double DataGridHorizontalScrollStartX
            {
                get; set;
            }

            public UIElement OldClippedChild
            {
                get; set;
            }

            public UIElement NewClippedChild
            {
                get; set;
            }
        }

        /// <summary>
        ///     Helper method to initialize the arrange state
        /// </summary>
        /// <param name="arrangeState"></param>
        private void InitializeArrangeState(ArrangeState arrangeState)
        {
            DataGrid parentDataGrid = ParentDataGrid;
            double horizontalOffset = parentDataGrid.HorizontalScrollOffset;
            double cellsPanelOffset = parentDataGrid.CellsPanelHorizontalOffset;
            arrangeState.NextFrozenCellStart = horizontalOffset;
            arrangeState.NextNonFrozenCellStart -= cellsPanelOffset;
            arrangeState.ViewportStartX = horizontalOffset - cellsPanelOffset;
            arrangeState.FrozenColumnCount = parentDataGrid.FrozenColumnCount;
        }

        /// <summary>
        ///     Helper method which which ends the arrange by setting values
        ///     from arrange state to appropriate fields.
        /// </summary>
        /// <param name="arrangeState"></param>
        private void FinishArrange(ArrangeState arrangeState)
        {
            DataGrid parentDataGrid = ParentDataGrid;

            // Update the NonFrozenColumnsViewportHorizontalOffset property of datagrid
            if (parentDataGrid != null)
            {
                parentDataGrid.NonFrozenColumnsViewportHorizontalOffset = arrangeState.DataGridHorizontalScrollStartX;
            }

            // Remove the clip on previous clipped child
            if (arrangeState.OldClippedChild != null)
            {
                arrangeState.OldClippedChild.CoerceValue(ClipProperty);
            }

            // Add the clip on new child to be clipped for the sake of frozen columns.
            _clippedChildForFrozenBehaviour = arrangeState.NewClippedChild;
            if (_clippedChildForFrozenBehaviour != null)
            {
                _clippedChildForFrozenBehaviour.CoerceValue(ClipProperty);
            }
        }

        private void SetDataGridCellPanelWidth(IList children, double newWidth)
        {
            if (children.Count != 0 &&
                   children[0] is DataGridColumnHeader &&
                   !DoubleUtil.AreClose(ParentDataGrid.CellsPanelActualWidth, newWidth))
            {
                // Set the CellsPanelActualWidth property of the datagrid
                ParentDataGrid.CellsPanelActualWidth = newWidth;
            }
        }

        [Conditional("DEBUG")]
        private static void Debug_VerifyRealizedIndexCountVsDisplayIndexCount(List<RealizedColumnsBlock> blockList, List<RealizedColumnsBlock> displayIndexBlockList)
        {
            Debug.Assert(
                blockList != null && blockList.Count > 0,
                "RealizedColumnsBlockList should not be null or empty");

            RealizedColumnsBlock lastBlock = blockList[blockList.Count - 1];
            RealizedColumnsBlock lastDisplayIndexBlock = displayIndexBlockList[displayIndexBlockList.Count - 1];
            Debug.Assert(
                (lastBlock.StartIndexOffset + lastBlock.EndIndex - lastBlock.StartIndex) == (lastDisplayIndexBlock.StartIndexOffset + lastDisplayIndexBlock.EndIndex - lastDisplayIndexBlock.StartIndex),
                "RealizedBlockList and DisplayIndex list should indicate same number of elements");
        }

        /// <summary>
        ///     Arrange
        ///
        ///     Iterates over the columns in the display index order and looks if
        ///     it the corresponding child is realized. If yes then arranges it.
        /// </summary>
        protected override Size ArrangeOverride(Size arrangeSize)
        {
            IList children = RealizedChildren;

            ArrangeState arrangeState = new ArrangeState();
            arrangeState.ChildHeight = arrangeSize.Height;
            DataGrid parentDataGrid = ParentDataGrid;

            /*
             * determine the horizontal offset, cells panel offset and other coordinates used for arrange of children
             */
            if (parentDataGrid != null)
            {
                parentDataGrid.QueueInvalidateCellsPanelHorizontalOffset();
                SetDataGridCellPanelWidth(children, arrangeSize.Width);
                InitializeArrangeState(arrangeState);
            }

            List<RealizedColumnsBlock> displayIndexBlockList = RealizedColumnsDisplayIndexBlockList;
            if (displayIndexBlockList != null && displayIndexBlockList.Count > 0)
            {
                double averageColumnWidth = parentDataGrid.InternalColumns.AverageColumnWidth;
                List<RealizedColumnsBlock> blockList = RealizedColumnsBlockList;
                Debug_VerifyRealizedIndexCountVsDisplayIndexCount(blockList, displayIndexBlockList);

                // Get realized children not in realized list, so that they dont participate in arrange
                List<int> additionalChildIndices = GetRealizedChildrenNotInBlockList(blockList, children);

                int displayIndexBlockIndex = -1;
                RealizedColumnsBlock displayIndexBlock = displayIndexBlockList[++displayIndexBlockIndex];
                bool pastLastBlock = false;
                for (int i = 0, count = parentDataGrid.Columns.Count; i < count; i++)
                {
                    bool realizedChild = InBlockOrNextBlock(displayIndexBlockList, i, ref displayIndexBlockIndex, ref displayIndexBlock, out pastLastBlock);
                    if (pastLastBlock)
                    {
                        break;
                    }

                    // Arrange the child if it is realized
                    if (realizedChild)
                    {
                        int columnIndex = parentDataGrid.ColumnIndexFromDisplayIndex(i);
                        RealizedColumnsBlock block = GetRealizedBlockForColumn(blockList, columnIndex);
                        int childIndex = block.StartIndexOffset + columnIndex - block.StartIndex;
                        if (additionalChildIndices != null)
                        {
                            for (int j = 0, additionalChildrenCount = additionalChildIndices.Count;
                                        j < additionalChildrenCount && additionalChildIndices[j] <= childIndex; j++)
                            {
                                childIndex++;
                            }
                        }

                        ArrangeChild(children[childIndex] as UIElement, i, arrangeState);
                    }
                    else
                    {
                        DataGridColumn column = parentDataGrid.ColumnFromDisplayIndex(i);
                        if (!column.IsVisible)
                        {
                            continue;
                        }

                        double childSize = GetColumnEstimatedMeasureWidth(column, averageColumnWidth);

                        Debug.Assert(i >= arrangeState.FrozenColumnCount, "Frozen cells should have been realized or not visible");

                        arrangeState.NextNonFrozenCellStart += childSize;
                    }
                }

                if (additionalChildIndices != null)
                {
                    for (int i = 0, count = additionalChildIndices.Count; i < count; i++)
                    {
                        UIElement child = children[additionalChildIndices[i]] as UIElement;
                        child.Arrange(new Rect());
                    }
                }
            }

            FinishArrange(arrangeState);

            return arrangeSize;
        }

        /// <summary>
        ///     Method which arranges the give child
        ///     based on given arrange state.
        ///
        ///     Determines the start position of the child
        ///     based on its display index, frozen count of
        ///     datagrid, current horizontal offset etc.
        /// </summary>
        private void ArrangeChild(
            UIElement child,
            int displayIndex,
            ArrangeState arrangeState)
        {
            Debug.Assert(child != null, "child cannot be null.");
            double childWidth = 0.0;
            IProvideDataGridColumn cell = child as IProvideDataGridColumn;

            // Determine if this child was clipped in last arrange for the sake of frozen columns
            if (child == _clippedChildForFrozenBehaviour)
            {
                arrangeState.OldClippedChild = child;
                _clippedChildForFrozenBehaviour = null;
            }

            // Width determinition of the child to be arranged. It is
            // display value if available else the ActualWidth
            if (cell != null)
            {
                Debug.Assert(cell.Column != null, "column cannot be null.");
                childWidth = cell.Column.Width.DisplayValue;
                if (double.IsNaN(childWidth))
                {
                    childWidth = cell.Column.ActualWidth;
                }
            }
            else
            {
                childWidth = child.DesiredSize.Width;
            }

            Rect rcChild = new Rect(new Size(childWidth, arrangeState.ChildHeight));

            // Determinition of start point for children to arrange. Lets say the there are 5 columns of which 2 are frozen.
            // If the datagrid is scrolled horizontally. Following is the snapshot of arrange
            /*
                    *                                                                                                    *
                    *| <Cell3> | <Unarranged space> | <RowHeader> | <Cell1> | <Cell2> | <Right Clip of Cell4> | <Cell5> |*
                    *                               |                        <Visible region>                           |*
             */
            if (displayIndex < arrangeState.FrozenColumnCount)
            {
                // For all the frozen children start from the horizontal offset
                // and arrange increamentally
                rcChild.X = arrangeState.NextFrozenCellStart;
                arrangeState.NextFrozenCellStart += childWidth;
                arrangeState.DataGridHorizontalScrollStartX += childWidth;
            }
            else
            {
                // For arranging non frozen children arrange which ever can be arranged
                // from the start to horizontal offset. This would fill out the space left by
                // frozen children. The next one child will be arranged and clipped accordingly past frozen
                // children. The remaining children will arranged in the remaining space.
                if (DoubleUtil.LessThanOrClose(arrangeState.NextNonFrozenCellStart, arrangeState.ViewportStartX))
                {
                    if (DoubleUtil.LessThanOrClose(arrangeState.NextNonFrozenCellStart + childWidth, arrangeState.ViewportStartX))
                    {
                        rcChild.X = arrangeState.NextNonFrozenCellStart;
                        arrangeState.NextNonFrozenCellStart += childWidth;
                    }
                    else
                    {
                        double cellChoppedWidth = arrangeState.ViewportStartX - arrangeState.NextNonFrozenCellStart;
                        if (DoubleUtil.AreClose(cellChoppedWidth, 0.0))
                        {
                            rcChild.X = arrangeState.NextFrozenCellStart;
                            arrangeState.NextNonFrozenCellStart = arrangeState.NextFrozenCellStart + childWidth;
                        }
                        else
                        {
                            rcChild.X = arrangeState.NextFrozenCellStart - cellChoppedWidth;
                            double clipWidth = childWidth - cellChoppedWidth;
                            arrangeState.NewClippedChild = child;
                            _childClipForFrozenBehavior.Rect = new Rect(cellChoppedWidth, 0, clipWidth, rcChild.Height);
                            arrangeState.NextNonFrozenCellStart = arrangeState.NextFrozenCellStart + clipWidth;
                        }
                    }
                }
                else
                {
                    rcChild.X = arrangeState.NextNonFrozenCellStart;
                    arrangeState.NextNonFrozenCellStart += childWidth;
                }
            }

            child.Arrange(rcChild);
        }

        /// <summary>
        ///     Method which gets the realized block for a given index
        /// </summary>
        /// <param name="blockList"></param>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        private static RealizedColumnsBlock GetRealizedBlockForColumn(List<RealizedColumnsBlock> blockList, int columnIndex)
        {
            for (int i = 0, count = blockList.Count; i < count; i++)
            {
                RealizedColumnsBlock block = blockList[i];
                if (columnIndex >= block.StartIndex &&
                    columnIndex <= block.EndIndex)
                {
                    return block;
                }
            }

            return new RealizedColumnsBlock(-1, -1, -1);
        }

        /// <summary>
        ///     Determines the list of children which are realized, but
        ///     shouldnt be as per the state stored at rows presenter
        /// </summary>
        private List<int> GetRealizedChildrenNotInBlockList(List<RealizedColumnsBlock> blockList, IList children)
        {
            DataGrid parentDataGrid = ParentDataGrid;
            RealizedColumnsBlock lastBlock = blockList[blockList.Count - 1];
            int blockElementCount = lastBlock.StartIndexOffset + lastBlock.EndIndex - lastBlock.StartIndex + 1;
            if (children.Count == blockElementCount)
            {
                return null;
            }

            Debug.Assert(children.Count > blockElementCount, "Element count from blocks can't be less than total children count");

            List<int> additionalChildIndices = new List<int>();
            if (blockList.Count == 0)
            {
                for (int i = 0, count = children.Count; i < count; i++)
                {
                    additionalChildIndices.Add(i);
                }
            }
            else
            {
                int blockIndex = 0;
                RealizedColumnsBlock block = blockList[blockIndex++];

                for (int i = 0, count = children.Count; i < count; i++)
                {
                    IProvideDataGridColumn cell = children[i] as IProvideDataGridColumn;
                    int columnIndex = i;
                    if (cell != null)
                    {
                        columnIndex = parentDataGrid.Columns.IndexOf(cell.Column);
                    }

                    if (columnIndex < block.StartIndex)
                    {
                        additionalChildIndices.Add(i);
                    }
                    else if (columnIndex > block.EndIndex)
                    {
                        if (blockIndex >= blockList.Count)
                        {
                            for (int j = i; j < count; j++)
                            {
                                additionalChildIndices.Add(j);
                            }

                            break;
                        }

                        block = blockList[blockIndex++];
                        Debug.Assert(columnIndex <= block.EndIndex, "Missing children for index in block list");

                        if (columnIndex < block.StartIndex)
                        {
                            additionalChildIndices.Add(i);
                        }
                    }
                }
            }

            return additionalChildIndices;
        }

        #endregion

        #region Column Virtualization

        // returns true if children are a superset of the realized columns
        internal bool HasCorrectRealizedColumns
        {
            get
            {
                DataGridColumnCollection columns = (DataGridColumnCollection)ParentDataGrid.Columns;
                EnsureRealizedChildren();   // necessary because this can be called before Measure (DevDiv2 1123429)
                IList children = RealizedChildren;

                // common case:  all columns are present
                if (children.Count == columns.Count)
                    return true;

                // see if each column in the realized block list appears as a child.
                // The block list and the children are both sorted, so we can do a linear merge.
                List<int> displayIndexMap = columns.DisplayIndexMap;
                List<RealizedColumnsBlock> blockList = RealizedColumnsBlockList;
                int k=0, n=children.Count;
                for (int j=0; j<blockList.Count; ++j)
                {
                    RealizedColumnsBlock block = blockList[j];
                    for (int index=block.StartIndex; index<=block.EndIndex; ++index)
                    {
                        for (; k<n; ++k)
                        {
                            IProvideDataGridColumn cell = children[k] as IProvideDataGridColumn;
                            if (cell != null)
                            {
                                int displayIndex = cell.Column.DisplayIndex;
                                int childColumnIndex = (displayIndex < 0 ? -1 : displayIndexMap[displayIndex]);
                                if (index < childColumnIndex)
                                    return false;   // child list skipped over index
                                else if (index == childColumnIndex)
                                    break;          // child list contains index
                            }
                        }

                        if (k==n)
                            return false;   // index didn't appear in child list
                        ++k;                // index did appear at position k
                    }
                }
                return true;
            }
        }

        private bool RebuildRealizedColumnsBlockList
        {
            get
            {
                DataGrid dataGrid = ParentDataGrid;
                if (dataGrid != null)
                {
                    DataGridColumnCollection columns = dataGrid.InternalColumns;
                    return IsVirtualizing ? columns.RebuildRealizedColumnsBlockListForVirtualizedRows : columns.RebuildRealizedColumnsBlockListForNonVirtualizedRows;
                }

                return true;
            }

            set
            {
                DataGrid dataGrid = ParentDataGrid;
                if (dataGrid != null)
                {
                    if (IsVirtualizing)
                    {
                        dataGrid.InternalColumns.RebuildRealizedColumnsBlockListForVirtualizedRows = value;
                    }
                    else
                    {
                        dataGrid.InternalColumns.RebuildRealizedColumnsBlockListForNonVirtualizedRows = value;
                    }
                }
            }
        }

        private List<RealizedColumnsBlock> RealizedColumnsBlockList
        {
            get
            {
                DataGrid dataGrid = ParentDataGrid;
                if (dataGrid != null)
                {
                    DataGridColumnCollection columns = dataGrid.InternalColumns;
                    return IsVirtualizing ? columns.RealizedColumnsBlockListForVirtualizedRows : columns.RealizedColumnsBlockListForNonVirtualizedRows;
                }

                return null;
            }

            set
            {
                DataGrid dataGrid = ParentDataGrid;
                if (dataGrid != null)
                {
                    if (IsVirtualizing)
                    {
                        dataGrid.InternalColumns.RealizedColumnsBlockListForVirtualizedRows = value;
                    }
                    else
                    {
                        dataGrid.InternalColumns.RealizedColumnsBlockListForNonVirtualizedRows = value;
                    }
                }
            }
        }

        private List<RealizedColumnsBlock> RealizedColumnsDisplayIndexBlockList
        {
            get
            {
                DataGrid dataGrid = ParentDataGrid;
                if (dataGrid != null)
                {
                    DataGridColumnCollection columns = dataGrid.InternalColumns;
                    return IsVirtualizing ? columns.RealizedColumnsDisplayIndexBlockListForVirtualizedRows : columns.RealizedColumnsDisplayIndexBlockListForNonVirtualizedRows;
                }

                return null;
            }

            set
            {
                DataGrid dataGrid = ParentDataGrid;
                if (dataGrid != null)
                {
                    if (IsVirtualizing)
                    {
                        dataGrid.InternalColumns.RealizedColumnsDisplayIndexBlockListForVirtualizedRows = value;
                    }
                    else
                    {
                        dataGrid.InternalColumns.RealizedColumnsDisplayIndexBlockListForNonVirtualizedRows = value;
                    }
                }
            }
        }

        /// <summary>
        ///     This method is invoked when the IsItemsHost property changes.
        /// </summary>
        /// <param name="oldIsItemsHost">The old value of the IsItemsHost property.</param>
        /// <param name="newIsItemsHost">The new value of the IsItemsHost property.</param>
        protected override void OnIsItemsHostChanged(bool oldIsItemsHost, bool newIsItemsHost)
        {
            base.OnIsItemsHostChanged(oldIsItemsHost, newIsItemsHost);

            if (newIsItemsHost)
            {
                ItemsControl parentPresenter = ParentPresenter;
                if (parentPresenter != null)
                {
                    IItemContainerGenerator generator = parentPresenter.ItemContainerGenerator as IItemContainerGenerator;
                    if (generator != null && generator == generator.GetItemContainerGeneratorForPanel(this))
                    {
                        DataGridCellsPresenter cellsPresenter = parentPresenter as DataGridCellsPresenter;
                        if (cellsPresenter != null)
                        {
                            cellsPresenter.InternalItemsHost = this;
                        }
                        else
                        {
                            DataGridColumnHeadersPresenter headersPresenter = parentPresenter as DataGridColumnHeadersPresenter;
                            if (headersPresenter != null)
                            {
                                headersPresenter.InternalItemsHost = this;
                            }
                        }
                    }
                }
            }
            else
            {
                ItemsControl parentPresenter = ParentPresenter;
                if (parentPresenter != null)
                {
                    DataGridCellsPresenter cellsPresenter = parentPresenter as DataGridCellsPresenter;
                    if (cellsPresenter != null)
                    {
                        if (cellsPresenter.InternalItemsHost == this)
                        {
                            cellsPresenter.InternalItemsHost = null;
                        }
                    }
                    else
                    {
                        DataGridColumnHeadersPresenter headersPresenter = parentPresenter as DataGridColumnHeadersPresenter;
                        if (headersPresenter != null && headersPresenter.InternalItemsHost == this)
                        {
                            headersPresenter.InternalItemsHost = null;
                        }
                    }
                }
            }
        }

        /// <summary>
        ///     The property which returns DataGridRowsPresenter ancestor of this panel
        /// </summary>
        private DataGridRowsPresenter ParentRowsPresenter
        {
            get
            {
                DataGrid parentDataGrid = ParentDataGrid;
                if (parentDataGrid == null)
                {
                    return null;
                }

                if (!parentDataGrid.IsGrouping)
                {
                    return parentDataGrid.InternalItemsHost as DataGridRowsPresenter;
                }

                // when grouping, we can't just look down from DataGrid - there
                // are different presenters for each group.  Instead, look up.
                DataGridCellsPresenter presenter = ParentPresenter as DataGridCellsPresenter;
                if (presenter != null)
                {
                    DataGridRow row = presenter.DataGridRowOwner;
                    if (row != null)
                    {
                        return VisualTreeHelper.GetParent(row) as DataGridRowsPresenter;
                    }
                }

                return null;
            }
        }

        private void DetermineVirtualizationState()
        {
            ItemsControl parentPresenter = ParentPresenter;
            if (parentPresenter != null)
            {
                IsVirtualizing = VirtualizingPanel.GetIsVirtualizing(parentPresenter);
                InRecyclingMode = (VirtualizingPanel.GetVirtualizationMode(parentPresenter) == VirtualizationMode.Recycling);
            }
        }

        /// <summary>
        ///     Property which determines if one has to virtualize the cells
        /// </summary>
        private bool IsVirtualizing
        {
            get; set;
        }

        /// <summary>
        ///     Property which determines if one is in recycling mode.
        /// </summary>
        private bool InRecyclingMode
        {
            get; set;
        }

        /// <summary>
        ///     Helper method which estimates the width of the column
        /// </summary>
        private static double GetColumnEstimatedMeasureWidth(DataGridColumn column, double averageColumnWidth)
        {
            if (!column.IsVisible)
            {
                return 0.0;
            }

            double childMeasureWidth = column.Width.DisplayValue;
            if (double.IsNaN(childMeasureWidth))
            {
                childMeasureWidth = Math.Max(averageColumnWidth, column.MinWidth);
                childMeasureWidth = Math.Min(childMeasureWidth, column.MaxWidth);
            }

            return childMeasureWidth;
        }

        /// <summary>
        ///     Helper method which estimates the sum of widths of
        ///     a given block of columns.
        /// </summary>
        private double GetColumnEstimatedMeasureWidthSum(int startIndex, int endIndex, double averageColumnWidth)
        {
            double measureWidth = 0.0;
            DataGrid parentDataGrid = ParentDataGrid;
            for (int i = startIndex; i <= endIndex; i++)
            {
                measureWidth += GetColumnEstimatedMeasureWidth(parentDataGrid.Columns[i], averageColumnWidth);
            }

            return measureWidth;
        }

        /// <summary>
        ///     Returns the list of childen that have been realized by the Generator.
        ///     We must use this method whenever we interact with the Generator's index.
        ///     In recycling mode the Children collection also contains recycled containers and thus does
        ///     not map to the Generator's list.
        /// </summary>
        private IList RealizedChildren
        {
            get
            {
                if (IsVirtualizing && InRecyclingMode)
                {
                    return _realizedChildren;
                }
                else
                {
                    return InternalChildren;
                }
            }
        }

        /// <summary>
        ///     Helper method which ensures that the _realizedChildren
        ///     member is properly initialized
        /// </summary>
        private void EnsureRealizedChildren()
        {
            if (IsVirtualizing && InRecyclingMode)
            {
                if (_realizedChildren == null)
                {
                    UIElementCollection children = InternalChildren;

                    _realizedChildren = new List<UIElement>(children.Count);

                    for (int i = 0; i < children.Count; i++)
                    {
                        _realizedChildren.Add(children[i]);
                    }
                }
            }
            else
            {
                _realizedChildren = null;
            }
        }

        /// <summary>
        ///     Helper method to compute the cells panel horizontal offset
        /// </summary>
        internal double ComputeCellsPanelHorizontalOffset()
        {
            Debug.Assert(ParentDataGrid != null, "ParentDataGrid should not be null");

            double cellsPanelOffset = 0.0;
            DataGrid dataGrid = ParentDataGrid;
            double horizontalOffset = dataGrid.HorizontalScrollOffset;
            ScrollViewer scrollViewer = dataGrid.InternalScrollHost;
            if (scrollViewer != null)
            {
                cellsPanelOffset = horizontalOffset + TransformToAncestor(scrollViewer).Transform(new Point()).X;
            }

            return cellsPanelOffset;
        }

        /// <summary>
        ///     Helper method which returns the viewport width
        /// </summary>
        private double GetViewportWidth()
        {
            double availableViewportWidth = 0.0;
            DataGrid parentDataGrid = ParentDataGrid;
            if (parentDataGrid != null)
            {
                ScrollContentPresenter scrollContentPresenter = parentDataGrid.InternalScrollContentPresenter;
                if (scrollContentPresenter != null &&
                    !scrollContentPresenter.CanContentScroll)
                {
                    availableViewportWidth = scrollContentPresenter.ViewportWidth;
                }
                else
                {
                    IScrollInfo scrollInfo = parentDataGrid.InternalItemsHost as IScrollInfo;
                    if (scrollInfo != null)
                    {
                        availableViewportWidth = scrollInfo.ViewportWidth;
                    }
                }
            }

            DataGridRowsPresenter parentRowsPresenter = ParentRowsPresenter;

            if (DoubleUtil.AreClose(availableViewportWidth, 0.0) && parentRowsPresenter != null)
            {
                Size rowPresenterAvailableSize = parentRowsPresenter.AvailableSize;
                if (!double.IsNaN(rowPresenterAvailableSize.Width) && !Double.IsInfinity(rowPresenterAvailableSize.Width))
                {
                    availableViewportWidth = rowPresenterAvailableSize.Width;
                }
                else if (parentDataGrid.IsGrouping) // parentRowsPresenter!=null implies parentDataGrid!=null
                {
                    IHierarchicalVirtualizationAndScrollInfo hvsInfo = DataGridHelper.FindParent<GroupItem>(parentRowsPresenter) as IHierarchicalVirtualizationAndScrollInfo;
                    if (hvsInfo != null)
                    {
                        availableViewportWidth = hvsInfo.Constraints.Viewport.Width;
                    }
                }
            }

            return availableViewportWidth;
        }

        /// <summary>
        ///     Called when the Items collection associated with the containing ItemsControl changes.
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="args">Event arguments</param>
        protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
        {
            base.OnItemsChanged(sender, args);

            switch (args.Action)
            {
                case NotifyCollectionChangedAction.Remove:
                    OnItemsRemove(args);
                    break;

                case NotifyCollectionChangedAction.Replace:
                    OnItemsReplace(args);
                    break;

                case NotifyCollectionChangedAction.Move:
                    OnItemsMove(args);
                    break;

                case NotifyCollectionChangedAction.Reset:
                    break;
            }
        }

        private void OnItemsRemove(ItemsChangedEventArgs args)
        {
            RemoveChildRange(args.Position, args.ItemCount, args.ItemUICount);
        }

        private void OnItemsReplace(ItemsChangedEventArgs args)
        {
            RemoveChildRange(args.Position, args.ItemCount, args.ItemUICount);
        }

        private void OnItemsMove(ItemsChangedEventArgs args)
        {
            RemoveChildRange(args.OldPosition, args.ItemCount, args.ItemUICount);
        }

        private void RemoveChildRange(GeneratorPosition position, int itemCount, int itemUICount)
        {
            if (IsItemsHost)
            {
                UIElementCollection children = InternalChildren;
                int pos = position.Index;
                if (position.Offset > 0)
                {
                    // An item is being removed after the one at the index
                    pos++;
                }

                if (pos < children.Count)
                {
                    Debug.Assert((itemCount == itemUICount) || (itemUICount == 0), "Both ItemUICount and ItemCount should be equal or ItemUICount should be 0.");
                    if (itemUICount > 0)
                    {
                        RemoveInternalChildRange(pos, itemUICount);

                        if (IsVirtualizing && InRecyclingMode)
                        {
                            // No need to call EnsureRealizedChildren because at this point it is expected that
                            // the _realizedChildren collection is already initialized (because of previous measures).
                            _realizedChildren.RemoveRange(pos, itemUICount);
                        }
                    }
                }
            }
        }

        /// <summary>
        ///     Sets the _realizedChildren in sync when children are cleared
        /// </summary>
        protected override void OnClearChildren()
        {
            base.OnClearChildren();
            _realizedChildren = null;
        }

        /// <summary>
        ///     A workaround method to access BringIndexIntoView method in this assembly.
        /// </summary>
        /// <param name="index"></param>
        internal void InternalBringIndexIntoView(int index)
        {
            BringIndexIntoView(index);
        }

        /// <summary>
        ///     Determines the position of the child and sets the horizontal
        ///     offset appropriately.
        /// </summary>
        /// <param name="index">Specify the item index that should become visible</param>
        /// <exception cref="ArgumentOutOfRangeException">
        ///     Thrown if index is out of range
        /// </exception>
        protected internal override void BringIndexIntoView(int index)
        {
            DataGrid parentDataGrid = ParentDataGrid;

            if (parentDataGrid == null)
            {
                base.BringIndexIntoView(index);
                return;
            }

            ArgumentOutOfRangeException.ThrowIfNegative(index);
            ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, parentDataGrid.Columns.Count);

            // if the column widths aren't known, try again when they are
            if (parentDataGrid.InternalColumns.ColumnWidthsComputationPending)
            {
                Dispatcher.BeginInvoke(DispatcherPriority.Render, (Action<int>)RetryBringIndexIntoView, index);
                return;
            }

            ScrollContentPresenter scrollContentPresenter = parentDataGrid.InternalScrollContentPresenter;
            IScrollInfo scrollInfo = null;
            if (scrollContentPresenter != null &&
                !scrollContentPresenter.CanContentScroll)
            {
                scrollInfo = scrollContentPresenter;
            }
            else
            {
                ScrollViewer scrollViewer = parentDataGrid.InternalScrollHost;
                if (scrollViewer != null)
                {
                    scrollInfo = scrollViewer.ScrollInfo;
                }
            }

            if (scrollInfo == null)
            {
                base.BringIndexIntoView(index);
                return;
            }

            bool wasMeasureDirty = MeasureDirty;  //see comment below
            bool needRetry = wasMeasureDirty;
            double newHorizontalOffset = 0.0;
            double oldHorizontalOffset = parentDataGrid.HorizontalScrollOffset;
            while (!IsChildInView(index, out newHorizontalOffset) &&
                   !DoubleUtil.AreClose(oldHorizontalOffset, newHorizontalOffset))
            {
                needRetry = true;
                scrollInfo.SetHorizontalOffset(newHorizontalOffset);
                UpdateLayout();
                oldHorizontalOffset = newHorizontalOffset;
            }

            // although the loop brings the desired column into view, the column
            // widths might change due to deferred data binding, which can push
            // the desired column out of view again.  To mitigate this, try
            // again after deferred bindings run (at Loaded priority)
            if (parentDataGrid.RetryBringColumnIntoView(needRetry))
            {
                DispatcherPriority priority = wasMeasureDirty ? DispatcherPriority.Background : DispatcherPriority.Loaded;
                Dispatcher.BeginInvoke(priority, (Action<int>)RetryBringIndexIntoView, index);

                // The idea is to run deferred bindings, already posted at
                // Loaded priority.  This may add content to a cell, causing a
                // layout request (at Render).  During UpdateLayout, the columns may
                // get new widths.  By the time we retry this method (at Loaded),
                // we'll see the new widths.
                //   But there's a subtle flaw.  MediaContext owns the task responsible
                // for calling UpdateLayout.  It demotes this task from Render to
                // Input priority when it hasn't seen Input in a while - see
                // MediaContext.ScheduleNextRenderOp.  If this happens,
                // the retry (at Loaded) will happen before the UpdateLayout (now at Input)
                // and we won't see the new widths.  [This flaw was very difficult to
                // diagnose, as it tended to go away when you set breakpoints or
                // tracepoints.  It's quite sensitive to timing.]
                //   To mitigate this, mark this panel as MeasureDirty.  Then during the
                // retry, check if MeasureDirty is still true;  if so, UpdateLayout
                // was demoted and we should reschedule the retry.  But this time
                // use Background priority, to let the UpdateLayout happen.
                //   This results in some flicker - the DataGrid redraws once with
                // the old column widths, and again with the new.  But that's the
                // best we can do given the arcane behavior of the layout system.
                InvalidateMeasure();
            }
        }

        private void RetryBringIndexIntoView(int index)
        {
            // if the app has changed the column collection since the retry was posted,
            // don't throw - just ignore it
            DataGrid parentDataGrid = ParentDataGrid;
            if (parentDataGrid != null && 0 <= index && index < parentDataGrid.Columns.Count)
            {
                BringIndexIntoView(index);
            }
        }

        /// <summary>
        ///     Method which determines if the child at given index is already in view.
        ///     Also returns the appropriate estimated horizontal offset to bring the
        ///     child into view if it is not already in view.
        /// </summary>
        private bool IsChildInView(int index, out double newHorizontalOffset)
        {
            DataGrid parentDataGrid = ParentDataGrid;
            double horizontalOffset = parentDataGrid.HorizontalScrollOffset;
            newHorizontalOffset = horizontalOffset;

            double averageColumnWidth = parentDataGrid.InternalColumns.AverageColumnWidth;
            int frozenColumnCount = parentDataGrid.FrozenColumnCount;

            double cellsPanelOffset = parentDataGrid.CellsPanelHorizontalOffset;
            double availableViewportWidth = GetViewportWidth();
            double nextFrozenCellStart = horizontalOffset;                // indicates the start position for next frozen cell
            double nextNonFrozenCellStart = -cellsPanelOffset;            // indicates the start position for next non-frozen cell
            double viewportStartX = horizontalOffset - cellsPanelOffset;  // indicates the start of viewport with respect to coordinate system of cell panel

            int displayIndex = Columns[index].DisplayIndex;
            double columnStart = 0.0;
            double columnEnd = 0.0;

            // Determine the start and end position of the concerned column in horizontal direction
            for (int i = 0; i <= displayIndex; i++)
            {
                DataGridColumn column = parentDataGrid.ColumnFromDisplayIndex(i);
                if (!column.IsVisible)
                {
                    continue;
                }

                double columnWidth = GetColumnEstimatedMeasureWidth(column, averageColumnWidth);

                if (i < frozenColumnCount)
                {
                    columnStart = nextFrozenCellStart;
                    columnEnd = columnStart + columnWidth;
                    nextFrozenCellStart += columnWidth;
                }
                else
                {
                    if (DoubleUtil.LessThanOrClose(nextNonFrozenCellStart, viewportStartX))
                    {
                        if (DoubleUtil.LessThanOrClose(nextNonFrozenCellStart + columnWidth, viewportStartX))
                        {
                            columnStart = nextNonFrozenCellStart;
                            columnEnd = columnStart + columnWidth;
                            nextNonFrozenCellStart += columnWidth;
                        }
                        else
                        {
                            columnStart = nextFrozenCellStart;
                            double cellChoppedWidth = viewportStartX - nextNonFrozenCellStart;
                            if (DoubleUtil.AreClose(cellChoppedWidth, 0.0))
                            {
                                columnEnd = columnStart + columnWidth;
                                nextNonFrozenCellStart = nextFrozenCellStart + columnWidth;
                            }
                            else
                            {
                                // If the concerned child is clipped for the sake of frozen columns
                                // then bring the start of child into view
                                double clipWidth = columnWidth - cellChoppedWidth;
                                columnEnd = columnStart + clipWidth;
                                nextNonFrozenCellStart = nextFrozenCellStart + clipWidth;
                                if (i == displayIndex)
                                {
                                    newHorizontalOffset = horizontalOffset - cellChoppedWidth;
                                    return false;
                                }
                            }
                        }
                    }
                    else
                    {
                        columnStart = nextNonFrozenCellStart;
                        columnEnd = columnStart + columnWidth;
                        nextNonFrozenCellStart += columnWidth;
                    }
                }
            }

            double viewportEndX = viewportStartX + availableViewportWidth;
            if (DoubleUtil.LessThan(columnStart, viewportStartX))
            {
                newHorizontalOffset = columnStart + cellsPanelOffset;
            }
            else if (DoubleUtil.GreaterThan(columnEnd, viewportEndX))
            {
                double offsetChange = columnEnd - viewportEndX;

                if (displayIndex < frozenColumnCount)
                {
                    nextFrozenCellStart -= (columnEnd - columnStart);
                }

                if (DoubleUtil.LessThan(columnStart - offsetChange, nextFrozenCellStart))
                {
                    offsetChange = columnStart - nextFrozenCellStart;
                }

                if (DoubleUtil.AreClose(offsetChange, 0.0))
                {
                    return true;
                }
                else
                {
                    newHorizontalOffset = horizontalOffset + offsetChange;
                }
            }
            else
            {
                return true;
            }

            return false;
        }

        #endregion

        #region Frozen Columns

        /// <summary>
        /// Method which returns the clip for the child which overlaps with frozen column
        /// </summary>
        /// <param name="child"></param>
        /// <returns></returns>
        internal Geometry GetFrozenClipForChild(UIElement child)
        {
            if (child == _clippedChildForFrozenBehaviour)
            {
                return _childClipForFrozenBehavior;
            }

            return null;
        }

        #endregion

        #region Helpers

        /// <summary>
        ///     Returns the columns on the parent DataGrid.
        /// </summary>
        private ObservableCollection<DataGridColumn> Columns
        {
            get
            {
                DataGrid parentDataGrid = ParentDataGrid;
                if (parentDataGrid != null)
                {
                    return parentDataGrid.Columns;
                }

                return null;
            }
        }

        /// <summary>
        ///     The row that this panel presents belongs to the DataGrid returned from this property.
        /// </summary>
        private DataGrid ParentDataGrid
        {
            get
            {
                if (_parentDataGrid == null)
                {
                    DataGridCellsPresenter presenter = ParentPresenter as DataGridCellsPresenter;

                    if (presenter != null)
                    {
                        DataGridRow row = presenter.DataGridRowOwner;

                        if (row != null)
                        {
                            _parentDataGrid = row.DataGridOwner;
                        }
                    }
                    else
                    {
                        DataGridColumnHeadersPresenter headersPresenter = ParentPresenter as DataGridColumnHeadersPresenter;

                        if (headersPresenter != null)
                        {
                            _parentDataGrid = headersPresenter.ParentDataGrid;
                        }
                    }
                }

                return _parentDataGrid;
            }
        }

        private ItemsControl ParentPresenter
        {
            get
            {
                FrameworkElement itemsPresenter = TemplatedParent as FrameworkElement;
                if (itemsPresenter != null)
                {
                    return itemsPresenter.TemplatedParent as ItemsControl;
                }

                return null;
            }
        }

        #endregion

        #region Data

        private DataGrid _parentDataGrid;

        private UIElement _clippedChildForFrozenBehaviour;
        private RectangleGeometry _childClipForFrozenBehavior = new RectangleGeometry();
        private List<UIElement> _realizedChildren;

        #endregion
    }
}
