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

//
// Description: DynamicDocumentPaginator associated with FlowDocument.
//

using System;                       // Object
using System.Collections.Generic;   // List<T>
using System.ComponentModel;        // AsyncCompletedEventArgs
using System.Windows;               // Size
using System.Windows.Documents;     // DocumentPaginator
using System.Windows.Media;         // Visual
using System.Windows.Threading;     // DispatcherOperationCallback
using MS.Internal.PtsHost;          // BreakRecordTable, FlowDocumentPage
using MS.Internal.Text;             // DynamicPropertyReader

namespace MS.Internal.Documents
{
    /// <summary>
    /// Delegate indicating when entire break record has been invalidated.
    /// </summary>
    internal delegate void BreakRecordTableInvalidatedEventHandler(object sender, EventArgs e);

    /// <summary>
    /// DynamicDocumentPaginator associated with FlowDocument.
    /// </summary>
    internal class FlowDocumentPaginator : DynamicDocumentPaginator, IServiceProvider, IFlowDocumentFormatter
    {
        //-------------------------------------------------------------------
        //
        //  Constructors
        //
        //-------------------------------------------------------------------

        #region Constructors

        /// <summary>
        /// Constructor
        /// </summary>
        internal FlowDocumentPaginator(FlowDocument document)
        {
            _pageSize = _defaultPageSize;
            _document = document;
            _brt = new BreakRecordTable(this);
            _dispatcherObject = new CustomDispatcherObject();

            // Background pagination by default is enabled.
            _backgroundPagination = true;
            InitiateNextAsyncOperation();
        }

        #endregion Constructors

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

        #region Public Methods


        /// <summary>
        /// Async version of <see cref="DocumentPaginator.GetPage"/>
        /// </summary>
        /// <param name="pageNumber">Page number.</param>
        /// <param name="userState">Unique identifier for the asynchronous task.</param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Throws ArgumentOutOfRangeException if PageNumber is negative.
        /// </exception>
        public override void GetPageAsync(int pageNumber, object userState)
        {
            // Page number cannot be negative.
            if (pageNumber < 0)
            {
                throw new ArgumentOutOfRangeException("pageNumber", SR.IDPNegativePageNumber);
            }

            // Reentrancy check.
            if (_document.StructuralCache.IsFormattingInProgress)
            {
                throw new InvalidOperationException(SR.FlowDocumentFormattingReentrancy);
            }
            if (_document.StructuralCache.IsContentChangeInProgress)
            {
                throw new InvalidOperationException(SR.TextContainerChangingReentrancyInvalid);
            }

            DocumentPage page = null;

            if (!_backgroundPagination)
            {
                page = GetPage(pageNumber);
            }
            else
            {
                // If entire content has been already pre-paginated (BreakRecordTable is clean)
                // and requesting non-existing page number, return DocumentPage.Missing.
                if (_brt.IsClean && !_brt.HasPageBreakRecord(pageNumber))
                {
                    page = DocumentPage.Missing;
                }

                if (_brt.HasPageBreakRecord(pageNumber))
                {
                    page = GetPage(pageNumber);
                }

                if (page == null)
                {
                    _asyncRequests.Add(new GetPageAsyncRequest(pageNumber, userState, this));
                    InitiateNextAsyncOperation();
                }
            }

            if (page != null)
            {
                OnGetPageCompleted(new GetPageCompletedEventArgs(page, pageNumber, null, false, userState));
            }
        }

