'use strict';

var mapboxgl = require('@plotly/mapbox-gl/dist/mapbox-gl-unminified');

var Lib = require('../../lib');
var strTranslate = Lib.strTranslate;
var strScale = Lib.strScale;
var getSubplotCalcData = require('../../plots/get_data').getSubplotCalcData;
var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
var d3 = require('@plotly/d3');
var Drawing = require('../../components/drawing');
var svgTextUtils = require('../../lib/svg_text_utils');

var Mapbox = require('./mapbox');

var MAPBOX = 'mapbox';

var constants = exports.constants = require('./constants');

exports.name = MAPBOX;

exports.attr = 'subplot';

exports.idRoot = MAPBOX;

exports.idRegex = exports.attrRegex = Lib.counterRegex(MAPBOX);

var deprecationWarning = [
    'mapbox subplots and traces are deprecated!',
    'Please consider switching to `map` subplots and traces.',
    'Learn more at: https://plotly.com/javascript/maplibre-migration/'
].join(' ');

exports.attributes = {
    subplot: {
        valType: 'subplotid',
        dflt: 'mapbox',
        editType: 'calc',
        description: [
            deprecationWarning,
            'Sets a reference between this trace\'s data coordinates and',
            'a mapbox subplot.',
            'If *mapbox* (the default value), the data refer to `layout.mapbox`.',
            'If *mapbox2*, the data refer to `layout.mapbox2`, and so on.'
        ].join(' ')
    }
};

exports.layoutAttributes = require('./layout_attributes');

exports.supplyLayoutDefaults = require('./layout_defaults');

var firstPlot = true;

exports.plot = function plot(gd) {
    if(firstPlot) {
        firstPlot = false;
        Lib.warn(deprecationWarning);
    }

    var fullLayout = gd._fullLayout;
    var calcData = gd.calcdata;
    var mapboxIds = fullLayout._subplots[MAPBOX];

    if(mapboxgl.version !== constants.requiredVersion) {
        throw new Error(constants.wrongVersionErrorMsg);
    }

    var accessToken = findAccessToken(gd, mapboxIds);
    mapboxgl.accessToken = accessToken;

    for(var i = 0; i < mapboxIds.length; i++) {
        var id = mapboxIds[i];
        var subplotCalcData = getSubplotCalcData(calcData, MAPBOX, id);
        var opts = fullLayout[id];
        var mapbox = opts._subplot;

        if(!mapbox) {
            mapbox = new Mapbox(gd, id);
            fullLayout[id]._subplot = mapbox;
        }

        if(!mapbox.viewInitial) {
            mapbox.viewInitial = {
                center: Lib.extendFlat({}, opts.center),
                zoom: opts.zoom,
                bearing: opts.bearing,
                pitch: opts.pitch
            };
        }

        mapbox.plot(subplotCalcData, fullLayout, gd._promises);
    }
};

exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
    var oldMapboxKeys = oldFullLayout._subplots[MAPBOX] || [];

    for(var i = 0; i < oldMapboxKeys.length; i++) {
        var oldMapboxKey = oldMapboxKeys[i];

        if(!newFullLayout[oldMapboxKey] && !!oldFullLayout[oldMapboxKey]._subplot) {
            oldFullLayout[oldMapboxKey]._subplot.destroy();
        }
    }
};

