/* Copyright 2015 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/display/text_layer', ['exports', 'pdfjs/shared/util',
            'pdfjs/display/dom_utils'], factory);
    } else if (typeof exports !== 'undefined') {
        factory(exports, require('../shared/util.js'), require('./dom_utils.js'));
    } else {
        factory((root.pdfjsDisplayTextLayer = {}), root.pdfjsSharedUtil,
            root.pdfjsDisplayDOMUtils);
    }
}(this, function(exports, sharedUtil, displayDOMUtils) {

    var Util = sharedUtil.Util;
    var createPromiseCapability = sharedUtil.createPromiseCapability;
    var CustomStyle = displayDOMUtils.CustomStyle;
    var getDefaultSetting = displayDOMUtils.getDefaultSetting;

    /**
     * Text layer render parameters.
     *
     * @typedef {Object} TextLayerRenderParameters
     * @property {TextContent} textContent - Text content to render (the object is
     *   returned by the page's getTextContent() method).
     * @property {HTMLElement} container - HTML element that will contain text runs.
     * @property {PageViewport} viewport - The target viewport to properly
     *   layout the text runs.
     * @property {Array} textDivs - (optional) HTML elements that are correspond
     *   the text items of the textContent input. This is output and shall be
     *   initially be set to empty array.
     * @property {number} timeout - (optional) Delay in milliseconds before
     *   rendering of the text  runs occurs.
     */
    var renderTextLayer = (function renderTextLayerClosure() {
        var MAX_TEXT_DIVS_TO_RENDER = 100000;

        var NonWhitespaceRegexp = /\S/;

        function isAllWhitespace(str) {
            return !NonWhitespaceRegexp.test(str);
        }

        function appendText(textDivs, viewport, geom, styles) {
            var style = styles[geom.fontName];
            var textDiv = document.createElement('div');
            textDivs.push(textDiv);
            if (isAllWhitespace(geom.str)) {
                textDiv.dataset.isWhitespace = true;
                return;
            }
            var tx = Util.transform(viewport.transform, geom.transform);
            var angle = Math.atan2(tx[1], tx[0]);
            if (style.vertical) {
                angle += Math.PI / 2;
            }
            var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]));
            var fontAscent = fontHeight;
            if (style.ascent) {
                fontAscent = style.ascent * fontAscent;
            } else if (style.descent) {
                fontAscent = (1 + style.descent) * fontAscent;
            }

            var left;
            var top;
            if (angle === 0) {
                left = tx[4];
                top = tx[5] - fontAscent;
            } else {
                left = tx[4] + (fontAscent * Math.sin(angle));
                top = tx[5] - (fontAscent * Math.cos(angle));
            }
            textDiv.style.left = left + 'px';
            textDiv.style.top = top + 'px';
            textDiv.style.fontSize = fontHeight + 'px';
            textDiv.style.fontFamily = style.fontFamily;

            textDiv.textContent = geom.str;
            // |fontName| is only used by the Font Inspector. This test will succeed
            // when e.g. the Font Inspector is off but the Stepper is on, but it's
            // not worth the effort to do a more accurate test.
            if (getDefaultSetting('pdfBug')) {
                textDiv.dataset.fontName = geom.fontName;
            }
            // Storing into dataset will convert number into string.
            if (angle !== 0) {
                textDiv.dataset.angle = angle * (180 / Math.PI);
            }
            // We don't bother scaling single-char text divs, because it has very
            // little effect on text highlighting. This makes scrolling on docs with
            // lots of such divs a lot faster.
            if (geom.str.length > 1) {
                if (style.vertical) {
                    textDiv.dataset.canvasWidth = geom.height * viewport.scale;
                } else {
                    textDiv.dataset.canvasWidth = geom.width * viewport.scale;
                }
            }
        }

        function render(task) {
            if (task._canceled) {
                return;
            }
            var textLayerFrag = task._container;
            var textDivs = task._textDivs;
            var capability = task._capability;
            var textDivsLength = textDivs.length;

            // No point in rendering many divs as it would make the browser
            // unusable even after the divs are rendered.
            if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
                capability.resolve();
                return;
            }

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

            var lastFontSize;
            var lastFontFamily;
            for (var i = 0; i < textDivsLength; i++) {
                var textDiv = textDivs[i];
                if (textDiv.dataset.isWhitespace !== undefined) {
                    continue;
                }

                var fontSize = textDiv.style.fontSize;
                var fontFamily = textDiv.style.fontFamily;

                // Only build font string and set to context if different from last.
                if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
                    ctx.font = fontSize + ' ' + fontFamily;
                    lastFontSize = fontSize;
                    lastFontFamily = fontFamily;
                }

                var width = ctx.measureText(textDiv.textContent).width;
                textLayerFrag.appendChild(textDiv);
                var transform;
                if (textDiv.dataset.canvasWidth !== undefined && width > 0) {
                    // Dataset values come of type string.
                    var textScale = textDiv.dataset.canvasWidth / width;
                    transform = 'scaleX(' + textScale + ')';
                } else {
                    transform = '';
                }
                var rotation = textDiv.dataset.angle;
                if (rotation) {
                    transform = 'rotate(' + rotation + 'deg) ' + transform;
                }
                if (transform) {
                    CustomStyle.setProp('transform', textDiv, transform);
                }
            }
            capability.resolve();
        }

        /**
         * Text layer rendering task.
         *
         * @param {TextContent} textContent
         * @param {HTMLElement} container
         * @param {PageViewport} viewport
         * @param {Array} textDivs
         * @private
         */
        function TextLayerRenderTask(textContent, container, viewport, textDivs) {
            this._textContent = textContent;
            this._container = container;
            this._viewport = viewport;
            textDivs = textDivs || [];
            this._textDivs = textDivs;
            this._canceled = false;
            this._capability = createPromiseCapability();
            this._renderTimer = null;
        }

        TextLayerRenderTask.prototype = {
            get promise() {
                return this._capability.promise;
            },

            cancel : function TextLayer_cancel() {
                this._canceled = true;
                if (this._renderTimer !== null) {
                    clearTimeout(this._renderTimer);
                    this._renderTimer = null;
                }
                this._capability.reject('canceled');
            },

            _render : function TextLayer_render(timeout) {
                var textItems = this._textContent.items;
                var styles = this._textContent.styles;
                var textDivs = this._textDivs;
                var viewport = this._viewport;
                for (var i = 0, len = textItems.length; i < len; i++) {
                    appendText(textDivs, viewport, textItems[i], styles);
                }

                if (!timeout) { // Render right away
                    render(this);
                } else { // Schedule
                    var self = this;
                    this._renderTimer = setTimeout(function() {
                        render(self);
                        self._renderTimer = null;
                    }, timeout);
                }
            }
        };

        /**
         * Starts rendering of the text layer.
         *
         * @param {TextLayerRenderParameters} renderParameters
         * @returns {TextLayerRenderTask}
         */
        function renderTextLayer(renderParameters) {
            var task = new TextLayerRenderTask(renderParameters.textContent,
                renderParameters.container,
                renderParameters.viewport,
                renderParameters.textDivs);
            task._render(renderParameters.timeout);
            return task;
        }

        return renderTextLayer;
    })();

    exports.renderTextLayer = renderTextLayer;
}));