        /// <summary>
        /// Retrieves the DocumentPage for the given page number. PageNumber
        /// is zero-based.
        /// </summary>
        /// <param name="pageNumber">Page number.</param>
        /// <returns>
        /// Returns DocumentPage.Missing if the given page does not exist.
        /// </returns>
        /// <remarks>
        /// Multiple requests for the same page number may return the same
        /// object (this is implementation specific).
        /// </remarks>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Throws ArgumentOutOfRangeException if PageNumber is negative.
        /// </exception>
        public override DocumentPage GetPage(int pageNumber)
        {
            DocumentPage page;

            // Ensure usage from just one Dispatcher object.
            // FlowDocumentPaginator runs its own layout, hence there is a need
            // to protect it from random access from other threads.
            _dispatcherObject.VerifyAccess();

            // Page number cannot be negative.
            if (pageNumber < 0)
            {
                throw new ArgumentOutOfRangeException("pageNumber", SR.IDPNegativePageNumber);
            }

            // Reentrancy check.
            if (_document.StructuralCache.IsFormattingInProgress)
            {
                throw new InvalidOperationException(SR.FlowDocumentFormattingReentrancy);
            }
            if (_document.StructuralCache.IsContentChangeInProgress)
            {
                throw new InvalidOperationException(SR.TextContainerChangingReentrancyInvalid);
            }

            // Disable processing of the queue during blocking operations to prevent unrelated reentrancy.
            using (_document.Dispatcher.DisableProcessing())
            {
                _document.StructuralCache.IsFormattingInProgress = true; // Set reentrancy flag.
                try
                {
                    // If entire content has been already pre-paginated (BreakRecordTable is clean)
                    // and requesting non-existing page number, return DocumentPage.Missing.
                    if (_brt.IsClean && !_brt.HasPageBreakRecord(pageNumber))
                    {
                        page = DocumentPage.Missing;
                    }
                    else
                    {
                        // If the DocumentPage is cached in the BreakRecordTable, use it.
                        page = _brt.GetCachedDocumentPage(pageNumber);
                        if (page == null)
                        {
                            // If requested page number does not have pre-calculated BreakRecord,
                            // do synchronous pagination up to the requested page number.
                            // [Synchronous pagination is done here, because GetPage is a sync operation.]
                            if (!_brt.HasPageBreakRecord(pageNumber))
                            {
                                page = FormatPagesTill(pageNumber);
                            }
                            // If requested page number does have pre-calculated BreakRecord,
                            // format a single page.
                            else
                            {
                                page = FormatPage(pageNumber);
                            }
                        }
                    }
                }
                finally
                {
                    _document.StructuralCache.IsFormattingInProgress = false; // Clear reentrancy flag.
                }
            }

            return page;
        }


        /// <summary>
        /// Async version of <see cref="DynamicDocumentPaginator.GetPageNumber"/>
        /// </summary>
        /// <param name="contentPosition">Content position.</param>
        /// <param name="userState">Unique identifier for the asynchronous task.</param>
        /// <exception cref="ArgumentException">
        /// Throws ArgumentException if the ContentPosition does not exist within
        /// this element’s tree.
        /// </exception>
        public override void GetPageNumberAsync(ContentPosition contentPosition, object userState)
        {
            // Content position cannot be null.
            ArgumentNullException.ThrowIfNull(contentPosition);
            // Content position cannot be Missing.
            if (contentPosition == ContentPosition.Missing)
            {
                throw new ArgumentException(SR.IDPInvalidContentPosition, "contentPosition");
            }

            // ContentPosition must be of appropriate type and must be part of
            // the content.
            TextPointer flowContentPosition = contentPosition as TextPointer;
            if (flowContentPosition == null)
            {
                throw new ArgumentException(SR.IDPInvalidContentPosition, "contentPosition");
            }
            if (flowContentPosition.TextContainer != _document.StructuralCache.TextContainer)
            {
                throw new ArgumentException(SR.IDPInvalidContentPosition, "contentPosition");
            }

            int pageNumber = 0;

            if (!_backgroundPagination)
            {
                pageNumber = GetPageNumber(contentPosition);
                OnGetPageNumberCompleted(new GetPageNumberCompletedEventArgs(contentPosition, pageNumber, null, false, userState));
            }
            else
            {
                if (_brt.GetPageNumberForContentPosition(flowContentPosition, ref pageNumber))
                {
                    OnGetPageNumberCompleted(new GetPageNumberCompletedEventArgs(contentPosition, pageNumber, null, false, userState));
                }
                else
                {
                    _asyncRequests.Add(new GetPageNumberAsyncRequest(flowContentPosition, userState, this));
                    InitiateNextAsyncOperation();
                }
            }
        }


