/* global Descartes, ol */
var interact = require('interactjs');
var Utils = Descartes.Utils;
var UI = Descartes.UI;

require('./css/PrintMapPreview.css');

/**
 * Class: Descartes.UI.PrintMapPreview
 * Cette classe permet de faire apparaître en surimpression sur la carte un aperçu de la composition à imprimer,
 * et de modifier celle-ci de manière interactive.
 *
 * Hérite de:
 *  - <Descartes.UI>
 */
var Class = Utils.Class(UI, {
    /**
     * Propriété: container
     * {HTMLElement} Element HTML contenant l'aperçu de la composition
     */
    container: null,

    /**
     * Propriété: olMap
     * {ol.Map} Carte OpenLayers sur laquelle afficher l'aperçu
     */
    olMap: null,

    margins: [0, 0, 0, 0],

    /**
     * Propriété: blocks
     * {Object} Contient tous les éléments HTML correspondant aux blocs de la composition
     */
    blocks: {
        title: null,
        date: null,
        map: null,
        legend: null,
        description: null,
        projection: null,
        qrCode: null,
        footer: null
    },

    /**
     * Constructeur: Descartes.UI.PrintMapPreview
     * Constructeur d'instances
     *
     * Paramètres:
     * olMap - {ol.Map} Carte OpenLayers par-dessus laquelle sera affichée la composition
     */
    initialize: function (olMap) {
        this.olMap = olMap;

        this._createContainer();
        this.blocks.map = this._createPreviewBlock('carte', true);
        this.blocks.title = this._createPreviewBlock('titre');
        this.blocks.date = this._createPreviewBlock('date');
        this.blocks.legend = this._createPreviewBlock('légende');
        this.blocks.description = this._createPreviewBlock('description');
        this.blocks.projection = this._createPreviewBlock('projection');
        this.blocks.qrCode = this._createPreviewBlock('qr code', false, false, true);
        this.blocks.footer = this._createPreviewBlock('informations propriétaires', false, true);

        UI.prototype.initialize.apply(this, [this.container]);
    },

    /**
     * Permet de basculer entre un affichage en mode paysage ou portrait
     * Note : cela remet à zéro la positions des blocs dans la composition
     *
     * Paramètres:
     * dimensions - {[number, number]} Dimensions en mm
     * margins - {[number, number, number, number]} Marges (gauche, droite, haut, bas) en mm
     */
    setPaperDimensions: function (dimensions, margins) {
        var parentSize = this.olMap.getViewport().getBoundingClientRect();
        var aspectRatio = dimensions[0] / dimensions[1];

        // preview width is computed based on size of map viewport minus padding on each border
        var padding = 70;
        var previewWidth = Math.round(
            Math.min(parentSize.width - padding, (parentSize.height - padding) * aspectRatio)
        );
        var previewHeight = Math.round(previewWidth / aspectRatio);

        var scaling = previewWidth / dimensions[0]; // scale from mm to px

        this.container.style.width = previewWidth + 'px';
        this.container.style.height = previewHeight + 'px';

        // Positionnement automatique des blocs

        var gutter = 2 * scaling; // gutter is 2mm
        var marginsPx = margins.map(v => v * scaling);
        var innerHeight = previewHeight - marginsPx[2] - marginsPx[3];
        var innerWidth = previewWidth - marginsPx[0] - marginsPx[1];
        var qrCodeSize = 30 * scaling; // qr code is 30mm

        var mainRowHeight = Math.round(innerHeight * 0.65);
        var secondaryRowHeight = Math.round((innerHeight - mainRowHeight) / 3 - gutter);
        var mainColWidth = Math.round(innerWidth * 0.8);
        var secondaryColWidth = Math.round(innerWidth - mainColWidth - gutter);

        var currentY = marginsPx[2];
        this._positionBlock(this.blocks.title, marginsPx[0], currentY, mainColWidth, secondaryRowHeight);
        this._positionBlock(this.blocks.date, marginsPx[0] + mainColWidth + gutter, currentY, secondaryColWidth, secondaryRowHeight);

        currentY += secondaryRowHeight + gutter;
        this._positionBlock(this.blocks.map, marginsPx[0], currentY, mainColWidth, mainRowHeight);
        this._positionBlock(this.blocks.qrCode, marginsPx[0] + mainColWidth - qrCodeSize, currentY + mainRowHeight - qrCodeSize, qrCodeSize, qrCodeSize);
        this._positionBlock(this.blocks.legend, marginsPx[0] + mainColWidth + gutter, currentY, secondaryColWidth, mainRowHeight);

        currentY += mainRowHeight + gutter;
        this._positionBlock(this.blocks.description, marginsPx[0], currentY, mainColWidth, secondaryRowHeight);
        this._positionBlock(this.blocks.projection, marginsPx[0] + mainColWidth + gutter, currentY, secondaryColWidth, secondaryRowHeight);

        currentY += secondaryRowHeight + gutter;
        this._positionBlock(this.blocks.footer, marginsPx[0], currentY, innerWidth, secondaryRowHeight);

        // save margins as px
        this.margins = marginsPx.slice();
    },

    _createContainer: function () {
        this.container = document.createElement('div');
        this.container.classList.add('DescartesPrintPreviewContainer');

        // make sure the map element has a correct position & overflow
        var parentPos = this.olMap.getTargetElement().style.position;
        if (parentPos !== 'absolute' || parentPos !== 'relative') {
            this.olMap.getTargetElement().style.position = 'relative';
        }
        this.olMap.getTargetElement().style.overflow = 'hidden';

        this.olMap.getTargetElement().appendChild(this.container);
    },

    _interactionRestrictCallback: function () {
        var rect = this.container.getBoundingClientRect();
        return {
            left: rect.left + window.scrollX + this.margins[0],
            top: rect.top + window.scrollY + this.margins[2],
            right: rect.right + window.scrollX - this.margins[1],
            bottom: rect.bottom + window.scrollY - this.margins[3]
        };
    },

    _createPreviewBlock: function (label, seeThrough, fixed, isSquare) {
        var blockEl = document.createElement('div');
        this.container.appendChild(blockEl);

        blockEl.classList.add('DescartesPrintPreviewBlock');
        if (seeThrough) {
            blockEl.classList.add('DescartesPrintPreviewBlockSeeThrough');
        } else {
            blockEl.innerText = label;
        }

        // fixed block: do not add interactions
        if (fixed) {
            blockEl.classList.add('DescartesPrintPreviewBlockFixed');
            return blockEl;
        }

        // add handles (edges and corners)
        var handleRoot = document.createElement('div');
        handleRoot.classList.add('handle-root');
        blockEl.appendChild(handleRoot);
        ['top', 'left', 'bottom', 'right'].forEach((pos, i, arr) => {
            var sideEl = document.createElement('div');
            sideEl.classList.add('handle', 'handle-' + pos);
            handleRoot.appendChild(sideEl);
        });
        ['top', 'left', 'bottom', 'right'].forEach((pos, i, arr) => {
            var cornerEl = document.createElement('div');
            var nextPos = arr[(i + 1) % arr.length];
            cornerEl.classList.add('handle', 'handle-' + pos, 'handle-' + nextPos);
            handleRoot.appendChild(cornerEl);
        });

        var snapping = interact.modifiers.snapSize({
            targets: [
                interact.snappers.grid({ width: 4, height: 4 })
            ]
        });

        interact(blockEl).draggable({
            listeners: {
                move: event => {
                    var target = event.target;
                    var x = parseFloat(target.getAttribute('data-x')) || 0;
                    var y = parseFloat(target.getAttribute('data-y')) || 0;

                    // translate coordinates
                    x += event.dx;
                    y += event.dy;

                    this._positionBlock(target, x, y, event.rect.width, event.rect.height);
                }
            },
            modifiers: [
                interact.modifiers.restrictRect({
                    restriction: this._interactionRestrictCallback.bind(this)
                }),
                snapping
            ]
        });
        interact(blockEl).resizable({
            edges: {
                left: '.handle-left',
                right: '.handle-right',
                top: '.handle-top',
                bottom: '.handle-bottom'
            },
            listeners: {
                move: event => {
                    var target = event.target;
                    var x = parseFloat(target.getAttribute('data-x')) || 0;
                    var y = parseFloat(target.getAttribute('data-y')) || 0;

                    // translate when resizing from top or left edges
                    x += event.deltaRect.left;
                    y += event.deltaRect.top;

                    this._positionBlock(target, x, y, event.rect.width, event.rect.height);
                }
            },
            modifiers: [
                interact.modifiers.restrictEdges({
                    outer: this._interactionRestrictCallback.bind(this)
                }),
                interact.modifiers.restrictSize({
                    min: { width: 20, height: 20 }
                }),
                snapping,
                ...isSquare ? [interact.modifiers.aspectRatio({ ratio: 1 })] : []
            ]
        });

        return blockEl;
    },

    // values are in pixels
    _positionBlock: function (blockEl, x, y, width, height) {
        blockEl.style.left = Math.round(x) + 'px';
        blockEl.style.top = Math.round(y) + 'px';
        blockEl.style.width = Math.round(width) + 'px';
        blockEl.style.height = Math.round(height) + 'px';

        blockEl.setAttribute('data-x', Math.round(x));
        blockEl.setAttribute('data-y', Math.round(y));

        // push to front
        var blocks = this.container.querySelectorAll('.DescartesPrintPreviewBlock');
        var last = blocks.item(blocks.length - 1);
        if (last !== blockEl) {
            this.container.appendChild(blockEl);
        }
    },

    /**
     * Renvoie le rapport entre la taille de l'aperçu d'impression et les dimensions réelles du papier en millimètres
     * Nécessaire pour calculer l'échelle finale de l'image imprimée.
     *
     * Paramètres:
     * paperDimensions - {[number, number]} Dimensions (H, L) du papier
     */
    getScaleRatio: function (paperDimensions) {
        var previewRect = this.container.getBoundingClientRect();
        return previewRect.width / paperDimensions[0];
    },

    /**
     * Renvoie une liste de blocs avec leur position et leur taille dans la composition
     *
     * Paramètres:
     * paperDimensions - {[number, number]} Dimensions (H, L) du papier; les informations des blocs seront
     *   calculées pour correspondre à ces dimensions
     */
    getBlocksInfo: function (paperDimensions) {
        var previewRect = this.container.getBoundingClientRect();
        var scaleRatio = paperDimensions[0] / previewRect.width;

        function convertToPaperDimensions(valueA, valueB) {
            return [
              valueA * scaleRatio,
              valueB * scaleRatio
            ];
        }

        // this will return a dict of all blocks with size and position converted to paper space
        // each block is an object like so:
        // id: { enabled: true, size: [300, 400], position: [20, 100] }
        return Object.keys(this.blocks).reduce(function (prev, blockId) {
            var rect = this.blocks[blockId].getBoundingClientRect();
            prev[blockId] = {
                enabled: this.blocks[blockId].style.display !== 'none',
                size: convertToPaperDimensions(rect.width, rect.height),
                position: convertToPaperDimensions(rect.left - previewRect.left, rect.top - previewRect.top)
            };
            return prev;
        }.bind(this), {});
    },

    /**
     * Renvoie les coordonnées géographiques du centre de la carte [lon, lat]
     */
    getMapCenter: function () {
        var mapRect = this.blocks.map.getBoundingClientRect();
        var olRect = this.olMap.getViewport().getBoundingClientRect();
        var mapCenterPx = [mapRect.left - olRect.left + mapRect.width / 2, mapRect.top - olRect.top + mapRect.height / 2];
        var mapCenterCoord = this.olMap.getCoordinateFromPixel(mapCenterPx);
        return ol.proj.toLonLat(mapCenterCoord, this.olMap.getView().getProjection());
    },

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

    /**
     * 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) {
        var el = this.blocks[blockId];
        if (!el) return;
        visible ? el.style.removeProperty('display') : el.style.display = 'none';
    },

    CLASS_NAME: 'Descartes.UI.PrintMapPreview'
});

module.exports = Class;
