// 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: Provides a view port for a page of content for a DocumentPage.
//

using System.Windows.Automation;            // AutomationPattern
using System.Windows.Automation.Peers;      // AutomationPeer
using System.Windows.Controls;              // StretchDirection
using System.Windows.Controls.Primitives;   // DocumentViewerBase
using System.Windows.Documents;             // DocumentPaginator
using System.Windows.Media;                 // Visual
using System.Windows.Media.Imaging;         // RenderTargetBitmap
using System.Windows.Threading;             // Dispatcher
using MS.Internal;                          // Invariant
using MS.Internal.Documents;                // DocumentPageHost, DocumentPageTextView
using MS.Internal.Automation;               // TextAdaptor
using MS.Internal.KnownBoxes;               // BooleanBoxes


namespace System.Windows.Controls.Primitives
{
    /// <summary> 
    /// Provides a view port for a page of content for a DocumentPage.
    /// </summary>
    public class DocumentPageView : FrameworkElement, IServiceProvider, IDisposable
    {
        //-------------------------------------------------------------------
        //
        //  Constructors
        //
        //-------------------------------------------------------------------

        #region Constructors

        /// <summary> 
        /// Create an instance of a DocumentPageView.
        /// </summary>
        /// <remarks>
        /// This does basic initialization of the DocumentPageView.  All subclasses
        /// must call the base constructor to perform this initialization.
        /// </remarks>
        public DocumentPageView() : base()
        {
            _pageZoom = 1.0;
        }

        /// <summary>
        /// Static ctor. Initializes property metadata.
        /// </summary>
        static DocumentPageView()
        {
            ClipToBoundsProperty.OverrideMetadata(typeof(DocumentPageView), new PropertyMetadata(BooleanBoxes.TrueBox));
        }

        #endregion

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

        #region Public Properties

        /// <summary>
        /// The Paginator from which this DocumentPageView retrieves pages.
        /// </summary>
        public DocumentPaginator DocumentPaginator 
        {
            get { return _documentPaginator; }
            set
            {
                CheckDisposed();
                if (_documentPaginator != value)
                {
                    // Cleanup all state associated with old Paginator.
                    if (_documentPaginator != null)
                    {
                        _documentPaginator.GetPageCompleted -= new GetPageCompletedEventHandler(HandleGetPageCompleted);
                        _documentPaginator.PagesChanged -= new PagesChangedEventHandler(HandlePagesChanged);
                        DisposeCurrentPage();
                        DisposeAsyncPage();
                    }

                    Invariant.Assert(_documentPage == null);
                    Invariant.Assert(_documentPageAsync == null);
                    _documentPaginator = value;
                    _textView = null;

                    // Register for events on new Paginator and invalidate 
                    // measure to force content update.
                    if (_documentPaginator != null)
                    {
                        _documentPaginator.GetPageCompleted += new GetPageCompletedEventHandler(HandleGetPageCompleted);
                        _documentPaginator.PagesChanged += new PagesChangedEventHandler(HandlePagesChanged);
                    }
                    InvalidateMeasure();
                }
            }
        }

        /// <summary>
        /// The DocumentPage for the displayed page.
        /// </summary>
        public DocumentPage DocumentPage
        {
            get { return (_documentPage == null) ? DocumentPage.Missing : _documentPage; }
        }

        /// <summary>
        /// The page number displayed; no content is displayed if this number is negative.
        /// PageNumber is zero-based.
        /// </summary>
        public int PageNumber
        {
            get { return (int) GetValue(PageNumberProperty); }
            set { SetValue(PageNumberProperty, value); }
        }

        /// <summary>
        /// Controls the stretching behavior for the page.
        /// </summary>
        public Stretch Stretch
        {
            get { return (Stretch)GetValue(StretchProperty); }
            set { SetValue(StretchProperty, value); }
        }

        /// <summary>
        /// Specifies the directions in which page may be stretched.
        /// </summary>
        public StretchDirection StretchDirection
        {
            get { return (StretchDirection)GetValue(StretchDirectionProperty); }
            set { SetValue(StretchDirectionProperty, value); }
        }

        #region Public Dynamic Properties