        /// <summary>
        /// Returns the page number on which the ContentPosition appears.
        /// </summary>
        /// <param name="contentPosition">Content position.</param>
        /// <returns>
        /// Returns the page number on which the ContentPosition appears.
        /// </returns>
        /// <exception cref="ArgumentException">
        /// Throws ArgumentException if the ContentPosition does not exist within
        /// this element's tree.
        /// </exception>
        public override int GetPageNumber(ContentPosition contentPosition)
        {
            TextPointer flowContentPosition;
            int pageNumber;

            // Ensure usage from just one Dispatcher object.
            // FlowDocumentPaginator runs its own layout, hence there is a need
            // to protect it from random access from other threads.
            _dispatcherObject.VerifyAccess();

            // ContentPosition cannot be null.
            ArgumentNullException.ThrowIfNull(contentPosition);
            // ContentPosition must be of appropriate type and must be part of
            // the content.
            flowContentPosition = contentPosition as TextPointer;
            if (flowContentPosition == null)
            {
                throw new ArgumentException(SR.IDPInvalidContentPosition, "contentPosition");
            }
            if (flowContentPosition.TextContainer != _document.StructuralCache.TextContainer)
            {
                throw new ArgumentException(SR.IDPInvalidContentPosition, "contentPosition");
            }

            // We are about to perform synchronous pagination, so need to check for
            // reentrancy.
            if (_document.StructuralCache.IsFormattingInProgress)
            {
                throw new InvalidOperationException(SR.FlowDocumentFormattingReentrancy);
            }
            if (_document.StructuralCache.IsContentChangeInProgress)
            {
                throw new InvalidOperationException(SR.TextContainerChangingReentrancyInvalid);
            }

            // Disable processing of the queue during blocking operations to prevent unrelated reentrancy.
            using (_document.Dispatcher.DisableProcessing())
            {
                _document.StructuralCache.IsFormattingInProgress = true; // Set reentrancy flag.
                pageNumber = 0;
                try
                {
                    while (!_brt.GetPageNumberForContentPosition(flowContentPosition, ref pageNumber))
                    {
                        // If failed to get PageNumber and BreakRecordTable is clean,
                        // the input ContentPosition does not belong to the content.
                        // But according to check above, it does belong to the content.
                        // Break and return -1 in this case
                        if (_brt.IsClean)
                        {
                            pageNumber = -1;
                            break;
                        }

                        // Do synchronous pagination for the next missing page number.
                        FormatPage(pageNumber);
                    }
                }
                finally
                {
                    _document.StructuralCache.IsFormattingInProgress = false; // Clear reentrancy flag.
                }
            }

            return pageNumber;
        }

        /// <summary>
        /// Returns the ContentPosition for the given page.
        /// </summary>
        /// <param name="page">Document page.</param>
        /// <returns>Returns the ContentPosition for the given page.</returns>
        /// <exception cref="ArgumentException">
        /// Throws ArgumentException if the page is not valid.
        /// </exception>
        public override ContentPosition GetPagePosition(DocumentPage page)
        {
            FlowDocumentPage flowDocumentPage;
            ITextView textView;
            ITextPointer position;
            Point point, newPoint;
            MatrixTransform transform;

            // Ensure usage from just one Dispatcher object.
            // FlowDocumentPaginator runs its own layout, hence there is a need
            // to protect it from random access from other threads.
            _dispatcherObject.VerifyAccess();

            // ContentPosition cannot be null.
            ArgumentNullException.ThrowIfNull(page);
            // DocumentPage must be of appropriate type.
            flowDocumentPage = page as FlowDocumentPage;
            if (flowDocumentPage == null || flowDocumentPage.IsDisposed)
            {
                return ContentPosition.Missing;
            }

            // DocumentPage.Visual for printing scenarions needs to be always returned
            // in LeftToRight FlowDirection. Hence, if the document is RightToLeft,
            // mirroring transform need to be applied to the content of DocumentPage.Visual.
            point = new Point(0, 0);
            if (_document.FlowDirection == FlowDirection.RightToLeft)
            {
                transform = new MatrixTransform(-1.0, 0.0, 0.0, 1.0, flowDocumentPage.Size.Width, 0.0);
                transform.TryTransform(point, out newPoint);
                point = newPoint;
            }

            // Get TextView for DocumentPage. Position of the page is calculated through hittesting
            // the top-left of the page. If position cannot be found, the start position of
            // the first range for TextView is treated as ContentPosition for the page.
            textView = (ITextView)((IServiceProvider)flowDocumentPage).GetService(typeof(ITextView));
            Invariant.Assert(textView != null, "Cannot access ITextView for FlowDocumentPage.");

            //Invariant.Assert(textView.TextSegments.Count > 0, "Page cannot be empty.");
            // It is not necessarily WPF's fault if there are no TextSegments.  
            // We have seen examples where PTS aborts the formatting because the content
            // exceeds its capacity.  Rather than crashing in this case, limp along
            // as if the position couldn't be determined.
            if (textView.TextSegments.Count == 0)
            {
                return ContentPosition.Missing;
            }

            position = textView.GetTextPositionFromPoint(point, true);
            if (position == null)
            {
                position = textView.TextSegments[0].Start;
            }
            return (position is TextPointer) ? (ContentPosition)position : ContentPosition.Missing;
        }