exports.toSVG = function(gd) {
    var fullLayout = gd._fullLayout;
    var subplotIds = fullLayout._subplots[MAPBOX];
    var size = fullLayout._size;

    for(var i = 0; i < subplotIds.length; i++) {
        var opts = fullLayout[subplotIds[i]];
        var domain = opts.domain;
        var mapbox = opts._subplot;

        var imageData = mapbox.toImage('png');
        var image = fullLayout._glimages.append('svg:image');

        image.attr({
            xmlns: xmlnsNamespaces.svg,
            'xlink:href': imageData,
            x: size.l + size.w * domain.x[0],
            y: size.t + size.h * (1 - domain.y[1]),
            width: size.w * (domain.x[1] - domain.x[0]),
            height: size.h * (domain.y[1] - domain.y[0]),
            preserveAspectRatio: 'none'
        });

        var subplotDiv = d3.select(opts._subplot.div);

        // Append logo if visible
        var hidden = subplotDiv.select('.mapboxgl-ctrl-logo').node().offsetParent === null;
        if(!hidden) {
            var logo = fullLayout._glimages.append('g');
            logo.attr('transform', strTranslate(size.l + size.w * domain.x[0] + 10, size.t + size.h * (1 - domain.y[0]) - 31));
            logo.append('path')
                .attr('d', constants.mapboxLogo.path0)
                .style({
                    opacity: 0.9,
                    fill: '#ffffff',
                    'enable-background': 'new'
                });

            logo.append('path')
                .attr('d', constants.mapboxLogo.path1)
                .style('opacity', 0.35)
                .style('enable-background', 'new');

            logo.append('path')
                .attr('d', constants.mapboxLogo.path2)
                .style('opacity', 0.35)
                .style('enable-background', 'new');

            logo.append('polygon')
                .attr('points', constants.mapboxLogo.polygon)
                .style({
                    opacity: 0.9,
                    fill: '#ffffff',
                    'enable-background': 'new'
                });
        }

        // Add attributions
        var attributions = subplotDiv
            .select('.mapboxgl-ctrl-attrib').text()
            .replace('Improve this map', '');

        var attributionGroup = fullLayout._glimages.append('g');

        var attributionText = attributionGroup.append('text');
        attributionText
            .text(attributions)
            .classed('static-attribution', true)
            .attr({
                'font-size': 12,
                'font-family': 'Arial',
                color: 'rgba(0, 0, 0, 0.75)',
                'text-anchor': 'end',
                'data-unformatted': attributions
            });

        var bBox = Drawing.bBox(attributionText.node());

        // Break into multiple lines twice larger than domain
        var maxWidth = size.w * (domain.x[1] - domain.x[0]);
        if((bBox.width > maxWidth / 2)) {
            var multilineAttributions = attributions.split('|').join('<br>');
            attributionText
                .text(multilineAttributions)
                .attr('data-unformatted', multilineAttributions)
                .call(svgTextUtils.convertToTspans, gd);

            bBox = Drawing.bBox(attributionText.node());
        }
        attributionText.attr('transform', strTranslate(-3, -bBox.height + 8));

        // Draw white rectangle behind text
        attributionGroup
            .insert('rect', '.static-attribution')
            .attr({
                x: -bBox.width - 6,
                y: -bBox.height - 3,
                width: bBox.width + 6,
                height: bBox.height + 3,
                fill: 'rgba(255, 255, 255, 0.75)'
            });

        // Scale down if larger than domain
        var scaleRatio = 1;
        if((bBox.width + 6) > maxWidth) scaleRatio = maxWidth / (bBox.width + 6);

        var offset = [(size.l + size.w * domain.x[1]), (size.t + size.h * (1 - domain.y[0]))];
        attributionGroup.attr('transform', strTranslate(offset[0], offset[1]) + strScale(scaleRatio));
    }
};

// N.B. mapbox-gl only allows one accessToken to be set per page:
// https://github.com/mapbox/mapbox-gl-js/issues/6331
function findAccessToken(gd, mapboxIds) {
    var fullLayout = gd._fullLayout;
    var context = gd._context;

    // special case for Mapbox Atlas users
    if(context.mapboxAccessToken === '') return '';

    var tokensUseful = [];
    var tokensListed = [];
    var hasOneSetMapboxStyle = false;
    var wontWork = false;

    // Take the first token we find in a mapbox subplot.
    // These default to the context value but may be overridden.
    for(var i = 0; i < mapboxIds.length; i++) {
        var opts = fullLayout[mapboxIds[i]];
        var token = opts.accesstoken;

        if(isStyleRequireAccessToken(opts.style)) {
            if(token) {
                Lib.pushUnique(tokensUseful, token);
            } else {
                if(isStyleRequireAccessToken(opts._input.style)) {
                    Lib.error('Uses Mapbox map style, but did not set an access token.');
                    hasOneSetMapboxStyle = true;
                }
                wontWork = true;
            }
        }

        if(token) {
            Lib.pushUnique(tokensListed, token);
        }
    }

    if(wontWork) {
        var msg = hasOneSetMapboxStyle ?
            constants.noAccessTokenErrorMsg :
            constants.missingStyleErrorMsg;
        Lib.error(msg);
        throw new Error(msg);
    }

    if(tokensUseful.length) {
        if(tokensUseful.length > 1) {
            Lib.warn(constants.multipleTokensErrorMsg);
        }
        return tokensUseful[0];
    } else {
        if(tokensListed.length) {
            Lib.log([
                'Listed mapbox access token(s)', tokensListed.join(','),
                'but did not use a Mapbox map style, ignoring token(s).'
            ].join(' '));
        }
        return '';
    }
}

function isStyleRequireAccessToken(s) {
    return typeof s === 'string' && (
        constants.styleValuesMapbox.indexOf(s) !== -1 ||
        s.indexOf('mapbox://') === 0 ||
        s.indexOf('stamen') === 0
    );
}

exports.updateFx = function(gd) {
    var fullLayout = gd._fullLayout;
    var subplotIds = fullLayout._subplots[MAPBOX];

    for(var i = 0; i < subplotIds.length; i++) {
        var subplotObj = fullLayout[subplotIds[i]]._subplot;
        subplotObj.updateFx(fullLayout);
    }
};