        /// <summary>
        /// <see cref="PageNumber"/>
        /// </summary>
        public static readonly DependencyProperty PageNumberProperty = 
                DependencyProperty.Register(
                        "PageNumber", 
                        typeof(int), 
                        typeof(DocumentPageView),
                        new FrameworkPropertyMetadata(
                                0, 
                                FrameworkPropertyMetadataOptions.AffectsMeasure, 
                                new PropertyChangedCallback(OnPageNumberChanged)));

        /// <summary>
        /// <see cref="Stretch" />
        /// </summary>
        public static readonly DependencyProperty StretchProperty =
                Viewbox.StretchProperty.AddOwner(
                        typeof(DocumentPageView),
                        new FrameworkPropertyMetadata(
                                Stretch.Uniform, 
                                FrameworkPropertyMetadataOptions.AffectsMeasure));

        /// <summary>
        /// <see cref="StretchDirection" />
        /// </summary>
        public static readonly DependencyProperty StretchDirectionProperty = 
                Viewbox.StretchDirectionProperty.AddOwner(
                        typeof(DocumentPageView),
                        new FrameworkPropertyMetadata(
                                StretchDirection.DownOnly, 
                                FrameworkPropertyMetadataOptions.AffectsMeasure));

        #endregion Public Dynamic Properties

        #endregion Public Properties

        //-------------------------------------------------------------------
        //
        //  Public Events
        //
        //-------------------------------------------------------------------

        #region Public Events

        /// <summary>
        /// Fired after a DocumentPage.Visual is connected.
        /// </summary>
        public event EventHandler PageConnected;

        /// <summary>
        /// Fired after a DocumentPage.Visual is disconnected.
        /// </summary>
        public event EventHandler PageDisconnected;

        #endregion Public Events

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

        #region Protected Methods

        protected override void OnDpiChanged(DpiScale oldDpiScaleInfo, DpiScale newDpiScaleInfo)
        {
            DisposeCurrentPage();
            DisposeAsyncPage();
        }

        /// <summary>
        /// Content measurement.
        /// </summary>
        /// <param name="availableSize">Available size that parent can give to the child. This is soft constraint.</param>
        /// <returns>The DocumentPageView's desired size.</returns>
        protected override sealed Size MeasureOverride(Size availableSize)
        {
            Size newPageSize, pageZoom;
            Size pageSize;
            Size desiredSize = new Size(); // If no page is available, return (0,0) as size.

            CheckDisposed();

            if (_suspendLayout)
            {
                desiredSize = this.DesiredSize;
            }
            else if (_documentPaginator != null)
            {
                // Reflow content if needed.
                if (ShouldReflowContent())
                {
                    // Reflow is disabled when dealing with infinite size in both directions.
                    // If only one dimention is infinte, calculate value based on PageSize of the
                    // document and Stretching properties.
                    if (!Double.IsInfinity(availableSize.Width) || !Double.IsInfinity(availableSize.Height))
                    {
                        pageSize = _documentPaginator.PageSize;
                        if (Double.IsInfinity(availableSize.Width))
                        {
                            newPageSize = new Size();
                            newPageSize.Height = availableSize.Height / _pageZoom;
                            newPageSize.Width = newPageSize.Height * (pageSize.Width / pageSize.Height); // Keep aspect ratio.
                        }
                        else if (Double.IsInfinity(availableSize.Height))
                        {
                            newPageSize = new Size();
                            newPageSize.Width = availableSize.Width / _pageZoom;
                            newPageSize.Height = newPageSize.Width * (pageSize.Height / pageSize.Width); // Keep aspect ratio.
                        }
                        else
                        {
                            newPageSize = new Size(availableSize.Width / _pageZoom, availableSize.Height / _pageZoom);
                        }
                        if (!DoubleUtil.AreClose(pageSize, newPageSize))
                        {
                            _documentPaginator.PageSize = newPageSize;
                        }
                    }
                }

                // If the main page or pending async page are not available yet, 
                // asynchronously request new page from Paginator.
                if (_documentPage == null && _documentPageAsync == null)
                {
                    if (PageNumber >= 0)
                    {
                        if (_useAsynchronous)
                        {                            
                            _documentPaginator.GetPageAsync(PageNumber, this);
                        }
                        else
                        {
                            _documentPageAsync = _documentPaginator.GetPage(PageNumber);
                            if (_documentPageAsync == null)
                            {
                                _documentPageAsync = DocumentPage.Missing;
                            }
                        }
                    }
                    else
                    {
                        _documentPage = DocumentPage.Missing;
                    }
                }

                // If pending async page is available, discard the main page and
                // set _documentPage to _documentPageAsync. 
                if (_documentPageAsync != null)
                {
                    // Do cleanup for currently used page, because it gets replaced.
                    DisposeCurrentPage();
                    // DisposeCurrentPage raises PageDisposed and DocumentPage.PageDestroyed events.
                    // Handlers for those events may dispose _documentPageAsync. Treat this situation
                    // as missing page.
                    if (_documentPageAsync == null)
                    {
                        _documentPageAsync = DocumentPage.Missing;
                    }
                    if (_pageVisualClone != null)
                    {
                        RemoveDuplicateVisual();
                    }

                    // Replace the main page with cached async page.
                    _documentPage = _documentPageAsync;
                    if (_documentPage != DocumentPage.Missing)
                    {
                        _documentPage.PageDestroyed += new EventHandler(HandlePageDestroyed);
                        _documentPageAsync.PageDestroyed -= new EventHandler(HandleAsyncPageDestroyed);
                    }
                    _documentPageAsync = null;

                    // Set a flag that will indicate that a PageConnected must be fired in 
                    // ArrangeOverride
                    _newPageConnected = true;
                }

                // If page is available, return its size as desired size.
                if (_documentPage != null && _documentPage != DocumentPage.Missing)
                {
                    pageSize = new Size(_documentPage.Size.Width * _pageZoom, _documentPage.Size.Height * _pageZoom);
                    pageZoom = Viewbox.ComputeScaleFactor(availableSize, pageSize, this.Stretch, this.StretchDirection);
                    desiredSize = new Size(pageSize.Width * pageZoom.Width, pageSize.Height * pageZoom.Height);
                }

                if (_pageVisualClone != null)
                {
                    desiredSize = _visualCloneSize;
                }
            }

            return desiredSize;
        }
        