        /// <summary>
        /// Returns the ContentPosition for an object within the content.
        /// </summary>
        /// <param name="o">Object within this element's tree.</param>
        /// <returns>Returns the ContentPosition for an object within the content.</returns>
        /// <exception cref="ArgumentException">
        /// Throws ArgumentException if the object does not exist within this element's tree.
        /// </exception>
        public override ContentPosition GetObjectPosition(Object o)
        {
            // Ensure usage from just one Dispatcher object.
            // FlowDocumentPaginator runs its own layout, hence there is a need
            // to protect it from random access from other threads.
            _dispatcherObject.VerifyAccess();

            // Object cannot be null.
            ArgumentNullException.ThrowIfNull(o);
            return _document.GetObjectPosition(o);
        }

        /// <summary>
        /// Cancels all asynchronous calls made with the given userState.
        /// If userState is null, all asynchronous calls are cancelled.
        /// </summary>
        /// <param name="userState">Unique identifier for the asynchronous task.</param>
        public override void CancelAsync(object userState)
        {
            if (userState == null)
            {
                CancelAllAsyncOperations();
            }
            else
            {
                for (int index = 0; index < _asyncRequests.Count; index++)
                {
                    AsyncRequest asyncRequest = _asyncRequests[index];

                    if (asyncRequest.UserState == userState)
                    {
                        asyncRequest.Cancel();
                        _asyncRequests.RemoveAt(index);
                        index--;
                    }
                }
            }
        }


        #endregion Public Methods

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

        #region Public Properties

        /// <summary>
        /// Whether PageCount is currently valid. If False, then the value of
        /// PageCount is the number of pages that have currently been formatted.
        /// </summary>
        /// <remarks>
        /// This value may revert to False after being True, in cases where
        /// PageSize or content changes, forcing a repagination.
        /// </remarks>
        public override bool IsPageCountValid
        {
            get
            {
                // Ensure usage from just one Dispatcher object.
                // FlowDocumentPaginator runs its own layout, hence there is a need
                // to protect it from random access from other threads.
                _dispatcherObject.VerifyAccess();

                return _brt.IsClean;
            }
        }

        /// <summary>
        /// If IsPageCountValid is True, this value is the number of pages
        /// of content. If False, this is the number of pages that have
        /// currently been formatted.
        /// </summary>
        /// <remarks>
        /// Value may change depending upon changes in PageSize or content changes.
        /// </remarks>
        public override int PageCount
        {
            get
            {
                // Ensure usage from just one Dispatcher object.
                // FlowDocumentPaginator runs its own layout, hence there is a need
                // to protect it from random access from other threads.
                _dispatcherObject.VerifyAccess();

                return _brt.Count;
            }
        }

