/* Copyright 2014 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

(function(root, factory) {
    if (typeof define === 'function' && define.amd) {
        define('pdfjs-web/pdf_viewer', ['exports', 'pdfjs-web/ui_utils',
                'pdfjs-web/pdf_page_view', 'pdfjs-web/pdf_rendering_queue',
                'pdfjs-web/text_layer_builder', 'pdfjs-web/annotation_layer_builder',
                'pdfjs-web/pdf_link_service', 'pdfjs-web/dom_events', 'pdfjs-web/pdfjs'],
            factory);
    } else if (typeof exports !== 'undefined') {
        factory(exports, require('./ui_utils.js'), require('./pdf_page_view.js'),
            require('./pdf_rendering_queue.js'), require('./text_layer_builder.js'),
            require('./annotation_layer_builder.js'),
            require('./pdf_link_service.js'), require('./dom_events.js'),
            require('./pdfjs.js'));
    } else {
        factory((root.pdfjsWebPDFViewer = {}), root.pdfjsWebUIUtils,
            root.pdfjsWebPDFPageView, root.pdfjsWebPDFRenderingQueue,
            root.pdfjsWebTextLayerBuilder, root.pdfjsWebAnnotationLayerBuilder,
            root.pdfjsWebPDFLinkService, root.pdfjsWebDOMEvents, root.pdfjsWebPDFJS);
    }
}(this, function(exports, uiUtils, pdfPageView, pdfRenderingQueue,
                 textLayerBuilder, annotationLayerBuilder, pdfLinkService,
                 domEvents, pdfjsLib) {

    var UNKNOWN_SCALE = uiUtils.UNKNOWN_SCALE;
    var SCROLLBAR_PADDING = uiUtils.SCROLLBAR_PADDING;
    var VERTICAL_PADDING = uiUtils.VERTICAL_PADDING;
    var MAX_AUTO_SCALE = uiUtils.MAX_AUTO_SCALE;
    var CSS_UNITS = uiUtils.CSS_UNITS;
    var DEFAULT_SCALE = uiUtils.DEFAULT_SCALE;
    var DEFAULT_SCALE_VALUE = uiUtils.DEFAULT_SCALE_VALUE;
    var scrollIntoView = uiUtils.scrollIntoView;
    var watchScroll = uiUtils.watchScroll;
    var getVisibleElements = uiUtils.getVisibleElements;
    var PDFPageView = pdfPageView.PDFPageView;
    var RenderingStates = pdfRenderingQueue.RenderingStates;
    var PDFRenderingQueue = pdfRenderingQueue.PDFRenderingQueue;
    var TextLayerBuilder = textLayerBuilder.TextLayerBuilder;
    var AnnotationLayerBuilder = annotationLayerBuilder.AnnotationLayerBuilder;
    var SimpleLinkService = pdfLinkService.SimpleLinkService;

    var PresentationModeState = {
        UNKNOWN    : 0,
        NORMAL     : 1,
        CHANGING   : 2,
        FULLSCREEN : 3,
    };

    var DEFAULT_CACHE_SIZE = 10;

    /**
     * @typedef {Object} PDFViewerOptions
     * @property {HTMLDivElement} container - The container for the viewer element.
     * @property {HTMLDivElement} viewer - (optional) The viewer element.
     * @property {EventBus} eventBus - The application event bus.
     * @property {IPDFLinkService} linkService - The navigation/linking service.
     * @property {DownloadManager} downloadManager - (optional) The download
     *   manager component.
     * @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
     *   queue object.
     * @property {boolean} removePageBorders - (optional) Removes the border shadow
     *   around the pages. The default is false.
     */

    /**
     * Simple viewer control to display PDF content/pages.
     * @class
     * @implements {IRenderableView}
     */
    var PDFViewer = (function pdfViewer() {
        function PDFPageViewBuffer(size) {
            var data = [];
            this.push = function cachePush(view) {
                var i = data.indexOf(view);
                if (i >= 0) {
                    data.splice(i, 1);
                }
                data.push(view);
                if (data.length > size) {
                    data.shift().destroy();
                }
            };
            this.resize = function(newSize) {
                size = newSize;
                while (data.length > size) {
                    data.shift().destroy();
                }
            };
        }

        function isSameScale(oldScale, newScale) {
            if (newScale === oldScale) {
                return true;
            }
            if (Math.abs(newScale - oldScale) < 1e-15) {
                // Prevent unnecessary re-rendering of all pages when the scale
                // changes only because of limited numerical precision.
                return true;
            }
            return false;
        }

        /**
         * @constructs PDFViewer
         * @param {PDFViewerOptions} options
         */
        function PDFViewer(options) {
            this.container = options.container;
            this.viewer = options.viewer || options.container.firstElementChild;
            this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
            this.linkService = options.linkService || new SimpleLinkService();
            this.downloadManager = options.downloadManager || null;
            this.removePageBorders = options.removePageBorders || false;

            this.defaultRenderingQueue = !options.renderingQueue;
            if (this.defaultRenderingQueue) {
                // Custom rendering queue is not specified, using default one
                this.renderingQueue = new PDFRenderingQueue();
                this.renderingQueue.setViewer(this);
            } else {
                this.renderingQueue = options.renderingQueue;
            }

            this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
            this.presentationModeState = PresentationModeState.UNKNOWN;
            this._resetView();

            if (this.removePageBorders) {
                this.viewer.classList.add('removePageBorders');
            }
        }

        PDFViewer.prototype = /** @lends PDFViewer.prototype */{
            get pagesCount() {
                return this._pages.length;
            },

            getPageView : function(index) {
                return this._pages[index];
            },

            get currentPageNumber() {
                return this._currentPageNumber;
            },

            set currentPageNumber(val) {
                if (!this.pdfDocument) {
                    this._currentPageNumber = val;
                    return;
                }
                this._setCurrentPageNumber(val);
                // The intent can be to just reset a scroll position and/or scale.
                this._resetCurrentPageView();
            },

            _setCurrentPageNumber : function pdfViewer_setCurrentPageNumber(val) {
                if (this._currentPageNumber === val) {
                    return;
                }
                var arg;
                if (!(0 < val && val <= this.pagesCount)) {
                    arg = {
                        source             : this,
                        pageNumber         : this._currentPageNumber,
                        previousPageNumber : val
                    };
                    this.eventBus.dispatch('pagechanging', arg);
                    this.eventBus.dispatch('pagechange', arg);
                    return;
                }

                arg = {
                    source             : this,
                    pageNumber         : val,
                    previousPageNumber : this._currentPageNumber
                };
                this._currentPageNumber = val;
                this.eventBus.dispatch('pagechanging', arg);
                this.eventBus.dispatch('pagechange', arg);
            },

            /**
             * @returns {number}
             */
            get currentScale() {
                return this._currentScale !== UNKNOWN_SCALE ? this._currentScale :
                    DEFAULT_SCALE;
            },

            /**
             * @param {number} val - Scale of the pages in percents.
             */
            set currentScale(val) {
                if (isNaN(val)) {
                    throw new Error('Invalid numeric scale');
                }
                if (!this.pdfDocument) {
                    this._currentScale = val;
                    this._currentScaleValue = val !== UNKNOWN_SCALE ? val.toString() : null;
                    return;
                }
                this._setScale(val, false);
            },

            /**
             * @returns {string}
             */
            get currentScaleValue() {
                return this._currentScaleValue;
            },

            /**
             * @param val - The scale of the pages (in percent or predefined value).
             */
            set currentScaleValue(val) {
                if (!this.pdfDocument) {
                    this._currentScale = isNaN(val) ? UNKNOWN_SCALE : val;
                    this._currentScaleValue = val;
                    return;
                }
                this._setScale(val, false);
            },

            /**
             * @returns {number}
             */
            get pagesRotation() {
                return this._pagesRotation;
            },

            /**
             * @param {number} rotation - The rotation of the pages (0, 90, 180, 270).
             */
            set pagesRotation(rotation) {
                this._pagesRotation = rotation;

                for (var i = 0, l = this._pages.length; i < l; i++) {
                    var pageView = this._pages[i];
                    pageView.update(pageView.scale, rotation);
                }

                this._setScale(this._currentScaleValue, true);

                if (this.defaultRenderingQueue) {
                    this.update();
                }
            },

            /**
             * @param pdfDocument {PDFDocument}
             */
            setDocument : function(pdfDocument) {
                if (this.pdfDocument) {
                    this._resetView();
                }

                this.pdfDocument = pdfDocument;
                if (!pdfDocument) {
                    return;
                }

                var pagesCount = pdfDocument.numPages;
                var self = this;

                var resolvePagesPromise;
                var pagesPromise = new Promise(function(resolve) {
                    resolvePagesPromise = resolve;
                });
                this.pagesPromise = pagesPromise;
                pagesPromise.then(function() {
                    self.eventBus.dispatch('pagesloaded', {
                        source     : self,
                        pagesCount : pagesCount
                    });
                });

                var isOnePageRenderedResolved = false;
                var resolveOnePageRendered = null;
                var onePageRendered = new Promise(function(resolve) {
                    resolveOnePageRendered = resolve;
                });
                this.onePageRendered = onePageRendered;

                var bindOnAfterAndBeforeDraw = function(pageView) {
                    pageView.onBeforeDraw = function pdfViewLoadOnBeforeDraw() {
                        // Add the page to the buffer at the start of drawing. That way it can
                        // be evicted from the buffer and destroyed even if we pause its
                        // rendering.
                        self._buffer.push(this);
                    };
                    // when page is painted, using the image as thumbnail base
                    pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
                        if (!isOnePageRenderedResolved) {
                            isOnePageRenderedResolved = true;
                            resolveOnePageRendered();
                        }
                    };
                };

                var firstPagePromise = pdfDocument.getPage(1);
                this.firstPagePromise = firstPagePromise;

                // Fetch a single page so we can get a viewport that will be the default
                // viewport for all pages
                return firstPagePromise.then(function(pdfPage) {
                    var scale = this.currentScale;
                    var viewport = pdfPage.getViewport(scale * CSS_UNITS);
                    for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
                        var textLayerFactory = null;
                        if (!pdfjsLib.PDFJS.disableTextLayer) {
                            textLayerFactory = this;
                        }
                        var pageView = new PDFPageView({
                            container              : this.viewer,
                            eventBus               : this.eventBus,
                            id                     : pageNum,
                            scale                  : scale,
                            defaultViewport        : viewport.clone(),
                            renderingQueue         : this.renderingQueue,
                            textLayerFactory       : textLayerFactory,
                            annotationLayerFactory : this
                        });
                        bindOnAfterAndBeforeDraw(pageView);
                        this._pages.push(pageView);
                    }

                    var linkService = this.linkService;

                    // Fetch all the pages since the viewport is needed before printing
                    // starts to create the correct size canvas. Wait until one page is
                    // rendered so we don't tie up too many pdfprotects early on.
                    onePageRendered.then(function() {
                        if (!pdfjsLib.PDFJS.disableAutoFetch) {
                            var getPagesLeft = pagesCount;
                            for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
                                pdfDocument.getPage(pageNum).then(function(pageNum, pdfPage) {
                                    var pageView = self._pages[pageNum - 1];
                                    if (!pageView.pdfPage) {
                                        pageView.setPdfPage(pdfPage);
                                    }
                                    linkService.cachePageRef(pageNum, pdfPage.ref);
                                    getPagesLeft--;
                                    if (!getPagesLeft) {
                                        resolvePagesPromise();
                                    }
                                }.bind(null, pageNum));
                            }
                        } else {
                            // XXX: Printing is semi-broken with auto fetch disabled.
                            resolvePagesPromise();
                        }
                    });

                    self.eventBus.dispatch('pagesinit', {source : self});

                    if (this.defaultRenderingQueue) {
                        this.update();
                    }

                    if (this.findController) {
                        this.findController.resolveFirstPage();
                    }
                }.bind(this));
            },

            _resetView : function() {
                this._pages = [];
                this._currentPageNumber = 1;
                this._currentScale = UNKNOWN_SCALE;
                this._currentScaleValue = null;
                this._buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
                this._location = null;
                this._pagesRotation = 0;
                this._pagesRequests = [];

                var container = this.viewer;
                while (container.hasChildNodes()) {
                    container.removeChild(container.lastChild);
                }
            },

            _scrollUpdate : function PDFViewer_scrollUpdate() {
                if (this.pagesCount === 0) {
                    return;
                }
                this.update();
                for (var i = 0, ii = this._pages.length; i < ii; i++) {
                    this._pages[i].updatePosition();
                }
            },

            _setScaleDispatchEvent : function pdfViewer_setScaleDispatchEvent(
                newScale, newValue, preset) {
                var arg = {
                    source      : this,
                    scale       : newScale,
                    presetValue : preset ? newValue : undefined
                };
                this.eventBus.dispatch('scalechanging', arg);
                this.eventBus.dispatch('scalechange', arg);
            },

            _setScaleUpdatePages : function pdfViewer_setScaleUpdatePages(
                newScale, newValue, noScroll, preset) {
                this._currentScaleValue = newValue;

                if (isSameScale(this._currentScale, newScale)) {
                    if (preset) {
                        this._setScaleDispatchEvent(newScale, newValue, true);
                    }
                    return;
                }

                for (var i = 0, ii = this._pages.length; i < ii; i++) {
                    this._pages[i].update(newScale);
                }
                this._currentScale = newScale;

                if (!noScroll) {
                    var page = this._currentPageNumber, dest;
                    if (this._location && !pdfjsLib.PDFJS.ignoreCurrentPositionOnZoom &&
                        !(this.isInPresentationMode || this.isChangingPresentationMode)) {
                        page = this._location.pageNumber;
                        dest = [null, {name : 'XYZ'}, this._location.left,
                            this._location.top, null];
                    }
                    this.scrollPageIntoView(page, dest);
                }

                this._setScaleDispatchEvent(newScale, newValue, preset);

                if (this.defaultRenderingQueue) {
                    this.update();
                }
            },

            _setScale : function pdfViewer_setScale(value, noScroll) {
                var scale = parseFloat(value);

                if (scale > 0) {
                    this._setScaleUpdatePages(scale, value, noScroll, false);
                } else {
                    var currentPage = this._pages[this._currentPageNumber - 1];
                    if (!currentPage) {
                        return;
                    }
                    var hPadding = (this.isInPresentationMode || this.removePageBorders) ?
                        0 : SCROLLBAR_PADDING;
                    var vPadding = (this.isInPresentationMode || this.removePageBorders) ?
                        0 : VERTICAL_PADDING;
                    var pageWidthScale = (this.container.clientWidth - hPadding) /
                        currentPage.width * currentPage.scale;
                    var pageHeightScale = (this.container.clientHeight - vPadding) /
                        currentPage.height * currentPage.scale;
                    switch (value) {
                        case 'page-actual':
                            scale = 1;
                            break;
                        case 'page-width':
                            scale = pageWidthScale;
                            break;
                        case 'page-height':
                            scale = pageHeightScale;
                            break;
                        case 'page-fit':
                            scale = Math.min(pageWidthScale, pageHeightScale);
                            break;
                        case 'auto':
                            var isLandscape = (currentPage.width > currentPage.height);
                            // For pages in landscape mode, fit the page height to the viewer
                            // *unless* the page would thus become too wide to fit horizontally.
                            var horizontalScale = isLandscape ?
                                Math.min(pageHeightScale, pageWidthScale) : pageWidthScale;
                            scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
                            break;
                        default:
                            console.error('pdfViewSetScale: \'' + value +
                                '\' is an unknown zoom value.');
                            return;
                    }
                    this._setScaleUpdatePages(scale, value, noScroll, true);
                }
            },

            /**
             * Refreshes page view: scrolls to the current page and updates the scale.
             */
            _resetCurrentPageView : function() {
                if (this.isInPresentationMode) {
                    // Fixes the case when PDF has different page sizes.
                    this._setScale(this._currentScaleValue, true);
                }

                var pageView = this._pages[this._currentPageNumber - 1];
                scrollIntoView(pageView.div);
            },

            /**
             * Scrolls page into view.
             * @param {number} pageNumber
             * @param {Array} dest - (optional) original PDF destination array:
             *   <page-ref> </XYZ|FitXXX> <args..>
             */
            scrollPageIntoView : function PDFViewer_scrollPageIntoView(pageNumber,
                                                                       dest) {
                if (!this.pdfDocument) {
                    return;
                }

                if (this.isInPresentationMode || !dest) {
                    this._setCurrentPageNumber(pageNumber);
                    this._resetCurrentPageView();
                    return;
                }

                var pageView = this._pages[pageNumber - 1];
                var x = 0, y = 0;
                var width = 0, height = 0, widthScale, heightScale;
                var changeOrientation = (pageView.rotation % 180 === 0 ? false : true);
                var pageWidth = (changeOrientation ? pageView.height : pageView.width) /
                    pageView.scale / CSS_UNITS;
                var pageHeight = (changeOrientation ? pageView.width : pageView.height) /
                    pageView.scale / CSS_UNITS;
                var scale = 0;
                switch (dest[1].name) {
                    case 'XYZ':
                        x = dest[2];
                        y = dest[3];
                        scale = dest[4];
                        // If x and/or y coordinates are not supplied, default to
                        // _top_ left of the page (not the obvious bottom left,
                        // since aligning the bottom of the intended page with the
                        // top of the window is rarely helpful).
                        x = x !== null ? x : 0;
                        y = y !== null ? y : pageHeight;
                        break;
                    case 'Fit':
                    case 'FitB':
                        scale = 'page-fit';
                        break;
                    case 'FitH':
                    case 'FitBH':
                        y = dest[2];
                        scale = 'page-width';
                        // According to the PDF spec, section 12.3.2.2, a `null` value in the
                        // parameter should maintain the position relative to the new page.
                        if (y === null && this._location) {
                            x = this._location.left;
                            y = this._location.top;
                        }
                        break;
                    case 'FitV':
                    case 'FitBV':
                        x = dest[2];
                        width = pageWidth;
                        height = pageHeight;
                        scale = 'page-height';
                        break;
                    case 'FitR':
                        x = dest[2];
                        y = dest[3];
                        width = dest[4] - x;
                        height = dest[5] - y;
                        var hPadding = this.removePageBorders ? 0 : SCROLLBAR_PADDING;
                        var vPadding = this.removePageBorders ? 0 : VERTICAL_PADDING;

                        widthScale = (this.container.clientWidth - hPadding) /
                            width / CSS_UNITS;
                        heightScale = (this.container.clientHeight - vPadding) /
                            height / CSS_UNITS;
                        scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
                        break;
                    default:
                        return;
                }

                if (scale && scale !== this._currentScale) {
                    this.currentScaleValue = scale;
                } else if (this._currentScale === UNKNOWN_SCALE) {
                    this.currentScaleValue = DEFAULT_SCALE_VALUE;
                }

                if (scale === 'page-fit' && !dest[4]) {
                    scrollIntoView(pageView.div);
                    return;
                }

                var boundingRect = [
                    pageView.viewport.convertToViewportPoint(x, y),
                    pageView.viewport.convertToViewportPoint(x + width, y + height)
                ];
                var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
                var top = Math.min(boundingRect[0][1], boundingRect[1][1]);

                scrollIntoView(pageView.div, {left : left, top : top});
            },

            _updateLocation : function(firstPage) {
                var currentScale = this._currentScale;
                var currentScaleValue = this._currentScaleValue;
                var normalizedScaleValue =
                        parseFloat(currentScaleValue) === currentScale ?
                            Math.round(currentScale * 10000) / 100 : currentScaleValue;

                var pageNumber = firstPage.id;
                var pdfOpenParams = '#page=' + pageNumber;
                pdfOpenParams += '&zoom=' + normalizedScaleValue;
                var currentPageView = this._pages[pageNumber - 1];
                var container = this.container;
                var topLeft = currentPageView.getPagePoint(
                    (container.scrollLeft - firstPage.x),
                    (container.scrollTop - firstPage.y));
                var intLeft = Math.round(topLeft[0]);
                var intTop = Math.round(topLeft[1]);
                pdfOpenParams += ',' + intLeft + ',' + intTop;

                this._location = {
                    pageNumber    : pageNumber,
                    scale         : normalizedScaleValue,
                    top           : intTop,
                    left          : intLeft,
                    pdfOpenParams : pdfOpenParams
                };
            },

            update : function PDFViewer_update() {
                var visible = this._getVisiblePages();
                var visiblePages = visible.views;
                if (visiblePages.length === 0) {
                    return;
                }

                var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE,
                    2 * visiblePages.length + 1);
                this._buffer.resize(suggestedCacheSize);

                this.renderingQueue.renderHighestPriority(visible);

                var currentId = this._currentPageNumber;
                var firstPage = visible.first;

                for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
                     i < ii; ++i) {
                    var page = visiblePages[i];

                    if (page.percent < 100) {
                        break;
                    }
                    if (page.id === currentId) {
                        stillFullyVisible = true;
                        break;
                    }
                }

                if (!stillFullyVisible) {
                    currentId = visiblePages[0].id;
                }

                if (!this.isInPresentationMode) {
                    this._setCurrentPageNumber(currentId);
                }

                this._updateLocation(firstPage);

                this.eventBus.dispatch('updateviewarea', {
                    source   : this,
                    location : this._location
                });
            },

            containsElement : function(element) {
                return this.container.contains(element);
            },

            focus : function() {
                this.container.focus();
            },

            get isInPresentationMode() {
                return this.presentationModeState === PresentationModeState.FULLSCREEN;
            },

            get isChangingPresentationMode() {
                return this.presentationModeState === PresentationModeState.CHANGING;
            },

            get isHorizontalScrollbarEnabled() {
                return (this.isInPresentationMode ?
                    false : (this.container.scrollWidth > this.container.clientWidth));
            },

            _getVisiblePages : function() {
                if (!this.isInPresentationMode) {
                    return getVisibleElements(this.container, this._pages, true);
                } else {
                    // The algorithm in getVisibleElements doesn't work in all browsers and
                    // configurations when presentation mode is active.
                    var visible = [];
                    var currentPage = this._pages[this._currentPageNumber - 1];
                    visible.push({id : currentPage.id, view : currentPage});
                    return {first : currentPage, last : currentPage, views : visible};
                }
            },

            cleanup : function() {
                for (var i = 0, ii = this._pages.length; i < ii; i++) {
                    if (this._pages[i] &&
                        this._pages[i].renderingState !== RenderingStates.FINISHED) {
                        this._pages[i].reset();
                    }
                }
            },

            /**
             * @param {PDFPageView} pageView
             * @returns {PDFPage}
             * @private
             */
            _ensurePdfPageLoaded : function(pageView) {
                if (pageView.pdfPage) {
                    return Promise.resolve(pageView.pdfPage);
                }
                var pageNumber = pageView.id;
                if (this._pagesRequests[pageNumber]) {
                    return this._pagesRequests[pageNumber];
                }
                var promise = this.pdfDocument.getPage(pageNumber).then(
                    function(pdfPage) {
                        pageView.setPdfPage(pdfPage);
                        this._pagesRequests[pageNumber] = null;
                        return pdfPage;
                    }.bind(this));
                this._pagesRequests[pageNumber] = promise;
                return promise;
            },

            forceRendering : function(currentlyVisiblePages) {
                var visiblePages = currentlyVisiblePages || this._getVisiblePages();
                var pageView = this.renderingQueue.getHighestPriority(visiblePages,
                    this._pages,
                    this.scroll.down);
                if (pageView) {
                    this._ensurePdfPageLoaded(pageView).then(function() {
                        this.renderingQueue.renderView(pageView);
                    }.bind(this));
                    return true;
                }
                return false;
            },

            getPageTextContent : function(pageIndex) {
                return this.pdfDocument.getPage(pageIndex + 1).then(function(page) {
                    return page.getTextContent({normalizeWhitespace : true});
                });
            },

            /**
             * @param {HTMLDivElement} textLayerDiv
             * @param {number} pageIndex
             * @param {PageViewport} viewport
             * @returns {TextLayerBuilder}
             */
            createTextLayerBuilder : function(textLayerDiv, pageIndex, viewport) {
                return new TextLayerBuilder({
                    textLayerDiv   : textLayerDiv,
                    eventBus       : this.eventBus,
                    pageIndex      : pageIndex,
                    viewport       : viewport,
                    findController : this.isInPresentationMode ? null : this.findController
                });
            },

            /**
             * @param {HTMLDivElement} pageDiv
             * @param {PDFPage} pdfPage
             * @returns {AnnotationLayerBuilder}
             */
            createAnnotationLayerBuilder : function(pageDiv, pdfPage) {
                return new AnnotationLayerBuilder({
                    pageDiv         : pageDiv,
                    pdfPage         : pdfPage,
                    linkService     : this.linkService,
                    downloadManager : this.downloadManager
                });
            },

            setFindController : function(findController) {
                this.findController = findController;
            },
        };

        return PDFViewer;
    })();

    exports.PresentationModeState = PresentationModeState;
    exports.PDFViewer = PDFViewer;
}));