        /// <summary>
        /// Content arrangement.
        /// </summary>
        /// <param name="finalSize">The final size that element should use to arrange itself and its children.</param>
        protected override sealed Size ArrangeOverride(Size finalSize)
        {
            Transform pageTransform;
            ScaleTransform pageScaleTransform;
            Visual pageVisual;
            Size pageSize, pageZoom;
            
            CheckDisposed();

            if (_pageVisualClone == null)
            {
                if (_pageHost == null)
                {
                    _pageHost = new DocumentPageHost();
                    this.AddVisualChild(_pageHost);
                }
                Invariant.Assert(_pageHost != null);

                pageVisual = (_documentPage == null) ? null : _documentPage.Visual;
                if (pageVisual == null)
                {
                    // Remove existing visiual children.
                    _pageHost.PageVisual = null;

                    // Reset offset and transform on the page host before Arrange
                    _pageHost.CachedOffset = new Point();
                    _pageHost.RenderTransform = null;

                    // Size for the page host needs to be set to finalSize
                    _pageHost.Arrange(new Rect(_pageHost.CachedOffset, finalSize));
                }
                else
                {
                    // Add visual representing the page contents. For performance reasons
                    // first check if it is already insered there.
                    if (_pageHost.PageVisual != pageVisual)
                    {
                        // There might be a case where a visual associated with a page was 
                        // inserted to a visual tree before. It got removed later, but GC did not
                        // destroy its parent yet. To workaround this case always check for the parent
                        // of page visual and disconnect it, when necessary.
                        DocumentPageHost.DisconnectPageVisual(pageVisual);

                        _pageHost.PageVisual = pageVisual;
                    }
                
                    // Compute transform to be applied to the page visual. First take into account
                    // mirroring transform, if necessary. Apply also scaling transform.
                    pageSize = _documentPage.Size;
                    pageTransform = Transform.Identity;

                    // DocumentPage.Visual is always LeftToRight, so if the current
                    // FlowDirection is RightToLeft, need to unmirror the child visual.
                    if (FlowDirection == FlowDirection.RightToLeft)
                    {
                        pageTransform = new MatrixTransform(-1.0, 0.0, 0.0, 1.0, pageSize.Width, 0.0);
                    }

                    // Apply zooming
                    if (!DoubleUtil.IsOne(_pageZoom))
                    {
                        pageScaleTransform = new ScaleTransform(_pageZoom, _pageZoom);
                        if (pageTransform == Transform.Identity)
                        {
                            pageTransform = pageScaleTransform;
                        }
                        else
                        {
                            pageTransform = new MatrixTransform(pageTransform.Value * pageScaleTransform.Value);
                        }
                        pageSize = new Size(pageSize.Width * _pageZoom, pageSize.Height * _pageZoom);
                    }

                    // Apply stretch properties
                    pageZoom = Viewbox.ComputeScaleFactor(finalSize, pageSize, this.Stretch, this.StretchDirection);
                    if (!DoubleUtil.IsOne(pageZoom.Width) || !DoubleUtil.IsOne(pageZoom.Height))
                    {
                        pageScaleTransform = new ScaleTransform(pageZoom.Width, pageZoom.Height);
                        if (pageTransform == Transform.Identity)
                        {
                            pageTransform = pageScaleTransform;
                        }
                        else
                        {
                            pageTransform = new MatrixTransform(pageTransform.Value * pageScaleTransform.Value);
                        }
                        pageSize = new Size(pageSize.Width * pageZoom.Width, pageSize.Height * pageZoom.Height);
                    }

                    // Set offset and transform on the page host before Arrange
                    _pageHost.CachedOffset = new Point((finalSize.Width - pageSize.Width) / 2, (finalSize.Height - pageSize.Height) / 2);
                    _pageHost.RenderTransform = pageTransform;

                    // Arrange pagehost to original size of the page.
                    _pageHost.Arrange(new Rect(_pageHost.CachedOffset, _documentPage.Size));
                }

                // Fire sync notification if new page was connected.
                if (_newPageConnected)
                {
                    OnPageConnected();
                }
                
                // Transform for the page has been changed, need to notify TextView about the changes.
                OnTransformChangedAsync();
            }
            else
            {
                if (_pageHost.PageVisual != _pageVisualClone)
                {
                    // Remove existing visiual children.
                    _pageHost.PageVisual = _pageVisualClone;
                    // Size for the page host needs to be set to finalSize

                    // Use previous offset and transform
                    _pageHost.Arrange(new Rect(_pageHost.CachedOffset, finalSize));
                }
            }

            return base.ArrangeOverride(finalSize);
        }