        /// <summary>
        /// The suggested size for formatting pages.
        /// </summary>
        /// <remarks>
        /// Note that the paginator may override the specified page size. Users
        /// should check DocumentPage.Size.
        /// </remarks>
        public override Size PageSize
        {
            get
            {
                return _pageSize;
            }
            set
            {
                // Ensure usage from just one Dispatcher object.
                // FlowDocumentPaginator runs its own layout, hence there is a need
                // to protect it from random access from other threads.
                _dispatcherObject.VerifyAccess();

                Size newPageSize = value;
                if (double.IsNaN(newPageSize.Width))
                {
                    newPageSize.Width = _defaultPageSize.Width;
                }
                if (double.IsNaN(newPageSize.Height))
                {
                    newPageSize.Height = _defaultPageSize.Height;
                }
                Size oldActualSize = ComputePageSize();
                _pageSize = newPageSize;
                Size newActualSize = ComputePageSize();
                if (!DoubleUtil.AreClose(oldActualSize, newActualSize))
                {
                    // Detect invalid content change operations.
                    if (_document.StructuralCache.IsFormattingInProgress)
                    {
                        _document.StructuralCache.OnInvalidOperationDetected();
                        throw new InvalidOperationException(SR.FlowDocumentInvalidContnetChange);
                    }

                    // Any change of page metrics invalidates entire break record table.
                    // Hence page metrics change is treated in the same way as ContentChanged
                    // spanning entire content.
                    // NOTE: May execute external code, so it is possible to get
                    //       an exception here.
                    InvalidateBRT();
                }
            }
        }

        /// <summary>
        /// Whether content is paginated in the background.
        /// When True, the Paginator will paginate its content in the background,
        /// firing the PaginationCompleted and PaginationProgress events as appropriate.
        /// Background pagination begins immediately when set to True. If the
        /// PageSize is modified and this property is set to True, then all pages
        /// will be repaginated and existing pages may be destroyed.
        /// The default value is False.
        /// </summary>
        public override bool IsBackgroundPaginationEnabled
        {
            get
            {
                return _backgroundPagination;
            }
            set
            {
                // Ensure usage from just one Dispatcher object.
                // FlowDocumentPaginator runs its own layout, hence there is a need
                // to protect it from random access from other threads.
                _dispatcherObject.VerifyAccess();

                if (value != _backgroundPagination)
                {
                    _backgroundPagination = value;
                    InitiateNextAsyncOperation();
                }

                if (!_backgroundPagination)
                {
                    CancelAllAsyncOperations();
                }
            }
        }

        /// <summary>
        /// <see cref="DocumentPaginator.Source"/>
        /// </summary>
        public override IDocumentPaginatorSource Source
        {
            get { return _document; }
        }

        #endregion Public Properties

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

        #region Internal Methods

        /// <summary>
        /// Initiate the next async operation.
        /// </summary>
        internal void InitiateNextAsyncOperation()
        {
            // Do background pagination if it is enabled and BreakRecordTable is not clean or async requests are pending
            if (_backgroundPagination && _backgroundPaginationOperation == null && (!_brt.IsClean || _asyncRequests.Count > 0))
            {
                _backgroundPaginationOperation = _document.Dispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(OnBackgroundPagination), this);
            }
        }

        /// <summary>
        /// Cancels all pending async operations.
        /// </summary>
        internal void CancelAllAsyncOperations()
        {
            for (int index = 0; index < _asyncRequests.Count; index++)
            {
                _asyncRequests[index].Cancel();
            }

            _asyncRequests.Clear();
        }

        /// <summary>
        /// Raise PagesChanged event.
        /// </summary>
        internal void OnPagesChanged(int pageStart, int pageCount)
        {
            OnPagesChanged(new PagesChangedEventArgs(pageStart, pageCount));
        }

        /// <summary>
        /// Asynchronously raise PaginationProgress event.
        /// </summary>
        /// <param name="pageStart"></param>
        /// <param name="pageCount"></param>
        internal void OnPaginationProgress(int pageStart, int pageCount)
        {
            OnPaginationProgress(new PaginationProgressEventArgs(pageStart, pageCount));
        }

        /// <summary>
        /// Asynchronously raise PaginationCompleted event.
        /// </summary>
        internal void OnPaginationCompleted()
        {
            OnPaginationCompleted(EventArgs.Empty);
        }

        #endregion Internal Methods

        #region Internal Events

        /// <summary>
        /// Fired when all break records in the BreakRecordTable are invalidated.
        /// </summary>
        internal event BreakRecordTableInvalidatedEventHandler BreakRecordTableInvalidated;

        #endregion Internal Events

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

        #region Private Methods


