/* global MODE, Descartes */

var _ = require('lodash');
var qrcode = require('qrcode');

var Utils = Descartes.Utils;
var EventManager = Descartes.Utils.EventManager;
var Action = Descartes.Action;
var MessagesConstants = require('../Messages');

var InkMapSetupInPlace = require('../UI/' + MODE + '/InkMapSetupInPlace');
var PrintMapPreview = require('../UI/PrintMapPreview');

/**
 * Class: Descartes.Action.InkMapParamsManager
 * Classe permettant d'exporter la carte courante sous forme de fichier PDF, après saisie des paramètres de mise en page
 *
 * Le modèle MVC manipulé est un objet JSON de la forme :
 * (start code)
 * {
 * 		marginTop: <marge haut>,
 * 		marginBottom: <marge bas>,
 * 		marginLeft: <marge gauche>,
 * 		marginRight: <marge droit>,
 * 		format: { code, width, height},
 * 		enabledFormats: {
 * 							code: <code format>,
 * 							width: <largeur papier>,
 * 							height: <hauteur papier>
 * 						}[]
 * }
 * (end)
 * Les formats disponibles sont déterminés en fonction de la taille de la carte.
 *
 * Hérite de:
 * - <Descartes.Action>
 *
 * Evénements déclenches:
 * paramsChanged - Les paramètres sont saisis et valides.
 *
 * Ecouteurs mis en place:
 *  - l'événement 'choosed' de la classe définissant la vue MVC associée (<Descartes.UI.InkMapSetupInPlace> par défaut) déclenche la méthode <doExport>.
 *  - l'événement 'resize' de la classe ol.Map déclenche la méthode <redrawRendererWithParams>.
 */