        /// <summary>
        /// Derived class must implement to support Visual children. The method must return
        /// the child at the specified index. Index must be between 0 and GetVisualChildrenCount-1.
        /// </summary>
        protected override Visual GetVisualChild(int index)
        {
            if (index != 0 || _pageHost == null)
            {
                throw new ArgumentOutOfRangeException("index", index, SR.Visual_ArgumentOutOfRange);
            }
            return _pageHost;
        }

        /// <summary>
        /// Dispose the object.
        /// </summary>
        protected void Dispose()
        {
            if (!_disposed)
            {
                _disposed = true;

                // Cleanup all state associated with Paginator.
                if (_documentPaginator != null)
                {
                    _documentPaginator.GetPageCompleted -= new GetPageCompletedEventHandler(HandleGetPageCompleted);
                    _documentPaginator.PagesChanged -= new PagesChangedEventHandler(HandlePagesChanged);
                    _documentPaginator.CancelAsync(this);
                    DisposeCurrentPage();
                    DisposeAsyncPage();                                        
                }
                Invariant.Assert(_documentPage == null);
                Invariant.Assert(_documentPageAsync == null);
                _documentPaginator = null;
                _textView = null;
            }
        }

        /// <summary>
        /// Returns service objects associated with this control.
        /// This method should be called by IServiceProvider.GetService implementation 
        /// for DocumentPageView or subclasses.
        /// </summary>
        /// <param name="serviceType">Specifies the type of service object to get.</param>
        protected object GetService(Type serviceType)
        {
            object service = null;
            ArgumentNullException.ThrowIfNull(serviceType);
            CheckDisposed();

            // No service is available if the Content does not provide
            // any services.
            if (_documentPaginator != null && _documentPaginator is IServiceProvider)
            {
                // Following services are available:
                // (1) TextView - wrapper for TextView exposed by the current page.
                // (2) TextContainer - the service object is retrieved from DocumentPaginator.
                if (serviceType == typeof(ITextView))
                {
                    if (_textView == null)
                    {
                        ITextContainer tc = ((IServiceProvider)_documentPaginator).GetService(typeof(ITextContainer)) as ITextContainer;
                        if (tc != null)
                        {
                            _textView = new DocumentPageTextView(this, tc);
                        }
                    }
                    service = _textView;
                }
                else if (serviceType == typeof(TextContainer) || serviceType == typeof(ITextContainer))
                {
                    service = ((IServiceProvider)_documentPaginator).GetService(serviceType);
                }
            }
            return service;
        }

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