        /// <summary>
        /// Invalidates the content of the entire break record table.
        /// </summary>
        private void InvalidateBRT()
        {
            if (BreakRecordTableInvalidated != null)
            {
                BreakRecordTableInvalidated(this, EventArgs.Empty);
            }

            _brt.OnInvalidateLayout();
        }

        /// <summary>
        /// Invalidates a subset of the break record table, no event is fired
        /// </summary>
        private void InvalidateBRTLayout(ITextPointer start, ITextPointer end)
        {
            _brt.OnInvalidateLayout(start, end);
        }


        /// <summary>
        /// Format pages up to a page identified by the pageNumber parameter.
        /// </summary>
        private DocumentPage FormatPagesTill(int pageNumber)
        {
            // Pre-calculate all BreakRecords up to the point where the input
            // BreakRecord for specified pageNumber is available.
            // Stop when required BreakRecord is available or entire BreakRecordTable is clean.
            while (!_brt.HasPageBreakRecord(pageNumber) && !_brt.IsClean)
            {
                // Get the first invalid entry in the BreakRecordTable, calculate
                // BreakRecord for it and update BreakRecordTable with the calculated
                // value.
                FormatPage(_brt.Count);
            }

            // If entire BreakRecordTable is clean, the page is not available.
            if (_brt.IsClean)
            {
                return DocumentPage.Missing;
            }

            // The input BreakRecord for the specified page number is already available.
            // Format the requested page.
            return FormatPage(pageNumber);
        }

        /// <summary>
        /// Format the page identified by the pageNumber parameter.
        /// </summary>
        private DocumentPage FormatPage(int pageNumber)
        {
            FlowDocumentPage page;
            PageBreakRecord breakRecordIn, breakRecordOut;
            Thickness pageMargin;
            Size pageSize;

            Invariant.Assert(_brt.HasPageBreakRecord(pageNumber), "BreakRecord for specified page number does not exist.");

            breakRecordIn = _brt.GetPageBreakRecord(pageNumber);
            page = new FlowDocumentPage(_document.StructuralCache);
            pageSize = ComputePageSize();
            pageMargin = _document.ComputePageMargin();

            breakRecordOut = page.FormatFinite(pageSize, pageMargin, breakRecordIn);
            page.Arrange(pageSize);

            // NOTE: May execute external code, so it is possible to get
            //       an exception here.
            _brt.UpdateEntry(pageNumber, page, breakRecordOut, page.DependentMax);
            return page;
        }

        /// <summary>
        /// Partially fill out BreakRecordTable by pre-calculating BreakRecords.
        /// This callback is invoked when background pagination is enabled and
        /// BreakRecordTable is not completely updated yet.
        /// </summary>
        private object OnBackgroundPagination(object arg)
        {
            DateTime dtStart = DateTime.Now;
            DateTime dtStop;

            _backgroundPaginationOperation = null; // Clear out pending request.

            // Ensure usage from just one Dispatcher object.
            // FlowDocumentPaginator runs its own layout, hence there is a need
            // to protect it from random access from other threads.
            _dispatcherObject.VerifyAccess();

            // Detect reentrancy.
            if (_document.StructuralCache.IsFormattingInProgress)
            {
                throw new InvalidOperationException(SR.FlowDocumentFormattingReentrancy);
            }

            // Ignore this formatting request, if the element was already disposed.
            if (_document.StructuralCache.PtsContext.Disposed)
            {
                return null;
            }


            // Disable processing of the queue during blocking operations to prevent unrelated reentrancy.
            using (_document.Dispatcher.DisableProcessing())
            {
                _document.StructuralCache.IsFormattingInProgress = true; // Set reentrancy flag
                try
                {
                    for (int index = 0; index < _asyncRequests.Count; index++)
                    {
                        AsyncRequest asyncRequest = _asyncRequests[index];

                        if (asyncRequest.Process())
                        {
                            _asyncRequests.RemoveAt(index);
                            index--; // Offset the index add
                        }
                    }

                    dtStop = DateTime.Now;

                    if (_backgroundPagination && !_brt.IsClean)
                    {
                        // Calculate BreakRecords until entire content is calculated or
                        // specific time span has been exceeded (_paginationTimeout).
                        while (!_brt.IsClean)
                        {
                            // Get the first invalid entry in the BreakRecordTable, calculate
                            // BreakRecord for it and update BreakRecordTable with the calculated
                            // value.
                            FormatPage(_brt.Count);

                            // Update time span.
                            dtStop = DateTime.Now;
                            long timeSpan = (dtStop.Ticks - dtStart.Ticks) / TimeSpan.TicksPerMillisecond;

                            if (timeSpan > _paginationTimeout)
                            {
                                break;
                            }
                        }

                        // Initiate the next async operation.
                        InitiateNextAsyncOperation();
                    }
                }
                finally
                {
                    _document.StructuralCache.IsFormattingInProgress = false;    // Clear reentrancy flag.
                }
            }

