// 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: TextView implementation for collection of DocumentPageTextViews. 
//

using System;                               // InvalidOperationException, ...
using System.Collections.Generic;           // List<T>
using System.Collections.ObjectModel;       // ReadOnlyCollection
using System.Windows;                       // Point, Rect, ...
using System.Windows.Controls;              // FlowDocumentPageViewer, DocumentViewer
using System.Windows.Controls.Primitives;   // DocumentPageView, DocumentViewerBase
using System.Windows.Documents;             // ITextView, ITextContainer
using System.Windows.Media;                 // VisualTreeHelper
using System.Windows.Threading;             // DispatcherPriority, DispatherOperationCallback

namespace MS.Internal.Documents
{
    /// <summary>
    /// TextView implementation for collection of DocumentPageTextViews.
    /// </summary>
    internal class MultiPageTextView : TextViewBase
    {
        //-------------------------------------------------------------------
        //
        //  Constructors
        //
        //-------------------------------------------------------------------

        #region Constructors

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="viewer">Viewer associated with TextView.</param>
        /// <param name="renderScope">Render scope - root of layout structure visualizing content.</param>
        /// <param name="textContainer">TextContainer representing content.</param>
        internal MultiPageTextView(DocumentViewerBase viewer, UIElement renderScope, ITextContainer textContainer)
        {
            _viewer = viewer;
            _renderScope = renderScope;
            _textContainer = textContainer;
            _pageTextViews = new List<DocumentPageTextView>();
            OnPagesUpdatedCore();
        }

        #endregion Constructors

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

        #region Protected Methods

        /// <summary>
        /// Fires Updated event.
        /// </summary>
        /// <param name="e">Event arguments for the Updated event.</param>
        protected override void OnUpdated(EventArgs e)
        {
            // Forward the event.
            base.OnUpdated(e);

            // Update.
            if (this.IsValid)
            {
                OnUpdatedWorker(null);
            }
            else
            {
                _renderScope.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new DispatcherOperationCallback(OnUpdatedWorker), EventArgs.Empty);
            }
        }

        #endregion Protected Methods

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

        #region Internal Methods

        /// <summary>
        /// <see cref="ITextView.GetTextPositionFromPoint"/>
        /// </summary>
        internal override ITextPointer GetTextPositionFromPoint(Point point, bool snapToText)
        {
            ITextPointer position = null;
            DocumentPageTextView pageTextView;

            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }

            pageTextView = GetTextViewFromPoint(point, false);
            if (pageTextView != null)
            {
                // Transform to DocumentPageView coordinates and query inner TextView
                point = TransformToDescendant(pageTextView.RenderScope, point);
                position = pageTextView.GetTextPositionFromPoint(point, snapToText);
            }
            return position;
        }

        /// <summary>
        /// <see cref="ITextView.GetRawRectangleFromTextPosition"/>
        /// </summary>
        internal override Rect GetRawRectangleFromTextPosition(ITextPointer position, out Transform transform)
        {
            Rect rect = Rect.Empty;
            DocumentPageTextView pageTextView;

            // Initialize transform to Identity
            transform = Transform.Identity;

            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }

            pageTextView = GetTextViewFromPosition(position);
            if (pageTextView != null)
            {
                // Query nested TextView and transform from DocumentPageView coordinates.
                Transform pageTextViewTransform, ancestorTransform;
                rect = pageTextView.GetRawRectangleFromTextPosition(position, out pageTextViewTransform);
                ancestorTransform = GetTransformToAncestor(pageTextView.RenderScope);
                transform = GetAggregateTransform(pageTextViewTransform, ancestorTransform);
            }
            return rect;
        }

        /// <summary>
        /// <see cref="TextViewBase.GetTightBoundingGeometryFromTextPositions"/>
        /// </summary>
        internal override Geometry GetTightBoundingGeometryFromTextPositions(ITextPointer startPosition, ITextPointer endPosition)
        {
            //  verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }

            Geometry geometry = null;

            for (int i = 0, count = _pageTextViews.Count; i < count; ++i)
            {
                ReadOnlyCollection<TextSegment> textSegments = _pageTextViews[i].TextSegments;

                for (int segmentIndex = 0; segmentIndex < textSegments.Count; segmentIndex++)
                {
                    TextSegment textSegment = textSegments[segmentIndex];

                    ITextPointer startPositionInTextSegment = startPosition.CompareTo(textSegment.Start) > 0 ? startPosition : textSegment.Start;
                    ITextPointer endPositionInTextSegment = endPosition.CompareTo(textSegment.End) < 0 ? endPosition : textSegment.End;

                    if (startPositionInTextSegment.CompareTo(endPositionInTextSegment) >= 0)
                    {
                        continue;
                    }

                    Geometry pageGeometry = _pageTextViews[i].GetTightBoundingGeometryFromTextPositions(startPositionInTextSegment, endPositionInTextSegment);
                    if (pageGeometry != null)
                    {
                        Transform transform = _pageTextViews[i].RenderScope.TransformToAncestor(_renderScope).AffineTransform;
                        CaretElement.AddTransformToGeometry(pageGeometry, transform);

                        CaretElement.AddGeometry(ref geometry, pageGeometry);
                    }
                }
            }

            return (geometry);
        }

        /// <summary>
        /// <see cref="ITextView.GetPositionAtNextLine"/>
        /// </summary>
        internal override ITextPointer GetPositionAtNextLine(ITextPointer position, double suggestedX, int count, out double newSuggestedX, out int linesMoved)
        {
            int pageNumber;

            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }
            return GetPositionAtNextLineCore(position, suggestedX, count, out newSuggestedX, out linesMoved, out pageNumber);
        }

        /// <summary>
        /// <see cref="ITextView.GetPositionAtNextPage"/>
        /// </summary>
        internal override ITextPointer GetPositionAtNextPage(ITextPointer position, Point suggestedOffset, int count, out Point newSuggestedOffset, out int pagesMoved)
        {
            int pageNumber;             // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }
            return GetPositionAtNextPageCore(position, suggestedOffset, count, out newSuggestedOffset, out pagesMoved, out pageNumber);
        }

        /// <summary>
        /// <see cref="ITextView.IsAtCaretUnitBoundary"/>
        /// </summary>
        internal override bool IsAtCaretUnitBoundary(ITextPointer position)
        {
            bool atCaretUnitBoundary = false;
            DocumentPageTextView pageTextView;

            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }

            pageTextView = GetTextViewFromPosition(position);
            if (pageTextView != null)
            {
                atCaretUnitBoundary = pageTextView.IsAtCaretUnitBoundary(position);
            }
            return atCaretUnitBoundary;
        }

        /// <summary>
        /// <see cref="ITextView.GetNextCaretUnitPosition"/>
        /// </summary>
        internal override ITextPointer GetNextCaretUnitPosition(ITextPointer position, LogicalDirection direction)
        {
            ITextPointer positionOut = null;
            DocumentPageTextView pageTextView;

            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }

            pageTextView = GetTextViewFromPosition(position);
            if (pageTextView != null)
            {
                positionOut = pageTextView.GetNextCaretUnitPosition(position, direction);
            }
            return positionOut;
        }

        /// <summary>
        /// <see cref="ITextView.GetBackspaceCaretUnitPosition"/>
        /// </summary>
        internal override ITextPointer GetBackspaceCaretUnitPosition(ITextPointer position)
        {
            ITextPointer positionOut = null;
            DocumentPageTextView pageTextView;

            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }

            pageTextView = GetTextViewFromPosition(position);
            if (pageTextView != null)
            {
                positionOut = pageTextView.GetBackspaceCaretUnitPosition(position);
            }
            return positionOut;
        }

        /// <summary>
        /// <see cref="ITextView.GetBackspaceCaretUnitPosition"/>
        /// </summary>
        internal override TextSegment GetLineRange(ITextPointer position)
        {
            TextSegment textSegment = TextSegment.Null;
            DocumentPageTextView pageTextView;

            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }

            pageTextView = GetTextViewFromPosition(position);
            if (pageTextView != null)
            {
                textSegment = pageTextView.GetLineRange(position);
            }
            return textSegment;
        }

        /// <summary>
        /// <see cref="ITextView.Contains"/>
        /// </summary>
        internal override bool Contains(ITextPointer position)
        {
            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }
            return (GetTextViewFromPosition(position) != null);
        }

        /// <summary>
        /// <see cref="ITextView.BringPositionIntoViewAsync"/>
        /// </summary>
        internal override void BringPositionIntoViewAsync(ITextPointer position, object userState)
        {
            DocumentPageTextView pageTextView;
            int pageNumber;
            BringPositionIntoViewRequest pendingRequest;

            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }
            if (_pendingRequest != null)
            {
                // Ignore new request if the previous is not completed yet.
                OnBringPositionIntoViewCompleted(new BringPositionIntoViewCompletedEventArgs(
                    position, false, null, false, userState));
            }

            pendingRequest = new BringPositionIntoViewRequest(position, userState);
            _pendingRequest = pendingRequest;

            pageTextView = GetTextViewFromPosition(position);
            // If the position is currently in the view, do nothing.
            // Otherwise, let the viewer handle the request.
            if (pageTextView != null)
            {
                pendingRequest.Succeeded = true;
                OnBringPositionIntoViewCompleted(pendingRequest);
            }
            else
            {
                if (position is ContentPosition)
                {
                    DynamicDocumentPaginator documentPaginator = _viewer.Document.DocumentPaginator as DynamicDocumentPaginator;
                    if (documentPaginator != null)
                    {
                        pageNumber = documentPaginator.GetPageNumber((ContentPosition)position) + 1;
                        if (_viewer.CanGoToPage(pageNumber))
                        {
                            _viewer.GoToPage(pageNumber);
                        }
                        else
                        {
                            OnBringPositionIntoViewCompleted(pendingRequest);
                        }
                    }
                    else
                    {
                        OnBringPositionIntoViewCompleted(pendingRequest);
                    }
                }
                else
                {
                    OnBringPositionIntoViewCompleted(pendingRequest);
                }
            }
        }

        /// <summary>
        /// <see cref="ITextView.BringPointIntoViewAsync"/>
        /// </summary>
        internal override void BringPointIntoViewAsync(Point point, object userState)
        {
            DocumentPageTextView pageTextView;
            ITextPointer position;
            BringPointIntoViewRequest pendingRequest;
            bool bringIntoViewPending;

            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }
            if (_pendingRequest != null)
            {
                // Ignore new request if the previous is not completed yet.
                OnBringPointIntoViewCompleted(new BringPointIntoViewCompletedEventArgs(
                    point, null, false, null, false, userState));
            }
            else
            {
                pendingRequest = new BringPointIntoViewRequest(point, userState);
                _pendingRequest = pendingRequest;

                pageTextView = GetTextViewFromPoint(point, false);
                // If the point is currently in the view, use existing TextView to retrieve the position.
                // Otherwise, let the viewer handle the request.
                if (pageTextView != null)
                {
                    // Transform to DocumentPageView coordinates and query inner TextView
                    point = TransformToDescendant(pageTextView.RenderScope, point);
                    position = pageTextView.GetTextPositionFromPoint(point, true);

                    pendingRequest.Position = position;
                    OnBringPointIntoViewCompleted(pendingRequest);
                }
                else
                {
                    // Request to bring point into view in the Viewer.
                    // This code is specific to known viewers. Since text selection is not
                    // exposed in a public way, it should not cause any "extensibility" problems.
                    GeneralTransform transform = _renderScope.TransformToAncestor(_viewer);

                    // REVIEW: should we do anything special if the point could not be
                    // transformed completely?
                    transform.TryTransform(point, out point);
                    bringIntoViewPending = false;
                    if (_viewer is FlowDocumentPageViewer)
                    {
                        // Special handling for FlowDocumentPageViewer
                        bringIntoViewPending = ((FlowDocumentPageViewer)_viewer).BringPointIntoView(point);
                    }
                    else if (_viewer is DocumentViewer)
                    {
                        // Special handling for DocumentViewer
                        bringIntoViewPending = ((DocumentViewer)_viewer).BringPointIntoView(point);
                    }
                    else
                    {
                        if (DoubleUtil.LessThan(point.X, 0))
                        {
                            if (_viewer.CanGoToPreviousPage)
                            {
                                _viewer.PreviousPage();
                                bringIntoViewPending = true;
                            }
                        }
                        else if (DoubleUtil.GreaterThan(point.X, _viewer.RenderSize.Width))
                        {
                            if (_viewer.CanGoToNextPage)
                            {
                                _viewer.NextPage();
                                bringIntoViewPending = true;
                            }
                        }
                        else if (DoubleUtil.LessThan(point.Y, 0))
                        {
                            if (_viewer.CanGoToPreviousPage)
                            {
                                _viewer.PreviousPage();
                                bringIntoViewPending = true;
                            }
                        }
                        else if (DoubleUtil.GreaterThan(point.Y, _viewer.RenderSize.Height))
                        {
                            if (_viewer.CanGoToNextPage)
                            {
                                _viewer.NextPage();
                                bringIntoViewPending = true;
                            }
                        }
                    }
                    if (!bringIntoViewPending)
                    {
                        OnBringPointIntoViewCompleted(pendingRequest);
                    }
                }
            }
        }

        /// <summary>
        /// <see cref="ITextView.BringLineIntoViewAsync"/>
        /// </summary>
        internal override void BringLineIntoViewAsync(ITextPointer position, double suggestedX, int count, object userState)
        {
            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }
            if (_pendingRequest != null)
            {
                // Ignore new request if the previous is not completed yet.
                OnBringLineIntoViewCompleted(new BringLineIntoViewCompletedEventArgs(
                    position, suggestedX, count, position, suggestedX, 0, false, null, false, userState));
            }
            else
            {
                _pendingRequest = new BringLineIntoViewRequest(position, suggestedX, count, userState);
                BringLineIntoViewCore((BringLineIntoViewRequest)_pendingRequest);
            }
        }

        /// <summary>
        /// <see cref="ITextView.BringLineIntoViewAsync"/>
        /// </summary>
        internal override void BringPageIntoViewAsync(ITextPointer position, Point suggestedOffset, int count, object userState)
        {
            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }
            if (_pendingRequest != null)
            {
                // Ignore new request if the previous is not completed yet.
                OnBringPageIntoViewCompleted(new BringPageIntoViewCompletedEventArgs(
                    position, suggestedOffset, count, position, suggestedOffset, 0, false, null, false, userState));
            }
            else
            {
                _pendingRequest = new BringPageIntoViewRequest(position, suggestedOffset, count, userState);
                BringPageIntoViewCore((BringPageIntoViewRequest)_pendingRequest);
            }
        }


        /// <summary>
        /// <see cref="ITextView.CancelAsync"/>
        /// </summary>
        internal override void CancelAsync(object userState)
        {
            BringLineIntoViewRequest lineRequest;
            BringPageIntoViewRequest pageRequest;
            BringPointIntoViewRequest pointRequest;
            BringPositionIntoViewRequest positionRequest;

            if (_pendingRequest != null)
            {
                if (_pendingRequest is BringLineIntoViewRequest)
                {
                    lineRequest = (BringLineIntoViewRequest)_pendingRequest;
                    OnBringLineIntoViewCompleted(new BringLineIntoViewCompletedEventArgs(
                        lineRequest.Position, lineRequest.SuggestedX, lineRequest.Count,
                        lineRequest.NewPosition, lineRequest.NewSuggestedX, lineRequest.Count - lineRequest.NewCount,
                        false, null, true, lineRequest.UserState));
                }
                else if (_pendingRequest is BringPageIntoViewRequest)
                {
                    pageRequest = (BringPageIntoViewRequest)_pendingRequest;
                    OnBringPageIntoViewCompleted(new BringPageIntoViewCompletedEventArgs(
                        pageRequest.Position, pageRequest.SuggestedOffset, pageRequest.Count,
                        pageRequest.NewPosition, pageRequest.NewSuggestedOffset, pageRequest.Count - pageRequest.NewCount,
                        false, null, true, pageRequest.UserState));
                }
                else if (_pendingRequest is BringPointIntoViewRequest)
                {
                    pointRequest = (BringPointIntoViewRequest)_pendingRequest;
                    OnBringPointIntoViewCompleted(new BringPointIntoViewCompletedEventArgs(
                        pointRequest.Point, pointRequest.Position, false, null, true, pointRequest.UserState));
                }
                else if (_pendingRequest is BringPositionIntoViewRequest)
                {
                    positionRequest = (BringPositionIntoViewRequest)_pendingRequest;
                    OnBringPositionIntoViewCompleted(new BringPositionIntoViewCompletedEventArgs(
                        positionRequest.Position, false, null, true, positionRequest.UserState));
                }
                _pendingRequest = null;
            }
        }

        /// <summary>
        /// Collection of DocumentPageViews has been changed. Need to update 
        /// collection of TextViews.
        /// </summary>
        internal void OnPagesUpdated()
        {
            OnPagesUpdatedCore();
            if (IsValid)
            {
                OnUpdated(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Invoked when Page Layout has changed in order to keep
        /// the TextView in sync.
        /// </summary>
        internal void OnPageLayoutChanged()
        {
            if (IsValid)
            {
                OnUpdated(EventArgs.Empty);
            }
        }


        /// <summary>
        /// Retrieves an active TextView containing the object or character 
        /// represented by the given TextPointer.
        /// </summary>
        /// <param name="position">Position of an object/character.</param>
        /// <returns>
        /// Active TextView containing the object or character represented by 
        /// the given TextPointer.
        /// </returns>
        internal ITextView GetPageTextViewFromPosition(ITextPointer position)
        {
            // Verify that layout information is valid. Cannot continue if not valid.
            if (!IsValid)
            {
                throw new InvalidOperationException(SR.TextViewInvalidLayout);
            }
            return GetTextViewFromPosition(position);
        }

        #endregion Internal Methods

        //-------------------------------------------------------------------
        //
        //  Internal Properties
        //
        //-------------------------------------------------------------------

        #region Internal Properties

        /// <summary>
        /// <see cref="ITextView.RenderScope"/>
        /// </summary>
        internal override UIElement RenderScope
        {
            get { return _renderScope; }
        }

        /// <summary>
        /// <see cref="ITextView.TextContainer"/>
        /// </summary>
        internal override ITextContainer TextContainer
        {
            get { return _textContainer; }
        }

        /// <summary>
        /// <see cref="ITextView.IsValid"/>
        /// </summary>
        internal override bool IsValid
        {
            get
            {
                bool valid = false;
                if (_pageTextViews != null)
                {
                    valid = true;
                    for (int i = 0; i < _pageTextViews.Count; i++)
                    {
                        if (!_pageTextViews[i].IsValid)
                        {
                            valid = false;
                            break;
                        }
                    }
                }
                return valid;
            }
        }


        /// <summary>
        /// <see cref="ITextView.RendersOwnSelection"/>
        /// </summary>
        internal override bool RendersOwnSelection
        {
            get
            {
                if (_pageTextViews != null && _pageTextViews.Count > 0)
                {
                    return _pageTextViews[0].RendersOwnSelection;
                }
                return false;
            }
        }


        /// <summary>
        /// <see cref="ITextView.TextSegments"/>
        /// </summary>
        internal override ReadOnlyCollection<TextSegment> TextSegments
        {
            get
            {
                List<TextSegment> textSegments = new List<TextSegment>();
                if (IsValid)
                {
                    // Get collection of active TextViews for all PageDocumentViews.
                    for (int i = 0; i < _pageTextViews.Count; i++)
                    {
                        textSegments.AddRange(_pageTextViews[i].TextSegments);
                    }
                }
                return new ReadOnlyCollection<TextSegment>(textSegments);
            }
        }

        #endregion Internal Properties

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

        #region Private Methods

        /// <summary>
        /// Collection of DocumentPageViews has been changed. Need to update 
        /// collection of TextViews.
        /// </summary>
        private void OnPagesUpdatedCore()
        {
            ReadOnlyCollection<DocumentPageView> pageViews;
            DocumentPageTextView pageTextView;
            int index;

            // Drop old collection of DocumentPageTextView objects.
            for (index = 0; index < _pageTextViews.Count; index++)
            {
                _pageTextViews[index].Updated -= new EventHandler(HandlePageTextViewUpdated);
            }

            _pageTextViews.Clear();
            pageViews = _viewer.PageViews;
            if (pageViews != null)
            {
                for (index = 0; index < pageViews.Count; index++)
                {
                    pageTextView = ((IServiceProvider)pageViews[index]).GetService(typeof(ITextView)) as DocumentPageTextView;
                    if (pageTextView != null)
                    {
                        _pageTextViews.Add(pageTextView);
                        pageTextView.Updated += new EventHandler(HandlePageTextViewUpdated);
                    }
                }
            }
        }

        /// <summary>
        /// Handler for Updated event raised by the inner TextView.
        /// </summary>
        private void HandlePageTextViewUpdated(object sender, EventArgs e)
        {
            OnUpdated(EventArgs.Empty);
        }

        /// <summary>
        /// Bring line into view.
        /// </summary>
        private void BringLineIntoViewCore(BringLineIntoViewRequest request)
        {
            ITextPointer newPosition;
            double newSuggestedX;
            int linesMoved;
            int pageNumber;

            // Try to use existing TextViews to handle this request.
            newPosition = GetPositionAtNextLineCore(request.NewPosition, request.NewSuggestedX, request.NewCount, out newSuggestedX, out linesMoved, out pageNumber);
            Invariant.Assert(Math.Abs(request.NewCount) >= Math.Abs(linesMoved));
            request.NewPosition = newPosition;
            request.NewSuggestedX = newSuggestedX;
            request.NewCount = request.NewCount - linesMoved;
            request.NewPageNumber = pageNumber;

            if (request.NewCount == 0)
            {
                OnBringLineIntoViewCompleted(request);
            }
            else
            {
                if (newPosition is DocumentSequenceTextPointer || newPosition is FixedTextPointer)
                {
                    //Fixed generally uses a stitched viewing mechanism and the approach used for Flow does not work properly for Fixed
                    //NextPage() call in DocumentViewer results in DocumentGrid.ScrollToNextRow() which scrolls to firstVisibleRow + 1
                    //E.g. if there are two views on the screen and we need to navigate to the third, this makes it impossible to achieve
                    if (_viewer.CanGoToPage(pageNumber + 1))
                    {
                        _viewer.GoToPage(pageNumber + 1);
                    }
                    else
                    {
                        OnBringLineIntoViewCompleted(request);
                    }
                }
                else
                {
                    if (request.NewCount > 0)
                    {
                        // If the viewer can navigate to the next page, request navigation and wait for 
                        // Updated event for the TextView.
                        // If cannot to the next page, raise BringLineIntoViewCompleted with success = 'False'.
                        if (_viewer.CanGoToNextPage)
                        {
                            _viewer.NextPage();
                        }
                        else
                        {
                            OnBringLineIntoViewCompleted(request);
                        }
                    }
                    else
                    {
                        // If the viewer can navigate to the previous page, request navigation and wait for 
                        // Updated event fo the TextView.
                        // If cannot to the previous page, raise BringLineIntoViewCompleted with success = 'False'.
                        if (_viewer.CanGoToPreviousPage)
                        {
                            _viewer.PreviousPage();
                        }
                        else
                        {
                            OnBringLineIntoViewCompleted(request);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Bring page into view.
        /// </summary>
        private void BringPageIntoViewCore(BringPageIntoViewRequest request)
        {
            ITextPointer newPosition;
            Point newSuggestedOffset;
            int pagesMoved;
            int newPageNumber;

            // Try to use existing TextViews to handle this request.
            newPosition = GetPositionAtNextPageCore(request.NewPosition, request.NewSuggestedOffset, request.NewCount, out newSuggestedOffset, out pagesMoved, out newPageNumber);
            Invariant.Assert(Math.Abs(request.NewCount) >= Math.Abs(pagesMoved));
            request.NewPosition = newPosition;
            request.NewSuggestedOffset = newSuggestedOffset;
            request.NewCount = request.NewCount - pagesMoved;

            if (request.NewCount == 0 || newPageNumber == -1)
            {
                OnBringPageIntoViewCompleted(request);
            }
            else
            {
                // If the viewer can navigate to the next page, request navigation and wait for 
                // Updated event for the TextView.
                // If cannot to the next page, raise BringLineIntoViewCompleted with success = 'False'.
                newPageNumber += (request.NewCount > 0) ? 1 : -1;
                if (_viewer.CanGoToPage(newPageNumber + 1))
                {
                    request.NewPageNumber = newPageNumber;
                    _viewer.GoToPage(newPageNumber + 1);
                }
                else
                {
                    OnBringPageIntoViewCompleted(request);
                }
            }
        }

        /// <summary>
        /// <see cref="ITextView.GetPositionAtNextLine"/>
        /// </summary>
        private ITextPointer GetPositionAtNextLineCore(ITextPointer position, double suggestedX, int count, out double newSuggestedX, out int linesMoved, out int pageNumber)
        {
            ITextPointer positionOut;
            DocumentPageTextView pageTextView;
            int originalCount;
            Point offset;
            int newLinesMoved;
            int previousCount;
            ReadOnlyCollection<TextSegment> segments;

            pageTextView = GetTextViewFromPosition(position);
            if (pageTextView != null)
            {
                originalCount = count;

                // Transform to DocumentPageView coordinates
                offset = TransformToDescendant(pageTextView.RenderScope, new Point(suggestedX, 0));
                suggestedX = offset.X;

                // Query inner TextView
                positionOut = pageTextView.GetPositionAtNextLine(position, suggestedX, count, out newSuggestedX, out linesMoved);
                pageNumber = ((DocumentPageView)pageTextView.RenderScope).PageNumber;

                // Transform from DocumentPageView coordinates
                offset = TransformToAncestor(pageTextView.RenderScope, new Point(newSuggestedX, 0));
                newSuggestedX = offset.X;

                // If number of lines moved is different than requested number of lines,
                // try to find query the previous/next page.
                while (originalCount != linesMoved)
                {
                    newLinesMoved = 0;
                    count = originalCount - linesMoved;

                    // Try to find TextView for DocumentPageView with the next/previous page number.
                    pageNumber += (count > 0) ? 1 : -1;
                    pageTextView = GetTextViewFromPageNumber(pageNumber);
                    if (pageTextView != null)
                    {
                        // All positions have to be in the TextView boundary.
                        // Since we are quering another TextView, move requested position to its range.
                        segments = pageTextView.TextSegments;
                        previousCount = count;
                        if (count > 0)
                        {
                            position = pageTextView.GetTextPositionFromPoint(new Point(suggestedX, 0), true);
                            if (position != null)
                            {
                                --count;
                                ++linesMoved;
                            }
                        }
                        else
                        {
                            position = pageTextView.GetTextPositionFromPoint(new Point(suggestedX, pageTextView.RenderScope.RenderSize.Height), true);
                            if (position != null)
                            {
                                ++count;
                                --linesMoved;
                            }
                        }

                        if (position != null)
                        {
                            // If moving to the first/last line of the next/previous TextView, there is
                            // special handling needed to position at the right suggestedX. Otherwise, TextView
                            // will return the same position.
                            if (count == 0)
                            {
                                positionOut = GetPositionAtPageBoundary(previousCount > 0, pageTextView, position, suggestedX);
                                newSuggestedX = suggestedX;
                            }
                            else
                            {
                                // Query nested TextView.
                                // Use the same logical 'suggestedX' as for previous DocumentPageView.
                                positionOut = pageTextView.GetPositionAtNextLine(position, suggestedX, count, out newSuggestedX, out newLinesMoved);
                                linesMoved += newLinesMoved;
                            }

                            // Transform from DocumentPageView coordinates
                            offset = TransformToAncestor(pageTextView.RenderScope, new Point(newSuggestedX, 0));
                            newSuggestedX = offset.X;
                        }
                    }
                    else
                    {
                        // If DocumentPageView has not been found, there is no point
                        // to continue.
                        // 'positionOut' contains the closes position we can return.
                        break;
                    }
                }
            }
            else
            {
                positionOut = position;
                linesMoved = 0;
                newSuggestedX = suggestedX;
                pageNumber = -1;
            }
            return positionOut;
        }

        private ITextPointer GetPositionAtNextPageCore(ITextPointer position, Point suggestedOffset, int count, out Point newSuggestedOffset, out int pagesMoved, out int pageNumber)
        {
            // Initialize output 
            ITextPointer positionOut = position;
            pagesMoved = 0;
            newSuggestedOffset = suggestedOffset;
            pageNumber = -1;

            DocumentPageTextView pageTextView = GetTextViewFromPosition(position);
            if (pageTextView != null)
            {
                int currentPageNumber = ((DocumentPageView)pageTextView.RenderScope).PageNumber;
                DocumentPageTextView newPageTextView = GetTextViewForNextPage(currentPageNumber, count, out pageNumber);
                pagesMoved = pageNumber - currentPageNumber;
                Invariant.Assert(Math.Abs(pagesMoved) <= Math.Abs(count));

                if (pageNumber != currentPageNumber && newPageTextView != null)
                {
                    // Transform suggested offset to to DocumentPageView coordinates to use as X-coordinate
                    Point point = TransformToDescendant(pageTextView.RenderScope, suggestedOffset);

                    // Query inner TextView for requested page to find position at that point. 
                    positionOut = newPageTextView.GetTextPositionFromPoint(point, /*snapToText*/true);
                    if (positionOut != null)
                    {
                        Rect rect = newPageTextView.GetRectangleFromTextPosition(positionOut);
                        point = TransformToAncestor(pageTextView.RenderScope, new Point(rect.X, rect.Y));
                        newSuggestedOffset = point;
                    }
                    else
                    {
                        positionOut = position;
                        pagesMoved = 0;
                        pageNumber = currentPageNumber;
                    }
                }
                else
                {
                    pagesMoved = 0;
                    pageNumber = currentPageNumber;
                }
            }

            return positionOut;
        }

        /// <summary>
        /// Retrieves position at the page boundary from given suggestedX.
        /// </summary>
        /// <param name="pageTop">Whether asking for top of the page or bottom of the page.</param>
        /// <param name="pageTextView">TextView representing the page.</param>
        /// <param name="position">Position at the beginning/end of the page.</param>
        /// <param name="suggestedX">Suggested offset in the page.</param>
        /// <returns>Position at the page boundary from given suggestedX.</returns>
        private ITextPointer GetPositionAtPageBoundary(bool pageTop, ITextView pageTextView, ITextPointer position, double suggestedX)
        {
            double newSuggestedX;
            int newLinesMoved;
            ITextPointer positionOut;

            // If moving to the first/last line of the next/previous TextView, there is
            // special handling needed to position at the right suggestedX. Otherwise, TextView
            // will return the same position.
            if (pageTop)
            {
                // Move line down and line up.
                positionOut = pageTextView.GetPositionAtNextLine(position, suggestedX, 1, out newSuggestedX, out newLinesMoved);
                if (newLinesMoved == 1)
                {
                    positionOut = pageTextView.GetPositionAtNextLine(positionOut, newSuggestedX, -1, out newSuggestedX, out newLinesMoved);
                }
                else
                {
                    // Line down failed, so use the first position of TextView.
                    positionOut = position;
                }
            }
            else
            {
                // Move line up and line down.
                positionOut = pageTextView.GetPositionAtNextLine(position, suggestedX, -1, out newSuggestedX, out newLinesMoved);
                if (newLinesMoved == -1)
                {
                    positionOut = pageTextView.GetPositionAtNextLine(positionOut, newSuggestedX, 1, out newSuggestedX, out newLinesMoved);
                }
                else
                {
                    // Line up failed, so use the last position of TextView.
                    positionOut = position;
                }
            }
            return positionOut;
        }

        /// <summary>
        /// Returns an active TextView that matches the supplied Point.
        /// </summary>
        /// <param name="point">Point in pixel coordinates to test.</param>
        /// <param name="snap">Snap to closest TextView.</param>
        /// <returns>An active TextView that matches the supplied Point.</returns>
        private DocumentPageTextView GetTextViewFromPoint(Point point, bool snap)
        {
            DocumentPageTextView textView = null;
            Rect textViewBounds;
            int i;

            // Try to find pageElement with exact hit.
            // Enumerate all inner TextViews and try to find exact hit for given Point.
            for (i = 0; i < _pageTextViews.Count; i++)
            {
                textViewBounds = TransformToAncestor(_pageTextViews[i].RenderScope, new Rect(_pageTextViews[i].RenderScope.RenderSize));
                if (textViewBounds.Contains(point))
                {
                    textView = _pageTextViews[i];
                    break;
                }
            }

            if (textView == null && snap)
            {
                // For each TextView calculate 'proximity' function.
                double[] textViewProximities = new double[_pageTextViews.Count];
                for (i = 0; i < _pageTextViews.Count; i++)
                {
                    textViewBounds = TransformToAncestor(_pageTextViews[i].RenderScope, new Rect(_pageTextViews[i].RenderScope.RenderSize));
                    double horz, vert;
                    if (point.X >= textViewBounds.Left && point.X <= textViewBounds.Right)
                    {
                        horz = 0;
                    }
                    else
                    {
                        horz = Math.Min(Math.Abs(point.X - textViewBounds.Left), Math.Abs(point.X - textViewBounds.Right));
                    }
                    if (point.Y >= textViewBounds.Top && point.Y <= textViewBounds.Bottom)
                    {
                        vert = 0;
                    }
                    else
                    {
                        vert = Math.Min(Math.Abs(point.Y - textViewBounds.Top), Math.Abs(point.Y - textViewBounds.Bottom));
                    }
                    textViewProximities[i] = Math.Sqrt(Math.Pow(horz, 2) + Math.Pow(vert, 2));
                }
                // Get the closest TextView according to 'proximity' function.
                double proximity = double.MaxValue;
                for (i = 0; i < textViewProximities.Length; i++)
                {
                    if (proximity > textViewProximities[i])
                    {
                        proximity = textViewProximities[i];
                        textView = _pageTextViews[i];
                    }
                }
            }

            return textView;
        }

        /// <summary>
        /// Retrieves an active TextView containing the object or character 
        /// represented by the given TextPointer.
        /// </summary>
        /// <param name="position">Position of an object/character.</param>
        /// <returns>
        /// Active TextView containing the object or character represented by 
        /// the given TextPointer.
        /// </returns>
        private DocumentPageTextView GetTextViewFromPosition(ITextPointer position)
        {
            DocumentPageTextView textView = null;
            int i;

            // Try to find pageElement with exact hit.
            for (i = 0; i < _pageTextViews.Count; i++)
            {
                if (_pageTextViews[i].Contains(position))
                {
                    textView = _pageTextViews[i];
                    break;
                }
            }

            return textView;
        }

        /// <summary>
        /// Retrieves an active TextView from DocumentPageView with specified page number.
        /// </summary>
        /// <param name="pageNumber">Page number.</param>
        /// <returns>
        /// Active TextView from DocumentPageView with specified page number.
        /// </returns>
        private DocumentPageTextView GetTextViewFromPageNumber(int pageNumber)
        {
            DocumentPageTextView textView = null;
            int i;

            // Try to find pageElement with exact hit.
            for (i = 0; i < _pageTextViews.Count; i++)
            {
                if (_pageTextViews[i].DocumentPageView.PageNumber == pageNumber)
                {
                    textView = _pageTextViews[i];
                    break;
                }
            }
            return textView;
        }

        /// <summary>
        /// Given page number and count, retrieves an active TextView at distance count from the specified page number
        /// </summary>
        /// <param name="pageNumber">Page number.</param>
        /// <param name="count">Number of pages between specified page number and desired page number</param>
        /// <param name="newPageNumber">Page number of the view that is actually returned</param>
        /// <remarks>
        /// If there is no view at distance count from the specified page number in the list of views, return the page text view that's
        /// closest in the direction of count, i.e. if we're on page 2 and count = 5, and we have pages 5 and 10 in view but not page 7, return
        /// 5, i.e. we never move by > count
        /// </remarks>
        private DocumentPageTextView GetTextViewForNextPage(int pageNumber, int count, out int newPageNumber)
        {
            Invariant.Assert(count != 0);
            newPageNumber = pageNumber + count;
            int closestPageNumber = newPageNumber;
            DocumentPageTextView textView = null;
            int closestDistance = Math.Abs(count);

            for (int i = 0; i < _pageTextViews.Count; i++)
            {
                if (_pageTextViews[i].DocumentPageView.PageNumber == newPageNumber)
                {
                    textView = _pageTextViews[i];
                    closestPageNumber = newPageNumber;
                    break;
                }
                else
                {
                    int currentPageNumber = _pageTextViews[i].DocumentPageView.PageNumber;
                    if (count > 0 && currentPageNumber > pageNumber)
                    {
                        int distance = currentPageNumber - pageNumber;
                        if (distance < closestDistance)
                        {
                            closestDistance = distance;
                            textView = _pageTextViews[i];
                            closestPageNumber = currentPageNumber;
                        }
                    }
                    else if (count < 0 && currentPageNumber < pageNumber)
                    {
                        int distance = Math.Abs(currentPageNumber - pageNumber);
                        if (distance < closestDistance)
                        {
                            closestDistance = distance;
                            textView = _pageTextViews[i];
                            closestPageNumber = currentPageNumber;
                        }
                    }
                }
            }

            if (textView != null)
            {
                newPageNumber = closestPageNumber;
            }
            else
            {
                newPageNumber = pageNumber;
                textView = GetTextViewFromPageNumber(pageNumber);
            }
            Invariant.Assert(newPageNumber >= 0);
            return textView;
        }


        /// <summary>
        /// Gets transform to ancestor for inner scope
        /// </summary>
        private Transform GetTransformToAncestor(Visual innerScope)
        {
            // NOTE: TransformToAncestor is safe (will never throw an exception).
            Transform transform = innerScope.TransformToAncestor(_renderScope) as Transform;
            if (transform == null)
            {
                transform = Transform.Identity;
            }
            return transform;
        }

        /// <summary>
        /// Transforms rectangle from inner scope.
        /// </summary>
        private Rect TransformToAncestor(Visual innerScope, Rect rect)
        {
            if (rect != Rect.Empty)
            {
                // NOTE: TransformToAncestor is safe (will never throw an exception).
                GeneralTransform transform = innerScope.TransformToAncestor(_renderScope);
                if (transform != null)
                {
                    rect = transform.TransformBounds(rect);
                }
            }
            return rect;
        }

        /// <summary>
        /// Transforms point from inner scope.
        /// </summary>
        private Point TransformToAncestor(Visual innerScope, Point point)
        {
            // NOTE: TransformToAncestor is safe (will never throw an exception).
            GeneralTransform transform = innerScope.TransformToAncestor(_renderScope);
            if (transform != null)
            {
                point = transform.Transform(point);
            }
            return point;
        }

        /// <summary>
        /// Transforms rectangle from inner scope 
        /// </summary>
        private Point TransformToDescendant(Visual innerScope, Point point)
        {
            // NOTE: TransformToAncestor is safe (will never throw an exception).
            GeneralTransform transform = innerScope.TransformToAncestor(_renderScope);
            if (transform != null)
            {
                transform = transform.Inverse;
                if (transform != null)
                {
                    point = transform.Transform(point);
                }
            }
            return point;
        }

        /// <summary>
        /// Fires BringPositionIntoViewCompleted event.
        /// </summary>
        private void OnBringPositionIntoViewCompleted(BringPositionIntoViewRequest request)
        {
            _pendingRequest = null;
            OnBringPositionIntoViewCompleted(new BringPositionIntoViewCompletedEventArgs(
                request.Position, request.Succeeded, null, false, request.UserState));
        }

        /// <summary>
        /// Fires BringPointIntoViewCompleted event.
        /// </summary>
        private void OnBringPointIntoViewCompleted(BringPointIntoViewRequest request)
        {
            _pendingRequest = null;
            OnBringPointIntoViewCompleted(new BringPointIntoViewCompletedEventArgs(
                request.Point, request.Position,
                request.Position != null, null, false, request.UserState));
        }

        /// <summary>
        /// Fires BringLineIntoViewCompleted event.
        /// </summary>
        private void OnBringLineIntoViewCompleted(BringLineIntoViewRequest request)
        {
            _pendingRequest = null;
            OnBringLineIntoViewCompleted(new BringLineIntoViewCompletedEventArgs(
                request.Position, request.SuggestedX, request.Count,
                request.NewPosition, request.NewSuggestedX, request.Count - request.NewCount,
                request.NewCount == 0, null, false, request.UserState));
        }

        /// <summary>
        /// Fires BringPageIntoViewCompleted event.
        /// </summary>
        private void OnBringPageIntoViewCompleted(BringPageIntoViewRequest request)
        {
            _pendingRequest = null;
            OnBringPageIntoViewCompleted(new BringPageIntoViewCompletedEventArgs(
                request.Position, request.SuggestedOffset, request.Count,
                request.NewPosition, request.NewSuggestedOffset, request.Count - request.NewCount,
                request.NewCount == 0, null, false, request.UserState));
        }

        /// <summary>
        /// Responds to an OnUpdated call.
        /// </summary>
        private object OnUpdatedWorker(object o)
        {
            BringLineIntoViewRequest lineRequest;
            BringPageIntoViewRequest pageRequest;
            BringPointIntoViewRequest pointRequest;
            BringPositionIntoViewRequest positionRequest;
            ITextView pageTextView;
            ITextPointer newPosition;
            Point point;
            double suggestedX;

            if (this.IsValid && _pendingRequest != null)
            {
                if (_pendingRequest is BringLineIntoViewRequest)
                {
                    lineRequest = (BringLineIntoViewRequest)_pendingRequest;

                    // Try to find TextView for DocumentPageView with stored page number.
                    pageTextView = GetTextViewFromPageNumber(lineRequest.NewPageNumber);
                    if (pageTextView != null)
                    {
                        // Transform to DocumentPageView coordinates
                        point = TransformToDescendant(pageTextView.RenderScope, new Point(lineRequest.NewSuggestedX, 0));
                        suggestedX = point.X;

                        // All positions have to be in the TextView boundary.
                        // Since we are quering another TextView, move requested position to its range.
                        if (lineRequest.Count > 0)
                        {
                            // Search for a point just outside the limits so that GetTextView.GetTextPositionFromPoint will not hit test
                            // inside anchored blocks unless they are offset out of the page.
                            newPosition = pageTextView.GetTextPositionFromPoint(new Point(-1, -1), true);
                            if (newPosition != null)
                            {
                                lineRequest.NewCount = lineRequest.NewCount - 1;
                            }
                        }
                        else
                        {
                            newPosition = pageTextView.GetTextPositionFromPoint((Point)pageTextView.RenderScope.RenderSize, true);
                            if (newPosition != null)
                            {
                                lineRequest.NewCount = lineRequest.NewCount + 1;
                            }
                        }

                        // If still have some lines to be moved, do another BringLineIntoView request.
                        // Otherwise the goal has been reached and fire completed event.
                        if (newPosition == null)
                        {
                            // New position cannot be found, return best result so far.
                            if (lineRequest.NewPosition == null)
                            {
                                lineRequest.NewPosition = lineRequest.Position;
                                lineRequest.NewCount = lineRequest.Count;
                            }
                            OnBringLineIntoViewCompleted(lineRequest);
                        }
                        else if (lineRequest.NewCount != 0)
                        {
                            lineRequest.NewPosition = newPosition;
                            BringLineIntoViewCore(lineRequest);
                        }
                        else
                        {
                            lineRequest.NewPosition = GetPositionAtPageBoundary(lineRequest.Count > 0, pageTextView, newPosition, lineRequest.NewSuggestedX);
                            OnBringLineIntoViewCompleted(lineRequest);
                        }
                    }
                    else if (IsPageNumberOutOfRange(lineRequest.NewPageNumber))
                    {
                        OnBringLineIntoViewCompleted(lineRequest);
                    }
                }
                else if (_pendingRequest is BringPageIntoViewRequest)
                {
                    pageRequest = (BringPageIntoViewRequest)_pendingRequest;

                    // Try to find TextView for DocumentPageView with stored page number.
                    pageTextView = GetTextViewFromPageNumber(pageRequest.NewPageNumber);
                    if (pageTextView != null)
                    {
                        // Transform to DocumentPageView coordinates
                        point = TransformToDescendant(pageTextView.RenderScope, pageRequest.NewSuggestedOffset);
                        Point suggestedOffset = point;

                        Invariant.Assert(pageRequest.NewCount != 0);
                        newPosition = pageTextView.GetTextPositionFromPoint(suggestedOffset, true);
                        if (newPosition != null)
                        {
                            pageRequest.NewCount = (pageRequest.Count > 0) ? pageRequest.NewCount - 1 : pageRequest.NewCount + 1;
                        }

                        // If still have some lines to be moved, do another BringLineIntoView request.
                        // Otherwise the goal has been reached and fire completed event.
                        if (newPosition == null)
                        {
                            // New position cannot be found, return best result so far.
                            if (pageRequest.NewPosition == null)
                            {
                                pageRequest.NewPosition = pageRequest.Position;
                                pageRequest.NewCount = pageRequest.Count;
                            }
                            OnBringPageIntoViewCompleted(pageRequest);
                        }
                        else if (pageRequest.NewCount != 0)
                        {
                            pageRequest.NewPosition = newPosition;
                            BringPageIntoViewCore(pageRequest);
                        }
                        else
                        {
                            pageRequest.NewPosition = newPosition;
                            OnBringPageIntoViewCompleted(pageRequest);
                        }
                    }
                    else if (IsPageNumberOutOfRange(pageRequest.NewPageNumber))
                    {
                        OnBringPageIntoViewCompleted(pageRequest);
                    }
                }
                else if (_pendingRequest is BringPointIntoViewRequest)
                {
                    pointRequest = (BringPointIntoViewRequest)_pendingRequest;

                    pageTextView = GetTextViewFromPoint(pointRequest.Point, true);
                    if (pageTextView != null)
                    {
                        // Transform to DocumentPageView coordinates and query inner TextView
                        point = TransformToDescendant(pageTextView.RenderScope, pointRequest.Point);
                        pointRequest.Position = pageTextView.GetTextPositionFromPoint(point, true);
                    }
                    OnBringPointIntoViewCompleted(pointRequest);
                }
                else if (_pendingRequest is BringPositionIntoViewRequest)
                {
                    positionRequest = (BringPositionIntoViewRequest)_pendingRequest;
                    positionRequest.Succeeded = positionRequest.Position.HasValidLayout;
                    OnBringPositionIntoViewCompleted(positionRequest);
                }
            }

            return null;
        }

        /// <summary>
        /// Distinguishes between whether a page is not available now, or will never be available.
        /// </summary>
        private bool IsPageNumberOutOfRange(int pageNumber)
        {
            if (pageNumber < 0)
            {
                return true;
            }

            IDocumentPaginatorSource document = _viewer.Document;
            if (document == null)
            {
                return true;
            }

            DocumentPaginator documentPaginator = document.DocumentPaginator;

            if (documentPaginator == null)
            {
                return true;
            }

            if (documentPaginator.IsPageCountValid && pageNumber >= documentPaginator.PageCount)
            {
                return true;
            }

            return false;
        }

        #endregion Private Methods

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

        #region Private Fields

        /// <summary>
        /// Viewer associated with TextView.
        /// </summary>
        private readonly DocumentViewerBase _viewer;

        /// <summary>
        /// Root of layout structure visualizing content.
        /// </summary>
        private readonly UIElement _renderScope;

        /// <summary>
        /// TextContainer representing content.
        /// </summary>
        private readonly ITextContainer _textContainer;

        /// <summary>
        /// Collection of hosted TextViews.
        /// </summary>
        private List<DocumentPageTextView> _pageTextViews;

        /// <summary>
        /// Pending BringIntoView request.
        /// </summary>
        private BringIntoViewRequest _pendingRequest;

        #endregion Private Fields

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

        #region Private Types

        /// <summary>
        /// Pending BringIntoView request.
        /// </summary>
        private class BringIntoViewRequest
        {
            internal BringIntoViewRequest(object userState)
            {
                this.UserState = userState;
            }
            internal readonly object UserState;
        }

        /// <summary>
        /// Pending BringPositionIntoView request.
        /// </summary>
        private class BringPositionIntoViewRequest : BringIntoViewRequest
        {
            internal BringPositionIntoViewRequest(ITextPointer position, object userState)
                : base(userState)
            {
                this.Position = position;
                this.Succeeded = false;
            }
            internal readonly ITextPointer Position;
            internal bool Succeeded;
        }

        /// <summary>
        /// Pending BringPointIntoView request.
        /// </summary>
        private class BringPointIntoViewRequest : BringIntoViewRequest
        {
            internal BringPointIntoViewRequest(Point point, object userState)
                : base(userState)
            {
                this.Point = point;
                this.Position = null;
            }
            internal readonly Point Point;
            internal ITextPointer Position;
        }

        /// <summary>
        /// Pending BringLineIntoView request.
        /// </summary>
        private class BringLineIntoViewRequest : BringIntoViewRequest
        {
            internal BringLineIntoViewRequest(ITextPointer position, double suggestedX, int count, object userState)
                : base(userState)
            {
                this.Position = position;
                this.SuggestedX = suggestedX;
                this.Count = count;
                this.NewPosition = position;
                this.NewSuggestedX = suggestedX;
                this.NewCount = count;
            }
            internal readonly ITextPointer Position;
            internal readonly double SuggestedX;
            internal readonly int Count;
            internal ITextPointer NewPosition;
            internal double NewSuggestedX;
            internal int NewCount;
            internal int NewPageNumber;
        }

        /// <summary>
        /// Pending BringPageIntoView request.
        /// </summary>
        private class BringPageIntoViewRequest : BringIntoViewRequest
        {
            internal BringPageIntoViewRequest(ITextPointer position, Point suggestedOffset, int count, object userState)
                : base(userState)
            {
                this.Position = position;
                this.SuggestedOffset = suggestedOffset;
                this.Count = count;
                this.NewPosition = position;
                this.NewSuggestedOffset = suggestedOffset;
                this.NewCount = count;
            }
            internal readonly ITextPointer Position;
            internal readonly Point SuggestedOffset;
            internal readonly int Count;
            internal ITextPointer NewPosition;
            internal Point NewSuggestedOffset;
            internal int NewCount;
            internal int NewPageNumber;
        }

        #endregion Private Types
    }
}