        #endregion Protected Methods

        //-------------------------------------------------------------------
        //
        //  Protected Properties
        //
        //-------------------------------------------------------------------

        #region Protected Properties

        /// <summary>
        /// Whether this DocumentPageView has been disposed.
        /// </summary>
        protected bool IsDisposed
        {
            get { return _disposed; }
        }

        /// <summary>
        /// Derived classes override this property to enable the Visual code to enumerate 
        /// the Visual children. Derived classes need to return the number of children
        /// from this method.
        /// </summary>        
        protected override int VisualChildrenCount
        {
            get { return _pageHost != null ? 1 : 0; }
        }

        #endregion Protected Properties

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

        #region Internal Methods

        /// <summary>
        /// Sets the zoom applied to the page being displayed.
        /// </summary>
        /// <param name="pageZoom">Page zooom value.</param>
        internal void SetPageZoom(double pageZoom)
        {
            Invariant.Assert(!DoubleUtil.LessThanOrClose(pageZoom, 0d) && !Double.IsInfinity(pageZoom));
            Invariant.Assert(!_disposed);

            if (!DoubleUtil.AreClose(_pageZoom, pageZoom))
            {
                _pageZoom = pageZoom;
                InvalidateMeasure();
            }
        }

        /// <summary>
        /// Suspends page layout.
        /// </summary>
        internal void SuspendLayout()
        {
            _suspendLayout = true;
            _pageVisualClone = DuplicatePageVisual();
            _visualCloneSize = this.DesiredSize;
        }

        /// <summary>
        /// Resumes page layout.
        /// </summary>
        internal void ResumeLayout()
        {
            _suspendLayout = false;
            _pageVisualClone = null;
            InvalidateMeasure();
        }

        /// <summary>
        /// Duplicates the current page visual, if possible
        /// </summary>
        internal void DuplicateVisual()
        {
            if (_documentPage != null && _pageVisualClone == null)
            {
                _pageVisualClone = DuplicatePageVisual();
                _visualCloneSize = this.DesiredSize;
                InvalidateArrange();
            }
        }

        /// <summary>
        /// Clears the duplicated page visual, if one exists.
        /// </summary>
        internal void RemoveDuplicateVisual()
        {
            if (_pageVisualClone != null)
            {
                _pageVisualClone = null;
                InvalidateArrange();
            }
        }

        #endregion Internal Methods

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

        #region Internal Properties

        /// <summary>
        ///    Default is true.  Controls whether we use asynchronous mode to 
        ///    request the DocumentPage.  In some cases, such as synchronous
        ///    printing, we don't want to wait for the asynchronous events.
        /// </summary>
        internal bool UseAsynchronousGetPage
        {
            get { return _useAsynchronous; }
            set { _useAsynchronous = value; }
        }

        /// <summary>
        /// The DocumentPage for the displayed page.
        /// </summary>
        internal DocumentPage DocumentPageInternal
        {
            get { return _documentPage; }
        }

        #endregion Internal Properties

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

        #region Private Methods

        /// <summary>
        /// Handles PageDestroyed event raised for the current DocumentPage.
        /// </summary>
        /// <param name="sender">Source of the event.</param>
        /// <param name="e">Not used.</param>
        private void HandlePageDestroyed(object sender, EventArgs e)
        {
            if (!_disposed)
            {
                InvalidateMeasure();
                DisposeCurrentPage();
            }
        }

        /// <summary>
        /// Handles PageDestroyed event raised for the cached async DocumentPage.
        /// </summary>
        /// <param name="sender">Source of the event.</param>
        /// <param name="e">Not used.</param>
        private void HandleAsyncPageDestroyed(object sender, EventArgs e)
        {
            if (!_disposed)
            {
                DisposeAsyncPage();
            }
        }