            return null;
        }

        /// <summary>
        /// Compute size for the page.
        /// </summary>
        private Size ComputePageSize()
        {
            double max, min;
            Size pageSize = new Size(_document.PageWidth, _document.PageHeight);
            if (double.IsNaN(pageSize.Width))
            {
                pageSize.Width = _pageSize.Width;
                max = _document.MaxPageWidth;
                if (pageSize.Width > max)
                {
                    pageSize.Width = max;
                }
                min = _document.MinPageWidth;
                if (pageSize.Width < min)
                {
                    pageSize.Width = min;
                }
            }
            if (double.IsNaN(pageSize.Height))
            {
                pageSize.Height = _pageSize.Height;
                max = _document.MaxPageHeight;
                if (pageSize.Height > max)
                {
                    pageSize.Height = max;
                }
                min = _document.MinPageHeight;
                if (pageSize.Height < min)
                {
                    pageSize.Height = min;
                }
            }
            return pageSize;
        }

        #endregion Private Methods

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

        #region Private Fields

        /// <summary>
        /// FlowDocument associated with the paginator.
        /// </summary>
        private readonly FlowDocument _document;

        /// <summary>
        /// Provides mechanism to ensure usage from just one Dispatcher.
        /// </summary>
        private readonly CustomDispatcherObject _dispatcherObject;

        /// <summary>
        /// BreakRecordTable
        /// </summary>
        private readonly BreakRecordTable _brt;

        /// <summary>
        /// Page size.
        /// </summary>
        private Size _pageSize;

        /// <summary>
        /// Whether content is paginated in the background.
        /// </summary>
        private bool _backgroundPagination;


        /// <summary>
        /// Pagination timeout time.
        /// </summary>
        private const int _paginationTimeout = 30;

        /// <summary>
        /// Default page size if none is specified.
        /// </summary>
        private static Size _defaultPageSize = new Size(8.5d * 96d, 11.0d * 96d);

        /// <summary>
        /// Async request list
        /// </summary>
        List<AsyncRequest> _asyncRequests = new List<AsyncRequest>(0);

        /// <summary>
        /// Background pagination dispatcher operation.
        /// </summary>
        DispatcherOperation _backgroundPaginationOperation;

        #endregion Private Fields


        #region Private Classes

        /// <summary>
        /// Base class for all async requests.
        /// </summary>
        private abstract class AsyncRequest
        {
            internal AsyncRequest(object userState, FlowDocumentPaginator paginator)
            {
                UserState = userState;
                Paginator = paginator;
            }

            /// <summary>
            /// Cancels this async request, responsible for firing appropriate events.
            /// </summary>
            internal abstract void Cancel();

            /// <summary>
            /// Processes this request. Returns true if processing completed. Responsible for firing appropriate events.
            /// </summary>
            internal abstract bool Process();


            /// <summary>
            /// User state - Needs to be internal for cancel.
            /// </summary>
            internal readonly object UserState;
            protected readonly FlowDocumentPaginator Paginator;
        }


        /// <summary>
        /// GetPage async request.
        /// </summary>
        private class GetPageAsyncRequest : AsyncRequest
        {
            internal GetPageAsyncRequest(int pageNumber, object userState, FlowDocumentPaginator paginator) : base(userState, paginator)
            {
                PageNumber = pageNumber;
            }

            /// <summary>
            /// <see cref="AsyncRequest.Cancel"/>
            /// </summary>
            internal override void Cancel()
            {
                Paginator.OnGetPageCompleted(new GetPageCompletedEventArgs(null, PageNumber, null, true, UserState));
            }