var Class = Utils.Class(Action, {
    /**
     * Propriete: defaultParams
     * {Object} Objet JSON stockant les paramètres de mise en page par défaut.
     *
     * :
     * previewToggleable - {boolean} Permettre l'affichage ou non de l'aperçu.
     * marginTop - Dimension en mm de la marge haut (10 par défaut).
     * marginBottom - Dimension en mm de la marge bas (10 par défaut).
     * marginLeft - Dimension en mm de la marge gauche (10 par défaut).
     * marginRight - Dimension en mm de la marge droit (10 par défaut).
     * dpi - {number} Résolution de la carte imprimée en dpi (300 par défaut).
     * projection - {string} Code de la projection de la carte imprimée.
     * projectionEditable - {boolean} Permettre la modification de la projection
     * formatEditable - {boolean} Permettre la modification du format
     * defaultFormatCode - {string} Code du format de papier par défaut (par ex 'A4L')
     * dateEnabled - {boolean} Imprimer la date
     * dateToggleable - {boolean} Permettre d'inclure ou non la date
     * descriptionEditable - {boolean} Permettre la modification de la description
     * descriptionEnabled - {boolean} Imprimer la description
     * descriptionToggleable - {boolean} Permettre d'inclure ou non la description
     * dpiEditable - {boolean} Permettre la modification du DPI
     * legendEnabled - {boolean} Imprimer la légende
     * legendToggleable - {boolean} Permettre d'inclure ou non la légende
     * marginEditable - {boolean} Permettre la modification des marges
     * northArrowEnabled - {boolean} Imprimer la rose des vents
     * northArrowToggleable - {boolean} Permettre d'inclure ou non la rose des vents
     * projectionEnabled - {boolean} Imprimer la projection
     * projectionToggleable - {boolean} Permettre d'inclure ou non la projection
     * qrCodeEnabled - {boolean} Imprimer le QR code
     * qrCodeToggleable - {boolean} Permettre d'inclure ou non le QR code
     * scaleBarEnabled - {boolean} Imprimer l'échelle graphique
     * scaleBarToggleable - {boolean} Permettre d'inclure ou non l'échelle graphique
     * titleEditable - {boolean} Permettre la modification du titre
     * titleEnabled - {boolean} Imprimer le titre
     * titleToggleable - {boolean} Permettre d'inclure ou non le titre
     */
    defaultParams: {
        previewToggleable: true,
        marginTop: 10,
        marginBottom: 10,
        marginLeft: 10,
        marginRight: 10,
        marginEditable: true,
        dpi: 90,
        dpiEditable: true,
        displayProjections: [],
        selectedDisplayProjectionIndex: 0,
        projectionEditable: true,
        formatEditable: true,
        defaultFormatCode: "A3L",
        dateEnabled: true,
        dateToggleable: true,
        descriptionEditable: true,
        descriptionEnabled: true,
        descriptionToggleable: true,
        legendEnabled: true,
        legendToggleable: true,
        northArrowEnabled: true,
        northArrowToggleable: true,
        projectionEnabled: true,
        projectionToggleable: true,
        qrCodeEnabled: true,
        qrCodeToggleable: true,
        scaleBarEnabled: true,
        scaleBarToggleable: true,
        titleEditable: true,
        titleEnabled: true,
        titleToggleable: true
    },
    /**
     * Propriete: defaultInfos
     * {Object} Objet JSON stockant les informations complémentaires par défaut à insérer dans le fichier PDF.
     *
     * :
     * title - {string} Titre de la carte d'impression.
     * description - {string} Description de la carte d'impression.
     * producer - {string}
     * attributions - {string}
     * additionalInfo - {string}
     * logoUrl - {string}
     */
    defaultInfos: {
        title: "",
        description: "",
        producer: "",
        attributions: "",
        additionalInfo: "",
        logoUrl: ""
    },

    /**
     * Propriete: mapContent
     * {<Descartes.MapContent>} Contenu de la carte (groupes et couches).
     */
    mapContent: null,

    EVENT_TYPES: ['paramsChanged'],

    /**
     * Constructeur: Descartes.Action.InkMapParamsManager
     * Constructeur d'instances
     *
     * Paramètres:
     * div - {DOMElement|String} Elément DOM de la page accueillant l'interface associée ou identifiant de cet élement.
     * olMap - {ol.Map} Carte OpenLayers sur laquelle intervient le contrôleur.
     * options - {Object} Objet optionnel contenant les propriétés à renseigner dans l'instance
     *
     * Options de construction propres à la classe:
     * params -  {Object} Objet JSON stockant les paramètres de mise en page initiaux (étendus par <defaultParams>), structuré comme le modèle associé.
     * infos - {Object} Objet JSON stockant les informations complémentaires à insérer dans le fichier PDF.
     */
    initialize: function (div, olMap, options) {
        this.model = {};
        Action.prototype.initialize.apply(this, arguments);
        _.extend(this.model, this.defaultParams);
        _.extend(this.model, this.defaultInfos);

        if (!_.isNil(options) && !_.isNil(options.params)) {
            _.extend(this.model, options.params);
            delete options.params;
        }

        if (!_.isNil(options) && !_.isNil(options.infos)) {
            _.extend(this.model, options.infos);
            delete options.infos;
        }
        this._configureParams();

        this._setDefaultPaperSize();

        this.model.projection = olMap.getView().getProjection().getCode();
        if (!(this.model.projectionEditable)) {
            this.model.displayProjections = [];
        }

        if (this.model.displayProjections.length === 0) {
            this.model.displayProjections.push(olMap.getView().getProjection().getCode());
            this.model.selectedDisplayProjectionIndex = 0;
        } else {
            this.model.projection = this.model.displayProjections[this.model.selectedDisplayProjectionIndex];
        }

        this.preview = new PrintMapPreview(olMap);

        // hide preview initially
        this.preview.togglePreviewVisibility(false);

        // toggle blocks according to initial state
        this.preview.toggleBlockVisibility('title', this.model.titleEnabled);
        this.preview.toggleBlockVisibility('date', this.model.dateEnabled);
        this.preview.toggleBlockVisibility('description', this.model.descriptionEnabled);
        this.preview.toggleBlockVisibility('qrCode', this.model.qrCodeEnabled);
        this.preview.toggleBlockVisibility('projection', this.model.projectionEnabled);
        this.preview.toggleBlockVisibility('legend', this.model.legendEnabled);

        if (_.isNil(this.renderer)) {
            this.renderer = new InkMapSetupInPlace(div, this.model, options);
        }
        this.events = new EventManager();

        if (this.model.enabledFormats.length !== 0) {
            this.renderer.events.register('startPrint', this, this.doExport);
            this.renderer.events.register('togglePreview', this, (event) => {
                this.togglePreviewVisibility(event.data[0]);
            });
            this.renderer.events.register('toggleBlock', this, (event) => {
                this.toggleBlockVisibility(event.data[0], event.data[1]);
            });
            this.renderer.events.register('changePaperSize', this, () =>
                this.changePaperSize(this.model.format, this.model.marginLeft, this.model.marginRight, this.model.marginTop, this.model.marginBottom));
            this.renderer.draw();
            this.changePaperSize(this.model.format, this.model.marginLeft, this.model.marginRight, this.model.marginTop, this.model.marginBottom);

        } else {
            alert(MessagesConstants.Descartes_EXPORT_ERROR_FORMAT_PAPER);
        }
    },

    /**
     * Methode: setMapContent
     * Associe le contenu de la carte au gestionnaire.
     *
     * Paramètres:
     * mapContent - {<Descartes.MapContent>} Contenu de la carte (groupes et couches).
     */
    setMapContent: function (mapContent) {
        this.mapContent = mapContent;
    },

    /**
     * Private
     */
    _configureParams: function () {
        if (!this.model.enabledFormats) {
            this.model.enabledFormats = [];
            for (var i = 0, len = Descartes.Descartes_Papers.length; i < len; i++) {
                var format = Descartes.Descartes_Papers[i];
                this._checkPaperSizes(format.name, format.width, format.length, 'P');
                this._checkPaperSizes(format.name, format.length, format.width, 'L');
            }
            return true;
        }
        return false;
    },

    /**
     * Private
     */
    _checkPaperSizes: function (paperName, width, height, orientation) {
        this.model.enabledFormats.push({
            code: paperName + orientation,
            width: width,
            height: height
        });
        if (this.model.format === undefined) {
            this.model.format = this.model.enabledFormats[0];
        }
    },

    /**
     * Private
     */
    _setDefaultPaperSize: function () {
        if (!this.model.defaultFormatCode) return;

        this.model.format = this.model.enabledFormats.filter(format => format.code === this.model.defaultFormatCode)[0];
    },

    /**
     * Methode: changePaperSize
     * Met à jour l'aperçu sur la carte avec le nouveau format
     *
     * Paramètres :
     * - {Object} format Format contenant les propriétés suivantes : `code` (par exemple 'A3P'), `width`, `height`
     * - {number} marginLeft
     * - {number} marginRight
     * - {number} marginTop
     * - {number} marginBottom
     */
    changePaperSize: function (format, marginLeft, marginRight, marginTop, marginBottom) {
        this.preview.setPaperDimensions([format.width, format.height], [marginLeft, marginRight, marginTop, marginBottom]);
    },

    /**
     * Permet de cacher/afficher l'aperçu d'impression
     *
     * Paramètres:
     * visible - {boolean} Visiblité de l'aperçu
     */
    togglePreviewVisibility: function (visible) {
        this.preview.togglePreviewVisibility(visible);
    },

    /**
     * Permet de cacher/afficher un bloc dans l'aperçu
     *
     * Paramètres:
     * blockId - {string} Bloc à cacher/afficher
     * visible - {boolean} Visiblité du bloc
     */
     toggleBlockVisibility: function (blockId, visible) {
        this.preview.toggleBlockVisibility(blockId, visible);
    },

    /**
     * Methode: doExport
     * Lance la génération du PDF grâce au service d'exportation PDF, et déclenche l'événement 'paramsChanged'.
     */
    doExport: async function () {
        if (!this.model.previewToggleable) {
            this.preview.togglePreviewVisibility(true);
        }

        var format = this.model.format;
        var paperSize = [format.width, format.height];
        var printBlocks = this.preview.getBlocksInfo(paperSize);
        var printRatio = this.preview.getScaleRatio(paperSize);

        var orderedLayers = this.mapContent.getVisibleLayers().sort(function (a, b) {
            return b.displayOrder - a.displayOrder;
        });
        var mapSpec = Utils.writeInkmapPartialSpec(
            this.olMap,
            this.preview.getMapCenter(),
            printBlocks.map.size,
            printRatio,
            this.model.scaleBarEnabled,
            this.model.northArrowEnabled,
            this.model.dpi,
            this.model.projection
        );
        mapSpec.layers = await Utils.writeInkmapLayers(orderedLayers, this.olMap, this.model.projection);

        if (!this.model.previewToggleable) {
            this.preview.togglePreviewVisibility(false);
        }

        var pdfDoc = Utils.createPdfDoc(paperSize);

        // TODO: use actual permalink?
        var qrCodeDataUrl = '';
        qrcode.toDataURL(window.location.toString(), (err, url) => {
            if (err) {
                console.error('QR Code génération failed', err);
                return;
            }
            qrCodeDataUrl = url;
        });

        // this triggers the PDF generation and save it
        Utils.exportPDF(pdfDoc, mapSpec, async (mapUrl) => {
            Utils.addImageToPdfDoc(pdfDoc, printBlocks.map.position, printBlocks.map.size, mapUrl);

            if (printBlocks.qrCode.enabled) {
                Utils.addImageToPdfDoc(pdfDoc, printBlocks.qrCode.position, printBlocks.qrCode.size, qrCodeDataUrl);
            }

            if (printBlocks.title.enabled) {
                Utils.addBlockToPdfDoc(pdfDoc, printBlocks.title.position, printBlocks.title.size,
                    this.model.title,
                    'center', 'bold', 20);
            }

            if (printBlocks.date.enabled) {
                Utils.addBlockToPdfDoc(pdfDoc, printBlocks.date.position, printBlocks.date.size,
                    'Date d\'impression : ' + new Date().toLocaleString(),
                    'center');
            }

            if (printBlocks.legend.enabled) {
                await Utils.addMapLegendToPdfDoc(pdfDoc, printBlocks.legend.position, printBlocks.legend.size,
                    orderedLayers);
            }

            if (printBlocks.projection.enabled) {
                Utils.addBlockToPdfDoc(pdfDoc, printBlocks.projection.position, printBlocks.projection.size,
                    'Projection : ' + Utils.getProjectionName(mapSpec.projection), 'center');
            }

            if (printBlocks.description.enabled) {
                Utils.addBlockToPdfDoc(pdfDoc, printBlocks.description.position, printBlocks.description.size,
                    this.model.description,
                    'left', null, 14);
            }

            if (printBlocks.footer.enabled) {
                await Utils.addFooterToPdfDoc(pdfDoc, printBlocks.footer.position, printBlocks.footer.size,
                    this.model.logoUrl, this.model.producer, this.model.attributions, this.model.additionalInfo);
            }
        });
    },

    CLASS_NAME: 'Descartes.Action.InkMapParamsManager'
});
module.exports = Class;
