/* 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-web/pdf_link_service', ['exports', 'pdfjs-web/ui_utils',
            'pdfjs-web/dom_events'], factory);
    } else if (typeof exports !== 'undefined') {
        factory(exports, require('./ui_utils.js'), require('./dom_events.js'));
    } else {
        factory((root.pdfjsWebPDFLinkService = {}), root.pdfjsWebUIUtils,
            root.pdfjsWebDOMEvents);
    }
}(this, function(exports, uiUtils, domEvents) {

    var parseQueryString = uiUtils.parseQueryString;

    /**
     * @typedef {Object} PDFLinkServiceOptions
     * @property {EventBus} eventBus - The application event bus.
     */

    /**
     * Performs navigation functions inside PDF, such as opening specified page,
     * or destination.
     * @class
     * @implements {IPDFLinkService}
     */
    var PDFLinkService = (function() {
        /**
         * @constructs PDFLinkService
         * @param {PDFLinkServiceOptions} options
         */
        function PDFLinkService(options) {
            options = options || {};
            this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
            this.baseUrl = null;
            this.pdfDocument = null;
            this.pdfViewer = null;
            this.pdfHistory = null;

            this._pagesRefCache = null;
        }

        PDFLinkService.prototype = {
            setDocument : function PDFLinkService_setDocument(pdfDocument, baseUrl) {
                this.baseUrl = baseUrl;
                this.pdfDocument = pdfDocument;
                this._pagesRefCache = Object.create(null);
            },

            setViewer : function PDFLinkService_setViewer(pdfViewer) {
                this.pdfViewer = pdfViewer;
            },

            setHistory : function PDFLinkService_setHistory(pdfHistory) {
                this.pdfHistory = pdfHistory;
            },

            /**
             * @returns {number}
             */
            get pagesCount() {
                return this.pdfDocument.numPages;
            },

            /**
             * @returns {number}
             */
            get page() {
                return this.pdfViewer.currentPageNumber;
            },

            /**
             * @param {number} value
             */
            set page(value) {
                this.pdfViewer.currentPageNumber = value;
            },

            /**
             * @param dest - The PDF destination object.
             */
            navigateTo : function PDFLinkService_navigateTo(dest) {
                var destString = '';
                var self = this;

                var goToDestination = function(destRef) {
                    // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
                    var pageNumber = destRef instanceof Object ?
                        self._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
                        (destRef + 1);
                    if (pageNumber) {
                        if (pageNumber > self.pagesCount) {
                            pageNumber = self.pagesCount;
                        }
                        self.pdfViewer.scrollPageIntoView(pageNumber, dest);

                        if (self.pdfHistory) {
                            // Update the browsing history.
                            self.pdfHistory.push({
                                dest : dest,
                                hash : destString,
                                page : pageNumber
                            });
                        }
                    } else {
                        self.pdfDocument.getPageIndex(destRef).then(function(pageIndex) {
                            var pageNum = pageIndex + 1;
                            var cacheKey = destRef.num + ' ' + destRef.gen + ' R';
                            self._pagesRefCache[cacheKey] = pageNum;
                            goToDestination(destRef);
                        });
                    }
                };

                var destinationPromise;
                if (typeof dest === 'string') {
                    destString = dest;
                    destinationPromise = this.pdfDocument.getDestination(dest);
                } else {
                    destinationPromise = Promise.resolve(dest);
                }
                destinationPromise.then(function(destination) {
                    dest = destination;
                    if (!(destination instanceof Array)) {
                        return; // invalid destination
                    }
                    goToDestination(destination[0]);
                });
            },

            /**
             * @param dest - The PDF destination object.
             * @returns {string} The hyperlink to the PDF object.
             */
            getDestinationHash : function PDFLinkService_getDestinationHash(dest) {
                if (typeof dest === 'string') {
                    return this.getAnchorUrl('#' + escape(dest));
                }
                if (dest instanceof Array) {
                    var destRef = dest[0]; // see navigateTo method for dest format
                    var pageNumber = destRef instanceof Object ?
                        this._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
                        (destRef + 1);
                    if (pageNumber) {
                        var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber);
                        var destKind = dest[1];
                        if (typeof destKind === 'object' && 'name' in destKind &&
                            destKind.name === 'XYZ') {
                            var scale = (dest[4] || this.pdfViewer.currentScaleValue);
                            var scaleNumber = parseFloat(scale);
                            if (scaleNumber) {
                                scale = scaleNumber * 100;
                            }
                            pdfOpenParams += '&zoom=' + scale;
                            if (dest[2] || dest[3]) {
                                pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
                            }
                        }
                        return pdfOpenParams;
                    }
                }
                return this.getAnchorUrl('');
            },

            /**
             * Prefix the full url on anchor links to make sure that links are resolved
             * relative to the current URL instead of the one defined in <base href>.
             * @param {String} anchor The anchor hash, including the #.
             * @returns {string} The hyperlink to the PDF object.
             */
            getAnchorUrl : function PDFLinkService_getAnchorUrl(anchor) {
                return (this.baseUrl || '') + anchor;
            },

            /**
             * @param {string} hash
             */
            setHash : function PDFLinkService_setHash(hash) {
                if (hash.indexOf('=') >= 0) {
                    var params = parseQueryString(hash);
                    if ('search' in params) {
                        this.eventBus.dispatch('findfromurlhash', {
                            source       : this,
                            query        : params['search'].replace(/"/g, ''),
                            phraseSearch : (params['phrase'] === 'true')
                        });
                    }
                    // borrowing syntax from "Parameters for Opening PDF Files"
                    if ('nameddest' in params) {
                        if (this.pdfHistory) {
                            this.pdfHistory.updateNextHashParam(params.nameddest);
                        }
                        this.navigateTo(params.nameddest);
                        return;
                    }
                    var pageNumber, dest;
                    if ('page' in params) {
                        pageNumber = (params.page | 0) || 1;
                    }
                    if ('zoom' in params) {
                        // Build the destination array.
                        var zoomArgs = params.zoom.split(','); // scale,left,top
                        var zoomArg = zoomArgs[0];
                        var zoomArgNumber = parseFloat(zoomArg);

                        if (zoomArg.indexOf('Fit') === -1) {
                            // If the zoomArg is a number, it has to get divided by 100. If it's
                            // a string, it should stay as it is.
                            dest = [null, {name : 'XYZ'},
                                zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
                                zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
                                (zoomArgNumber ? zoomArgNumber / 100 : zoomArg)];
                        } else {
                            if (zoomArg === 'Fit' || zoomArg === 'FitB') {
                                dest = [null, {name : zoomArg}];
                            } else if ((zoomArg === 'FitH' || zoomArg === 'FitBH') ||
                                (zoomArg === 'FitV' || zoomArg === 'FitBV')) {
                                dest = [null, {name : zoomArg},
                                    zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null];
                            } else if (zoomArg === 'FitR') {
                                if (zoomArgs.length !== 5) {
                                    console.error('PDFLinkService_setHash: ' +
                                        'Not enough parameters for \'FitR\'.');
                                } else {
                                    dest = [null, {name : zoomArg},
                                        (zoomArgs[1] | 0), (zoomArgs[2] | 0),
                                        (zoomArgs[3] | 0), (zoomArgs[4] | 0)];
                                }
                            } else {
                                console.error('PDFLinkService_setHash: \'' + zoomArg +
                                    '\' is not a valid zoom value.');
                            }
                        }
                    }
                    if (dest) {
                        this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
                    } else if (pageNumber) {
                        this.page = pageNumber; // simple page
                    }
                    if ('pagemode' in params) {
                        this.eventBus.dispatch('pagemode', {
                            source : this,
                            mode   : params.pagemode
                        });
                    }
                } else if (/^\d+$/.test(hash)) { // page number
                    this.page = hash;
                } else { // named destination
                    if (this.pdfHistory) {
                        this.pdfHistory.updateNextHashParam(unescape(hash));
                    }
                    this.navigateTo(unescape(hash));
                }
            },

            /**
             * @param {string} action
             */
            executeNamedAction : function PDFLinkService_executeNamedAction(action) {
                // See PDF reference, table 8.45 - Named action
                switch (action) {
                    case 'GoBack':
                        if (this.pdfHistory) {
                            this.pdfHistory.back();
                        }
                        break;

                    case 'GoForward':
                        if (this.pdfHistory) {
                            this.pdfHistory.forward();
                        }
                        break;

                    case 'NextPage':
                        this.page++;
                        break;

                    case 'PrevPage':
                        this.page--;
                        break;

                    case 'LastPage':
                        this.page = this.pagesCount;
                        break;

                    case 'FirstPage':
                        this.page = 1;
                        break;

                    default:
                        break; // No action according to spec
                }

                this.eventBus.dispatch('namedaction', {
                    source : this,
                    action : action
                });
            },

            /**
             * @param {number} pageNum - page number.
             * @param {Object} pageRef - reference to the page.
             */
            cachePageRef : function PDFLinkService_cachePageRef(pageNum, pageRef) {
                var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
                this._pagesRefCache[refStr] = pageNum;
            }
        };

        return PDFLinkService;
    })();

    var SimpleLinkService = (function SimpleLinkServiceClosure() {
        function SimpleLinkService() {
        }

        SimpleLinkService.prototype = {
            /**
             * @returns {number}
             */
            get page() {
                return 0;
            },
            /**
             * @param {number} value
             */
            set page(value) {
            },
            /**
             * @param dest - The PDF destination object.
             */
            navigateTo         : function(dest) {
            },
            /**
             * @param dest - The PDF destination object.
             * @returns {string} The hyperlink to the PDF object.
             */
            getDestinationHash : function(dest) {
                return '#';
            },
            /**
             * @param hash - The PDF parameters/hash.
             * @returns {string} The hyperlink to the PDF object.
             */
            getAnchorUrl       : function(hash) {
                return '#';
            },
            /**
             * @param {string} hash
             */
            setHash            : function(hash) {
            },
            /**
             * @param {string} action
             */
            executeNamedAction : function(action) {
            },
            /**
             * @param {number} pageNum - page number.
             * @param {Object} pageRef - reference to the page.
             */
            cachePageRef       : function(pageNum, pageRef) {
            }
        };
        return SimpleLinkService;
    })();

    exports.PDFLinkService = PDFLinkService;
    exports.SimpleLinkService = SimpleLinkService;
}));