            /// <summary>
            /// <see cref="AsyncRequest.Process"/>
            /// </summary>
            internal override bool Process()
            {
                if (!Paginator._brt.HasPageBreakRecord(PageNumber))
                {
                    return false;
                }

                DocumentPage page = Paginator.FormatPage(PageNumber);

                Paginator.OnGetPageCompleted(new GetPageCompletedEventArgs(page, PageNumber, null, false, UserState));

                return true;
            }

            internal readonly int PageNumber;
        }

        /// <summary>
        /// GetPageNumber async request.
        /// </summary>
        private class GetPageNumberAsyncRequest : AsyncRequest
        {
            internal GetPageNumberAsyncRequest(TextPointer textPointer, object userState, FlowDocumentPaginator paginator) : base(userState, paginator)
            {
                TextPointer = textPointer;
            }

            /// <summary>
            /// <see cref="AsyncRequest.Cancel"/>
            /// </summary>
            internal override void Cancel()
            {
                Paginator.OnGetPageNumberCompleted(new GetPageNumberCompletedEventArgs(TextPointer, -1, null, true, UserState));
            }

            /// <summary>
            /// <see cref="AsyncRequest.Process"/>
            /// </summary>
            internal override bool Process()
            {
                int pageNumber = 0;

                if (!Paginator._brt.GetPageNumberForContentPosition(TextPointer, ref pageNumber))
                {
                    return false;
                }

                Paginator.OnGetPageNumberCompleted(new GetPageNumberCompletedEventArgs(TextPointer, pageNumber, null, false, UserState));

                return true;
            }

            internal readonly TextPointer TextPointer;
        }

        #endregion Private Classes

        //-------------------------------------------------------------------
        //
        //  Private Types
        //
        //-------------------------------------------------------------------

        #region Private Types

        /// <summary>
        /// Provides mechanism to ensure usage from just one Dispatcher.
        /// </summary>
        private class CustomDispatcherObject : DispatcherObject { }

        #endregion Private Types

        //-------------------------------------------------------------------
        //
        //  IServiceProvider Members
        //
        //-------------------------------------------------------------------

        #region IServiceProvider Members

        /// <summary>
        /// Returns service objects associated with this control.
        /// </summary>
        /// <param name="serviceType">Specifies the type of service object to get.</param>
        object IServiceProvider.GetService(Type serviceType)
        {
            return ((IServiceProvider)_document).GetService(serviceType);
        }

        #endregion IServiceProvider Members

        //-------------------------------------------------------------------
        //
        //  IFlowDocumentFormatter Members
        //
        //-------------------------------------------------------------------

        #region IFlowDocumentFormatter Members

        /// <summary>
        /// Responds to change affecting entire content of associated FlowDocument.
        /// </summary>
        /// <param name="affectsLayout">Whether change affects layout.</param>
        void IFlowDocumentFormatter.OnContentInvalidated(bool affectsLayout)
        {
            if (affectsLayout)
            {
                InvalidateBRT();
            }
            else
            {
                _brt.OnInvalidateRender();
            }
        }

        /// <summary>
        /// Responds to change affecting entire content of associated FlowDocument.
        /// </summary>
        /// <param name="affectsLayout">Whether change affects layout.</param>
        /// <param name="start">Start of the affected content range.</param>
        /// <param name="end">End of the affected content range.</param>
        void IFlowDocumentFormatter.OnContentInvalidated(bool affectsLayout, ITextPointer start, ITextPointer end)
        {
            if (affectsLayout)
            {
                InvalidateBRTLayout(start, end);
            }
            else
            {
                _brt.OnInvalidateRender(start, end);
            }
        }

        /// <summary>
        /// Suspend formatting.
        /// </summary>
        void IFlowDocumentFormatter.Suspend()
        {
            IsBackgroundPaginationEnabled = false;
            InvalidateBRT();
        }

        /// <summary>
        /// Is layout data in a valid state.
        /// </summary>
        bool IFlowDocumentFormatter.IsLayoutDataValid
        {
            get
            {
                return !_document.StructuralCache.IsContentChangeInProgress;
            }
        }

        #endregion IFlowDocumentFormatter Members
    }
}
