/* Copyright 2012 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_thumbnail_view', ['exports',
            'pdfjs-web/ui_utils', 'pdfjs-web/pdf_rendering_queue'], factory);
    } else if (typeof exports !== 'undefined') {
        factory(exports, require('./ui_utils.js'),
            require('./pdf_rendering_queue.js'));
    } else {
        factory((root.pdfjsWebPDFThumbnailView = {}), root.pdfjsWebUIUtils,
            root.pdfjsWebPDFRenderingQueue);
    }
}(this, function(exports, uiUtils, pdfRenderingQueue) {

    var mozL10n = uiUtils.mozL10n;
    var getOutputScale = uiUtils.getOutputScale;
    var RenderingStates = pdfRenderingQueue.RenderingStates;

    var THUMBNAIL_WIDTH = 98; // px
    var THUMBNAIL_CANVAS_BORDER_WIDTH = 1; // px

    /**
     * @typedef {Object} PDFThumbnailViewOptions
     * @property {HTMLDivElement} container - The viewer element.
     * @property {number} id - The thumbnail's unique ID (normally its number).
     * @property {PageViewport} defaultViewport - The page viewport.
     * @property {IPDFLinkService} linkService - The navigation/linking service.
     * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
     * @property {boolean} disableCanvasToImageConversion - (optional) Don't convert
     *   the canvas thumbnails to images. This prevents `toDataURL` calls,
     *   but increases the overall memory usage. The default value is false.
     */

    /**
     * @class
     * @implements {IRenderableView}
     */
    var PDFThumbnailView = (function PDFThumbnailViewClosure() {
        function getTempCanvas(width, height) {
            var tempCanvas = PDFThumbnailView.tempImageCache;
            if (!tempCanvas) {
                tempCanvas = document.createElement('canvas');
                PDFThumbnailView.tempImageCache = tempCanvas;
            }
            tempCanvas.width = width;
            tempCanvas.height = height;

            // Since this is a temporary canvas, we need to fill the canvas with a white
            // background ourselves. `_getPageDrawContext` uses CSS rules for this.
//#if MOZCENTRAL || FIREFOX || GENERIC
            tempCanvas.mozOpaque = true;
//#endif
            var ctx = tempCanvas.getContext('2d', {alpha : false});
            ctx.save();
            ctx.fillStyle = 'rgb(255, 255, 255)';
            ctx.fillRect(0, 0, width, height);
            ctx.restore();
            return tempCanvas;
        }

        /**
         * @constructs PDFThumbnailView
         * @param {PDFThumbnailViewOptions} options
         */
        function PDFThumbnailView(options) {
            var container = options.container;
            var id = options.id;
            var defaultViewport = options.defaultViewport;
            var linkService = options.linkService;
            var renderingQueue = options.renderingQueue;
            var disableCanvasToImageConversion =
                    options.disableCanvasToImageConversion || false;

            this.id = id;
            this.renderingId = 'thumbnail' + id;

            this.pdfPage = null;
            this.rotation = 0;
            this.viewport = defaultViewport;
            this.pdfPageRotate = defaultViewport.rotation;

            this.linkService = linkService;
            this.renderingQueue = renderingQueue;

            this.resume = null;
            this.renderingState = RenderingStates.INITIAL;
            this.disableCanvasToImageConversion = disableCanvasToImageConversion;

            this.pageWidth = this.viewport.width;
            this.pageHeight = this.viewport.height;
            this.pageRatio = this.pageWidth / this.pageHeight;

            this.canvasWidth = THUMBNAIL_WIDTH;
            this.canvasHeight = (this.canvasWidth / this.pageRatio) | 0;
            this.scale = this.canvasWidth / this.pageWidth;

            var anchor = document.createElement('a');
            anchor.href = linkService.getAnchorUrl('#page=' + id);
            anchor.title = mozL10n.get('thumb_page_title', {page : id}, 'Page {{page}}');
            anchor.onclick = function stopNavigation() {
                linkService.page = id;
                return false;
            };

            var div = document.createElement('div');
            div.id = 'thumbnailContainer' + id;
            div.className = 'thumbnail';
            this.div = div;

            if (id === 1) {
                // Highlight the thumbnail of the first page when no page number is
                // specified (or exists in cache) when the document is loaded.
                div.classList.add('selected');
            }

            var ring = document.createElement('div');
            ring.className = 'thumbnailSelectionRing';
            var borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH;
            ring.style.width = this.canvasWidth + borderAdjustment + 'px';
            ring.style.height = this.canvasHeight + borderAdjustment + 'px';
            this.ring = ring;

            div.appendChild(ring);
            anchor.appendChild(div);
            container.appendChild(anchor);
        }

        PDFThumbnailView.prototype = {
            setPdfPage : function PDFThumbnailView_setPdfPage(pdfPage) {
                this.pdfPage = pdfPage;
                this.pdfPageRotate = pdfPage.rotate;
                var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
                this.viewport = pdfPage.getViewport(1, totalRotation);
                this.reset();
            },

            reset : function PDFThumbnailView_reset() {
                if (this.renderTask) {
                    this.renderTask.cancel();
                }
                this.resume = null;
                this.renderingState = RenderingStates.INITIAL;

                this.pageWidth = this.viewport.width;
                this.pageHeight = this.viewport.height;
                this.pageRatio = this.pageWidth / this.pageHeight;

                this.canvasHeight = (this.canvasWidth / this.pageRatio) | 0;
                this.scale = (this.canvasWidth / this.pageWidth);

                this.div.removeAttribute('data-loaded');
                var ring = this.ring;
                var childNodes = ring.childNodes;
                for (var i = childNodes.length - 1; i >= 0; i--) {
                    ring.removeChild(childNodes[i]);
                }
                var borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH;
                ring.style.width = this.canvasWidth + borderAdjustment + 'px';
                ring.style.height = this.canvasHeight + borderAdjustment + 'px';

                if (this.canvas) {
                    // Zeroing the width and height causes Firefox to release graphics
                    // pdfprotects immediately, which can greatly reduce memory consumption.
                    this.canvas.width = 0;
                    this.canvas.height = 0;
                    delete this.canvas;
                }
                if (this.image) {
                    this.image.removeAttribute('src');
                    delete this.image;
                }
            },

            update : function PDFThumbnailView_update(rotation) {
                if (typeof rotation !== 'undefined') {
                    this.rotation = rotation;
                }
                var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
                this.viewport = this.viewport.clone({
                    scale    : 1,
                    rotation : totalRotation
                });
                this.reset();
            },

            /**
             * @private
             */
            _getPageDrawContext :
                function PDFThumbnailView_getPageDrawContext(noCtxScale) {
                    var canvas = document.createElement('canvas');
                    // Keep the no-thumbnail outline visible, i.e. `data-loaded === false`,
                    // until rendering/image conversion is complete, to avoid display issues.
                    this.canvas = canvas;

//#if MOZCENTRAL || FIREFOX || GENERIC
                    canvas.mozOpaque = true;
//#endif
                    var ctx = canvas.getContext('2d', {alpha : false});
                    var outputScale = getOutputScale(ctx);

                    canvas.width = (this.canvasWidth * outputScale.sx) | 0;
                    canvas.height = (this.canvasHeight * outputScale.sy) | 0;
                    canvas.style.width = this.canvasWidth + 'px';
                    canvas.style.height = this.canvasHeight + 'px';

                    if (!noCtxScale && outputScale.scaled) {
                        ctx.scale(outputScale.sx, outputScale.sy);
                    }
                    return ctx;
                },

            /**
             * @private
             */
            _convertCanvasToImage : function PDFThumbnailView_convertCanvasToImage() {
                if (!this.canvas) {
                    return;
                }
                if (this.renderingState !== RenderingStates.FINISHED) {
                    return;
                }
                var id = this.renderingId;
                var className = 'thumbnailImage';
                var ariaLabel = mozL10n.get('thumb_page_title', {page : this.id},
                    'Thumbnail of Page {{page}}');

                if (this.disableCanvasToImageConversion) {
                    this.canvas.id = id;
                    this.canvas.className = className;
                    this.canvas.setAttribute('aria-label', ariaLabel);

                    this.div.setAttribute('data-loaded', true);
                    this.ring.appendChild(this.canvas);
                    return;
                }
                var image = document.createElement('img');
                image.id = id;
                image.className = className;
                image.setAttribute('aria-label', ariaLabel);

                image.style.width = this.canvasWidth + 'px';
                image.style.height = this.canvasHeight + 'px';

                image.src = this.canvas.toDataURL();
                this.image = image;

                this.div.setAttribute('data-loaded', true);
                this.ring.appendChild(image);

                // Zeroing the width and height causes Firefox to release graphics
                // pdfprotects immediately, which can greatly reduce memory consumption.
                this.canvas.width = 0;
                this.canvas.height = 0;
                delete this.canvas;
            },

            draw : function PDFThumbnailView_draw() {
                if (this.renderingState !== RenderingStates.INITIAL) {
                    console.error('Must be in new state before drawing');
                    return Promise.resolve(undefined);
                }

                this.renderingState = RenderingStates.RUNNING;

                var resolveRenderPromise, rejectRenderPromise;
                var promise = new Promise(function(resolve, reject) {
                    resolveRenderPromise = resolve;
                    rejectRenderPromise = reject;
                });

                var self = this;

                function thumbnailDrawCallback(error) {
                    // The renderTask may have been replaced by a new one, so only remove
                    // the reference to the renderTask if it matches the one that is
                    // triggering this callback.
                    if (renderTask === self.renderTask) {
                        self.renderTask = null;
                    }
                    if (error === 'cancelled') {
                        rejectRenderPromise(error);
                        return;
                    }

                    self.renderingState = RenderingStates.FINISHED;
                    self._convertCanvasToImage();

                    if (!error) {
                        resolveRenderPromise(undefined);
                    } else {
                        rejectRenderPromise(error);
                    }
                }

                var ctx = this._getPageDrawContext();
                var drawViewport = this.viewport.clone({scale : this.scale});
                var renderContinueCallback = function renderContinueCallback(cont) {
                    if (!self.renderingQueue.isHighestPriority(self)) {
                        self.renderingState = RenderingStates.PAUSED;
                        self.resume = function resumeCallback() {
                            self.renderingState = RenderingStates.RUNNING;
                            cont();
                        };
                        return;
                    }
                    cont();
                };

                var renderContext = {
                    canvasContext : ctx,
                    viewport      : drawViewport
                };
                var renderTask = this.renderTask = this.pdfPage.render(renderContext);
                renderTask.onContinue = renderContinueCallback;

                renderTask.promise.then(
                    function pdfPageRenderCallback() {
                        thumbnailDrawCallback(null);
                    },
                    function pdfPageRenderError(error) {
                        thumbnailDrawCallback(error);
                    }
                );
                return promise;
            },

            setImage : function PDFThumbnailView_setImage(pageView) {
                if (this.renderingState !== RenderingStates.INITIAL) {
                    return;
                }
                var img = pageView.canvas;
                if (!img) {
                    return;
                }
                if (!this.pdfPage) {
                    this.setPdfPage(pageView.pdfPage);
                }

                this.renderingState = RenderingStates.FINISHED;

                var ctx = this._getPageDrawContext(true);
                var canvas = ctx.canvas;

                if (img.width <= 2 * canvas.width) {
                    ctx.drawImage(img, 0, 0, img.width, img.height,
                        0, 0, canvas.width, canvas.height);
                    this._convertCanvasToImage();
                    return;
                }
                // drawImage does an awful job of rescaling the image, doing it gradually.
                var MAX_NUM_SCALING_STEPS = 3;
                var reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS;
                var reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS;
                var reducedImage = getTempCanvas(reducedWidth, reducedHeight);
                var reducedImageCtx = reducedImage.getContext('2d');

                while (reducedWidth > img.width || reducedHeight > img.height) {
                    reducedWidth >>= 1;
                    reducedHeight >>= 1;
                }
                reducedImageCtx.drawImage(img, 0, 0, img.width, img.height,
                    0, 0, reducedWidth, reducedHeight);
                while (reducedWidth > 2 * canvas.width) {
                    reducedImageCtx.drawImage(reducedImage,
                        0, 0, reducedWidth, reducedHeight,
                        0, 0, reducedWidth >> 1, reducedHeight >> 1);
                    reducedWidth >>= 1;
                    reducedHeight >>= 1;
                }
                ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight,
                    0, 0, canvas.width, canvas.height);
                this._convertCanvasToImage();
            }
        };

        return PDFThumbnailView;
    })();

    PDFThumbnailView.tempImageCache = null;

    exports.PDFThumbnailView = PDFThumbnailView;
}));