        /// <summary>
        /// Handles GetPageCompleted event raised by the DocumentPaginator.
        /// </summary>
        /// <param name="sender">Source of the event.</param>
        /// <param name="e">Details about this event.</param>
        private void HandleGetPageCompleted(object sender, GetPageCompletedEventArgs e)
        {
            if (!_disposed && (e != null) && !e.Cancelled && e.Error == null)
            {
                if (e.PageNumber == this.PageNumber && e.UserState == this)
                {
                    if (_documentPageAsync != null && _documentPageAsync != DocumentPage.Missing)
                    {
                        _documentPageAsync.PageDestroyed -= new EventHandler(HandleAsyncPageDestroyed);
                    }
                    _documentPageAsync = e.DocumentPage;
                    if (_documentPageAsync == null)
                    {
                        _documentPageAsync = DocumentPage.Missing;
                    }
                    if (_documentPageAsync != DocumentPage.Missing)
                    {
                        _documentPageAsync.PageDestroyed += new EventHandler(HandleAsyncPageDestroyed);                            
                    }
                    InvalidateMeasure();                        
                }
                // else; the page is not ours
            }
        }

        /// <summary>
        /// Handles PagesChanged event raised by the DocumentPaginator.
        /// </summary>
        /// <param name="sender">Source of the event.</param>
        /// <param name="e">Details about this event.</param>
        private void HandlePagesChanged(object sender, PagesChangedEventArgs e)
        {
            if (!_disposed && (e != null))
            {
                if (this.PageNumber >= e.Start && 
                    (e.Count == int.MaxValue || this.PageNumber <= e.Start + e.Count))
                {
                    OnPageContentChanged();
                }
            }
        }

        /// <summary>
        /// Async notification about transform changes for embedded page.
        /// </summary>
        private void OnTransformChangedAsync()
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                new DispatcherOperationCallback(OnTransformChanged), null);
        }

        /// <summary>
        /// Notification about transform changes for embedded page.
        /// </summary>
        /// <param name="arg">Not used.</param>
        /// <returns>Not used.</returns>
        private object OnTransformChanged(object arg)
        {
            if (_textView != null && _documentPage != null)
            {
                _textView.OnTransformChanged();
            }
            return null;
        }

        /// <summary>
        /// Raises PageConnected event.
        /// </summary>
        private void OnPageConnected()
        {
            _newPageConnected = false;
            if (_textView != null)
            {
                _textView.OnPageConnected();
            }
            if (this.PageConnected != null && _documentPage != null)
            {
                this.PageConnected(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Raises PageDisconnected event.
        /// </summary>
        private void OnPageDisconnected()
        {
            if (_textView != null)
            {
                _textView.OnPageDisconnected();
            }
            if (this.PageDisconnected != null)
            {
                this.PageDisconnected(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Responds to page content change.
        /// </summary>
        private void OnPageContentChanged()
        {
            // Force remeasure which will cause to reget DocumentPage
            InvalidateMeasure();
            // Do cleanup for currently used page, because it gets replaced.
            DisposeCurrentPage();
            DisposeAsyncPage();
        }

        /// <summary>
        /// Disposes the current DocumentPage.
        /// </summary>
        private void DisposeCurrentPage()
        {
            // Do cleanup for currently used page, because it gets replaced.
            if (_documentPage != null)
            {
                // Remove visual for currently used page.
                if (_pageHost != null)
                {
                    _pageHost.PageVisual = null;
                }

                // Clear TextView & DocumentPage
                if (_documentPage != DocumentPage.Missing)
                {
                    _documentPage.PageDestroyed -= new EventHandler(HandlePageDestroyed);                    
                }
                if (_documentPage is IDisposable)
                {
                    ((IDisposable)_documentPage).Dispose();
                }
                _documentPage = null;               

                OnPageDisconnected();
            }          
        }

        /// <summary>
        /// Disposes pending async DocumentPage.
        /// </summary>
        private void DisposeAsyncPage()
        {
            // Do cleanup for cached async page.
            if (_documentPageAsync != null)
            {
                if (_documentPageAsync != DocumentPage.Missing)
                {
                    _documentPageAsync.PageDestroyed -= new EventHandler(HandleAsyncPageDestroyed);                    
                }
                if (_documentPageAsync is IDisposable)
                {
                    ((IDisposable)_documentPageAsync).Dispose();
                }                
                _documentPageAsync = null;
            }
        }

        /// <summary>
        /// Checks if the instance is already disposed. 
        /// </summary>
        private void CheckDisposed()
        {
            ObjectDisposedException.ThrowIf(_disposed, typeof(DocumentPageView));
        }

        /// <summary>
        /// Check whether content needs to be reflowed.
        /// </summary>
        /// <returns>True, if content needs to be reflowed.</returns>
        private bool ShouldReflowContent()
        {
            bool shouldReflow = false;
            DocumentViewerBase hostViewer;

            if (DocumentViewerBase.GetIsMasterPage(this))
            {
                hostViewer = GetHostViewer();
                if (hostViewer != null)
                {
                    shouldReflow = hostViewer.IsMasterPageView(this);
                }
            }
            return shouldReflow;
        }

        /// <summary>
        /// Retrieves DocumentViewerBase that hosts this view.
        /// </summary>
        /// <returns>DocumentViewerBase that hosts this view.</returns>
        private DocumentViewerBase GetHostViewer()
        {
            DocumentViewerBase hostViewer = null;
            Visual visualParent;

            // First do quick check for TemplatedParent. It will cover good
            // amount of cases, because static viewers will have their 
            // DocumentPageViews defined in the style.
            // If quick check does not work, do a visual tree walk.
            if (this.TemplatedParent is DocumentViewerBase)
            {
                hostViewer = (DocumentViewerBase)this.TemplatedParent;
            }
            else
            {
                // Check if hosted by DocumentViewerBase.
                visualParent = VisualTreeHelper.GetParent(this) as Visual;
                while (visualParent != null)
                {
                    if (visualParent is DocumentViewerBase)
                    {
                        hostViewer = (DocumentViewerBase)visualParent;
                        break;
                    }
                    visualParent = VisualTreeHelper.GetParent(visualParent) as Visual;
                }
            }
            return hostViewer;
        }


        /// <summary>
        /// The PageNumber has changed and needs to be updated.
        /// </summary>
        private static void OnPageNumberChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Invariant.Assert(d != null && d is DocumentPageView);
            ((DocumentPageView)d).OnPageContentChanged();
        }

        /// <summary>
        /// Duplicates content of the PageVisual.
        /// </summary>
        private DrawingVisual DuplicatePageVisual()
        {
            DrawingVisual drawingVisual = null;
            if (_pageHost != null && _pageHost.PageVisual != null && _documentPage.Size != Size.Empty)
            {
                const double maxWidth = 4096.0;
                const double maxHeight = maxWidth;

                Rect pageVisualRect = new Rect(_documentPage.Size);

                pageVisualRect.Width = Math.Min(pageVisualRect.Width, maxWidth);
                pageVisualRect.Height = Math.Min(pageVisualRect.Height, maxHeight);

                drawingVisual = new DrawingVisual();

                try
                {
                    if(pageVisualRect.Width > 1.0 && pageVisualRect.Height > 1.0)
                    {
                        RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap((int)pageVisualRect.Width, (int)pageVisualRect.Height, 96.0, 96.0, PixelFormats.Pbgra32);
                        renderTargetBitmap.Render(_pageHost.PageVisual);

                        ImageBrush imageBrush = new ImageBrush(renderTargetBitmap);
                        drawingVisual.Opacity = 0.50;
                        using (DrawingContext dc = drawingVisual.RenderOpen())
                        {
                            dc.DrawRectangle(imageBrush, null, pageVisualRect);
                        }
                    }
                }
                catch(System.OverflowException)
                {
                    // Ignore overflow exception - caused by render target creation not possible under current memory conditions.
                }
            }
            return drawingVisual;
        }

        #endregion Private Methods

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

        #region Private Fields

        private DocumentPaginator _documentPaginator;
        private double _pageZoom;
        private DocumentPage _documentPage;
        private DocumentPage _documentPageAsync;
        private DocumentPageTextView _textView;
        private DocumentPageHost _pageHost;
        private Visual _pageVisualClone;
        private Size _visualCloneSize;
        private bool _useAsynchronous = true;
        private bool _suspendLayout;
        private bool _disposed;
        private bool _newPageConnected;        

        #endregion Private Fields

        //-------------------------------------------------------------------
        //
        //  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 this.GetService(serviceType);
        }

        #endregion IServiceProvider Members

        //-------------------------------------------------------------------
        //
        //  IDisposable Members
        //
        //-------------------------------------------------------------------

        #region IDisposable Members

        /// <summary>
        /// Dispose the object.
        /// </summary>
        void IDisposable.Dispose()
        {
            this.Dispose();
        }

        #endregion IDisposable Members
    }
}



