'use strict';

var Registry = require('../../registry');
var Plots = require('../../plots/plots');
var axisIds = require('../../plots/cartesian/axis_ids');
var Icons = require('../../fonts/ploticon');
var eraseActiveShape = require('../shapes/draw').eraseActiveShape;
var Lib = require('../../lib');
var _ = Lib._;

var modeBarButtons = module.exports = {};

/**
 * ModeBar buttons configuration
 *
 * @param {string} name
 *      name / id of the buttons (for tracking)
 * @param {string} title
 *      text that appears while hovering over the button,
 *      enter null, false or '' for no hover text
 * @param {string} icon
 *      svg icon object associated with the button
 *      can be linked to Plotly.Icons to use the default plotly icons
 * @param {string} [gravity]
 *      icon positioning
 * @param {function} click
 *      click handler associated with the button, a function of
 *      'gd' (the main graph object) and
 *      'ev' (the event object)
 * @param {string} [attr]
 *      attribute associated with button,
 *      use this with 'val' to keep track of the state
 * @param {*} [val]
 *      initial 'attr' value, can be a function of gd
 * @param {boolean} [toggle]
 *      is the button a toggle button?
 */
modeBarButtons.toImage = {
    name: 'toImage',
    title: function(gd) {
        var opts = gd._context.toImageButtonOptions || {};
        var format = opts.format || 'png';
        return format === 'png' ?
            _(gd, 'Download plot as a png') : // legacy text
            _(gd, 'Download plot'); // generic non-PNG text
    },
    icon: Icons.camera,
    click: function(gd) {
        var toImageButtonOptions = gd._context.toImageButtonOptions;
        var opts = {format: toImageButtonOptions.format || 'png'};

        Lib.notifier(_(gd, 'Taking snapshot - this may take a few seconds'), 'long');

        if(opts.format !== 'svg' && Lib.isIE()) {
            Lib.notifier(_(gd, 'IE only supports svg.  Changing format to svg.'), 'long');
            opts.format = 'svg';
        }

        ['filename', 'width', 'height', 'scale'].forEach(function(key) {
            if(key in toImageButtonOptions) {
                opts[key] = toImageButtonOptions[key];
            }
        });

        Registry.call('downloadImage', gd, opts)
          .then(function(filename) {
              Lib.notifier(_(gd, 'Snapshot succeeded') + ' - ' + filename, 'long');
          })
          .catch(function() {
              Lib.notifier(_(gd, 'Sorry, there was a problem downloading your snapshot!'), 'long');
          });
    }
};

modeBarButtons.sendDataToCloud = {
    name: 'sendDataToCloud',
    title: function(gd) { return _(gd, 'Edit in Chart Studio'); },
    icon: Icons.disk,
    click: function(gd) {
        Plots.sendDataToCloud(gd);
    }
};

modeBarButtons.editInChartStudio = {
    name: 'editInChartStudio',
    title: function(gd) { return _(gd, 'Edit in Chart Studio'); },
    icon: Icons.pencil,
    click: function(gd) {
        Plots.sendDataToCloud(gd);
    }
};

modeBarButtons.zoom2d = {
    name: 'zoom2d',
    _cat: 'zoom',
    title: function(gd) { return _(gd, 'Zoom'); },
    attr: 'dragmode',
    val: 'zoom',
    icon: Icons.zoombox,
    click: handleCartesian
};

modeBarButtons.pan2d = {
    name: 'pan2d',
    _cat: 'pan',
    title: function(gd) { return _(gd, 'Pan'); },
    attr: 'dragmode',
    val: 'pan',
    icon: Icons.pan,
    click: handleCartesian
};

modeBarButtons.select2d = {
    name: 'select2d',
    _cat: 'select',
    title: function(gd) { return _(gd, 'Box Select'); },
    attr: 'dragmode',
    val: 'select',
    icon: Icons.selectbox,
    click: handleCartesian
};

modeBarButtons.lasso2d = {
    name: 'lasso2d',
    _cat: 'lasso',
    title: function(gd) { return _(gd, 'Lasso Select'); },
    attr: 'dragmode',
    val: 'lasso',
    icon: Icons.lasso,
    click: handleCartesian
};

modeBarButtons.drawclosedpath = {
    name: 'drawclosedpath',
    title: function(gd) { return _(gd, 'Draw closed freeform'); },
    attr: 'dragmode',
    val: 'drawclosedpath',
    icon: Icons.drawclosedpath,
    click: handleCartesian
};

modeBarButtons.drawopenpath = {
    name: 'drawopenpath',
    title: function(gd) { return _(gd, 'Draw open freeform'); },
    attr: 'dragmode',
    val: 'drawopenpath',
    icon: Icons.drawopenpath,
    click: handleCartesian
};

modeBarButtons.drawline = {
    name: 'drawline',
    title: function(gd) { return _(gd, 'Draw line'); },
    attr: 'dragmode',
    val: 'drawline',
    icon: Icons.drawline,
    click: handleCartesian
};

modeBarButtons.drawrect = {
    name: 'drawrect',
    title: function(gd) { return _(gd, 'Draw rectangle'); },
    attr: 'dragmode',
    val: 'drawrect',
    icon: Icons.drawrect,
    click: handleCartesian
};

modeBarButtons.drawcircle = {
    name: 'drawcircle',
    title: function(gd) { return _(gd, 'Draw circle'); },
    attr: 'dragmode',
    val: 'drawcircle',
    icon: Icons.drawcircle,
    click: handleCartesian
};

modeBarButtons.eraseshape = {
    name: 'eraseshape',
    title: function(gd) { return _(gd, 'Erase active shape'); },
    icon: Icons.eraseshape,
    click: eraseActiveShape
};

modeBarButtons.zoomIn2d = {
    name: 'zoomIn2d',
    _cat: 'zoomin',
    title: function(gd) { return _(gd, 'Zoom in'); },
    attr: 'zoom',
    val: 'in',
    icon: Icons.zoom_plus,
    click: handleCartesian
};

modeBarButtons.zoomOut2d = {
    name: 'zoomOut2d',
    _cat: 'zoomout',
    title: function(gd) { return _(gd, 'Zoom out'); },
    attr: 'zoom',
    val: 'out',
    icon: Icons.zoom_minus,
    click: handleCartesian
};

modeBarButtons.autoScale2d = {
    name: 'autoScale2d',
    _cat: 'autoscale',
    title: function(gd) { return _(gd, 'Autoscale'); },
    attr: 'zoom',
    val: 'auto',
    icon: Icons.autoscale,
    click: handleCartesian
};

modeBarButtons.resetScale2d = {
    name: 'resetScale2d',
    _cat: 'resetscale',
    title: function(gd) { return _(gd, 'Reset axes'); },
    attr: 'zoom',
    val: 'reset',
    icon: Icons.home,
    click: handleCartesian
};

modeBarButtons.hoverClosestCartesian = {
    name: 'hoverClosestCartesian',
    _cat: 'hoverclosest',
    title: function(gd) { return _(gd, 'Show closest data on hover'); },
    attr: 'hovermode',
    val: 'closest',
    icon: Icons.tooltip_basic,
    gravity: 'ne',
    click: handleCartesian
};

modeBarButtons.hoverCompareCartesian = {
    name: 'hoverCompareCartesian',
    _cat: 'hoverCompare',
    title: function(gd) { return _(gd, 'Compare data on hover'); },
    attr: 'hovermode',
    val: function(gd) {
        return gd._fullLayout._isHoriz ? 'y' : 'x';
    },
    icon: Icons.tooltip_compare,
    gravity: 'ne',
    click: handleCartesian
};

function handleCartesian(gd, ev) {
    var button = ev.currentTarget;
    var astr = button.getAttribute('data-attr');
    var val = button.getAttribute('data-val') || true;
    var fullLayout = gd._fullLayout;
    var aobj = {};
    var axList = axisIds.list(gd, null, true);
    var allSpikesEnabled = fullLayout._cartesianSpikesEnabled;

    var ax, i;

    if(astr === 'zoom') {
        var mag = (val === 'in') ? 0.5 : 2;
        var r0 = (1 + mag) / 2;
        var r1 = (1 - mag) / 2;
        var axName;

        for(i = 0; i < axList.length; i++) {
            ax = axList[i];

            if(!ax.fixedrange) {
                axName = ax._name;
                if(val === 'auto') {
                    aobj[axName + '.autorange'] = true;
                } else if(val === 'reset') {
                    if(ax._rangeInitial0 === undefined && ax._rangeInitial1 === undefined) {
                        aobj[axName + '.autorange'] = true;
                    } else if(ax._rangeInitial0 === undefined) {
                        aobj[axName + '.autorange'] = ax._autorangeInitial;
                        aobj[axName + '.range'] = [null, ax._rangeInitial1];
                    } else if(ax._rangeInitial1 === undefined) {
                        aobj[axName + '.range'] = [ax._rangeInitial0, null];
                        aobj[axName + '.autorange'] = ax._autorangeInitial;
                    } else {
                        aobj[axName + '.range'] = [ax._rangeInitial0, ax._rangeInitial1];
                    }

                    // N.B. "reset" also resets showspikes
                    if(ax._showSpikeInitial !== undefined) {
                        aobj[axName + '.showspikes'] = ax._showSpikeInitial;
                        if(allSpikesEnabled === 'on' && !ax._showSpikeInitial) {
                            allSpikesEnabled = 'off';
                        }
                    }
                } else {
                    var rangeNow = [
                        ax.r2l(ax.range[0]),
                        ax.r2l(ax.range[1]),
                    ];

                    var rangeNew = [
                        r0 * rangeNow[0] + r1 * rangeNow[1],
                        r0 * rangeNow[1] + r1 * rangeNow[0]
                    ];

                    aobj[axName + '.range[0]'] = ax.l2r(rangeNew[0]);
                    aobj[axName + '.range[1]'] = ax.l2r(rangeNew[1]);
                }
            }
        }
    } else {
        // if ALL traces have orientation 'h', 'hovermode': 'x' otherwise: 'y'
        if(astr === 'hovermode' && (val === 'x' || val === 'y')) {
            val = fullLayout._isHoriz ? 'y' : 'x';
            button.setAttribute('data-val', val);
        }

        aobj[astr] = val;
    }

    fullLayout._cartesianSpikesEnabled = allSpikesEnabled;

    Registry.call('_guiRelayout', gd, aobj);
}

modeBarButtons.zoom3d = {
    name: 'zoom3d',
    _cat: 'zoom',
    title: function(gd) { return _(gd, 'Zoom'); },
    attr: 'scene.dragmode',
    val: 'zoom',
    icon: Icons.zoombox,
    click: handleDrag3d
};

modeBarButtons.pan3d = {
    name: 'pan3d',
    _cat: 'pan',
    title: function(gd) { return _(gd, 'Pan'); },
    attr: 'scene.dragmode',
    val: 'pan',
    icon: Icons.pan,
    click: handleDrag3d
};

modeBarButtons.orbitRotation = {
    name: 'orbitRotation',
    title: function(gd) { return _(gd, 'Orbital rotation'); },
    attr: 'scene.dragmode',
    val: 'orbit',
    icon: Icons['3d_rotate'],
    click: handleDrag3d
};

modeBarButtons.tableRotation = {
    name: 'tableRotation',
    title: function(gd) { return _(gd, 'Turntable rotation'); },
    attr: 'scene.dragmode',
    val: 'turntable',
    icon: Icons['z-axis'],
    click: handleDrag3d
};

function handleDrag3d(gd, ev) {
    var button = ev.currentTarget;
    var attr = button.getAttribute('data-attr');
    var val = button.getAttribute('data-val') || true;
    var sceneIds = gd._fullLayout._subplots.gl3d || [];
    var layoutUpdate = {};

    var parts = attr.split('.');

    for(var i = 0; i < sceneIds.length; i++) {
        layoutUpdate[sceneIds[i] + '.' + parts[1]] = val;
    }

    // for multi-type subplots
    var val2d = (val === 'pan') ? val : 'zoom';
    layoutUpdate.dragmode = val2d;

    Registry.call('_guiRelayout', gd, layoutUpdate);
}

modeBarButtons.resetCameraDefault3d = {
    name: 'resetCameraDefault3d',
    _cat: 'resetCameraDefault',
    title: function(gd) { return _(gd, 'Reset camera to default'); },
    attr: 'resetDefault',
    icon: Icons.home,
    click: handleCamera3d
};

modeBarButtons.resetCameraLastSave3d = {
    name: 'resetCameraLastSave3d',
    _cat: 'resetCameraLastSave',
    title: function(gd) { return _(gd, 'Reset camera to last save'); },
    attr: 'resetLastSave',
    icon: Icons.movie,
    click: handleCamera3d
};

function handleCamera3d(gd, ev) {
    var button = ev.currentTarget;
    var attr = button.getAttribute('data-attr');
    var resetLastSave = attr === 'resetLastSave';
    var resetDefault = attr === 'resetDefault';

    var fullLayout = gd._fullLayout;
    var sceneIds = fullLayout._subplots.gl3d || [];
    var aobj = {};

    for(var i = 0; i < sceneIds.length; i++) {
        var sceneId = sceneIds[i];
        var camera = sceneId + '.camera';
        var aspectratio = sceneId + '.aspectratio';
        var aspectmode = sceneId + '.aspectmode';
        var scene = fullLayout[sceneId]._scene;
        var didUpdate;

        if(resetLastSave) {
            aobj[camera + '.up'] = scene.viewInitial.up;
            aobj[camera + '.eye'] = scene.viewInitial.eye;
            aobj[camera + '.center'] = scene.viewInitial.center;
            didUpdate = true;
        } else if(resetDefault) {
            aobj[camera + '.up'] = null;
            aobj[camera + '.eye'] = null;
            aobj[camera + '.center'] = null;
            didUpdate = true;
        }

        if(didUpdate) {
            aobj[aspectratio + '.x'] = scene.viewInitial.aspectratio.x;
            aobj[aspectratio + '.y'] = scene.viewInitial.aspectratio.y;
            aobj[aspectratio + '.z'] = scene.viewInitial.aspectratio.z;
            aobj[aspectmode] = scene.viewInitial.aspectmode;
        }
    }

    Registry.call('_guiRelayout', gd, aobj);
}

modeBarButtons.hoverClosest3d = {
    name: 'hoverClosest3d',
    _cat: 'hoverclosest',
    title: function(gd) { return _(gd, 'Toggle show closest data on hover'); },
    attr: 'hovermode',
    val: null,
    toggle: true,
    icon: Icons.tooltip_basic,
    gravity: 'ne',
    click: handleHover3d
};

function getNextHover3d(gd, ev) {
    var button = ev.currentTarget;
    var val = button._previousVal;
    var fullLayout = gd._fullLayout;
    var sceneIds = fullLayout._subplots.gl3d || [];

    var axes = ['xaxis', 'yaxis', 'zaxis'];

    // initialize 'current spike' object to be stored in the DOM
    var currentSpikes = {};
    var layoutUpdate = {};

    if(val) {
        layoutUpdate = val;
        button._previousVal = null;
    } else {
        for(var i = 0; i < sceneIds.length; i++) {
            var sceneId = sceneIds[i];
            var sceneLayout = fullLayout[sceneId];

            var hovermodeAStr = sceneId + '.hovermode';
            currentSpikes[hovermodeAStr] = sceneLayout.hovermode;
            layoutUpdate[hovermodeAStr] = false;

            // copy all the current spike attrs
            for(var j = 0; j < 3; j++) {
                var axis = axes[j];
                var spikeAStr = sceneId + '.' + axis + '.showspikes';
                layoutUpdate[spikeAStr] = false;
                currentSpikes[spikeAStr] = sceneLayout[axis].showspikes;
            }
        }

        button._previousVal = currentSpikes;
    }
    return layoutUpdate;
}

function handleHover3d(gd, ev) {
    var layoutUpdate = getNextHover3d(gd, ev);
    Registry.call('_guiRelayout', gd, layoutUpdate);
}

modeBarButtons.zoomInGeo = {
    name: 'zoomInGeo',
    _cat: 'zoomin',
    title: function(gd) { return _(gd, 'Zoom in'); },
    attr: 'zoom',
    val: 'in',
    icon: Icons.zoom_plus,
    click: handleGeo
};

modeBarButtons.zoomOutGeo = {
    name: 'zoomOutGeo',
    _cat: 'zoomout',
    title: function(gd) { return _(gd, 'Zoom out'); },
    attr: 'zoom',
    val: 'out',
    icon: Icons.zoom_minus,
    click: handleGeo
};

modeBarButtons.resetGeo = {
    name: 'resetGeo',
    _cat: 'reset',
    title: function(gd) { return _(gd, 'Reset'); },
    attr: 'reset',
    val: null,
    icon: Icons.autoscale,
    click: handleGeo
};

modeBarButtons.hoverClosestGeo = {
    name: 'hoverClosestGeo',
    _cat: 'hoverclosest',
    title: function(gd) { return _(gd, 'Toggle show closest data on hover'); },
    attr: 'hovermode',
    val: null,
    toggle: true,
    icon: Icons.tooltip_basic,
    gravity: 'ne',
    click: toggleHover
};

function handleGeo(gd, ev) {
    var button = ev.currentTarget;
    var attr = button.getAttribute('data-attr');
    var val = button.getAttribute('data-val') || true;
    var fullLayout = gd._fullLayout;
    var geoIds = fullLayout._subplots.geo || [];

    for(var i = 0; i < geoIds.length; i++) {
        var id = geoIds[i];
        var geoLayout = fullLayout[id];

        if(attr === 'zoom') {
            var scale = geoLayout.projection.scale;
            var newScale = (val === 'in') ? 2 * scale : 0.5 * scale;

            Registry.call('_guiRelayout', gd, id + '.projection.scale', newScale);
        }
    }

    if(attr === 'reset') {
        resetView(gd, 'geo');
    }
}

modeBarButtons.hoverClosestGl2d = {
    name: 'hoverClosestGl2d',
    _cat: 'hoverclosest',
    title: function(gd) { return _(gd, 'Toggle show closest data on hover'); },
    attr: 'hovermode',
    val: null,
    toggle: true,
    icon: Icons.tooltip_basic,
    gravity: 'ne',
    click: toggleHover
};

modeBarButtons.hoverClosestPie = {
    name: 'hoverClosestPie',
    _cat: 'hoverclosest',
    title: function(gd) { return _(gd, 'Toggle show closest data on hover'); },
    attr: 'hovermode',
    val: 'closest',
    icon: Icons.tooltip_basic,
    gravity: 'ne',
    click: toggleHover
};

function getNextHover(gd) {
    var fullLayout = gd._fullLayout;

    if(fullLayout.hovermode) return false;

    if(fullLayout._has('cartesian')) {
        return fullLayout._isHoriz ? 'y' : 'x';
    }
    return 'closest';
}

function toggleHover(gd) {
    var newHover = getNextHover(gd);
    Registry.call('_guiRelayout', gd, 'hovermode', newHover);
}

modeBarButtons.resetViewSankey = {
    name: 'resetSankeyGroup',
    title: function(gd) { return _(gd, 'Reset view'); },
    icon: Icons.home,
    click: function(gd) {
        var aObj = {
            'node.groups': [],
            'node.x': [],
            'node.y': []
        };
        for(var i = 0; i < gd._fullData.length; i++) {
            var viewInitial = gd._fullData[i]._viewInitial;
            aObj['node.groups'].push(viewInitial.node.groups.slice());
            aObj['node.x'].push(viewInitial.node.x.slice());
            aObj['node.y'].push(viewInitial.node.y.slice());
        }
        Registry.call('restyle', gd, aObj);
    }
};

// buttons when more then one plot types are present

modeBarButtons.toggleHover = {
    name: 'toggleHover',
    title: function(gd) { return _(gd, 'Toggle show closest data on hover'); },
    attr: 'hovermode',
    val: null,
    toggle: true,
    icon: Icons.tooltip_basic,
    gravity: 'ne',
    click: function(gd, ev) {
        var layoutUpdate = getNextHover3d(gd, ev);
        layoutUpdate.hovermode = getNextHover(gd);

        Registry.call('_guiRelayout', gd, layoutUpdate);
    }
};

modeBarButtons.resetViews = {
    name: 'resetViews',
    title: function(gd) { return _(gd, 'Reset views'); },
    icon: Icons.home,
    click: function(gd, ev) {
        var button = ev.currentTarget;

        button.setAttribute('data-attr', 'zoom');
        button.setAttribute('data-val', 'reset');
        handleCartesian(gd, ev);

        button.setAttribute('data-attr', 'resetLastSave');
        handleCamera3d(gd, ev);

        resetView(gd, 'geo');
        resetView(gd, 'mapbox');
        resetView(gd, 'map');
    }
};

modeBarButtons.toggleSpikelines = {
    name: 'toggleSpikelines',
    title: function(gd) { return _(gd, 'Toggle Spike Lines'); },
    icon: Icons.spikeline,
    attr: '_cartesianSpikesEnabled',
    val: 'on',
    click: function(gd) {
        var fullLayout = gd._fullLayout;
        var allSpikesEnabled = fullLayout._cartesianSpikesEnabled;

        fullLayout._cartesianSpikesEnabled = allSpikesEnabled === 'on' ? 'off' : 'on';
        Registry.call('_guiRelayout', gd, setSpikelineVisibility(gd));
    }
};

function setSpikelineVisibility(gd) {
    var fullLayout = gd._fullLayout;
    var areSpikesOn = fullLayout._cartesianSpikesEnabled === 'on';
    var axList = axisIds.list(gd, null, true);
    var aobj = {};

    for(var i = 0; i < axList.length; i++) {
        var ax = axList[i];
        aobj[ax._name + '.showspikes'] = areSpikesOn ? true : ax._showSpikeInitial;
    }

    return aobj;
}

modeBarButtons.resetViewMapbox = {
    name: 'resetViewMapbox',
    _cat: 'resetView',
    title: function(gd) { return _(gd, 'Reset view'); },
    attr: 'reset',
    icon: Icons.home,
    click: function(gd) {
        resetView(gd, 'mapbox');
    }
};

modeBarButtons.resetViewMap = {
    name: 'resetViewMap',
    _cat: 'resetView',
    title: function(gd) { return _(gd, 'Reset view'); },
    attr: 'reset',
    icon: Icons.home,
    click: function(gd) {
        resetView(gd, 'map');
    }
};

modeBarButtons.zoomInMapbox = {
    name: 'zoomInMapbox',
    _cat: 'zoomin',
    title: function(gd) { return _(gd, 'Zoom in'); },
    attr: 'zoom',
    val: 'in',
    icon: Icons.zoom_plus,
    click: handleMapboxZoom
};

modeBarButtons.zoomInMap = {
    name: 'zoomInMap',
    _cat: 'zoomin',
    title: function(gd) { return _(gd, 'Zoom in'); },
    attr: 'zoom',
    val: 'in',
    icon: Icons.zoom_plus,
    click: handleMapZoom
};

modeBarButtons.zoomOutMapbox = {
    name: 'zoomOutMapbox',
    _cat: 'zoomout',
    title: function(gd) { return _(gd, 'Zoom out'); },
    attr: 'zoom',
    val: 'out',
    icon: Icons.zoom_minus,
    click: handleMapboxZoom
};

modeBarButtons.zoomOutMap = {
    name: 'zoomOutMap',
    _cat: 'zoomout',
    title: function(gd) { return _(gd, 'Zoom out'); },
    attr: 'zoom',
    val: 'out',
    icon: Icons.zoom_minus,
    click: handleMapZoom
};

function handleMapboxZoom(gd, ev) {
    _handleMapZoom(gd, ev, 'mapbox');
}

function handleMapZoom(gd, ev) {
    _handleMapZoom(gd, ev, 'map');
}

function _handleMapZoom(gd, ev, mapType) {
    var button = ev.currentTarget;
    var val = button.getAttribute('data-val');
    var fullLayout = gd._fullLayout;
    var subplotIds = fullLayout._subplots[mapType] || [];
    var scalar = 1.05;
    var aObj = {};

    for(var i = 0; i < subplotIds.length; i++) {
        var id = subplotIds[i];
        var current = fullLayout[id].zoom;
        var next = (val === 'in') ? scalar * current : current / scalar;
        aObj[id + '.zoom'] = next;
    }

    Registry.call('_guiRelayout', gd, aObj);
}

function resetView(gd, subplotType) {
    var fullLayout = gd._fullLayout;
    var subplotIds = fullLayout._subplots[subplotType] || [];
    var aObj = {};

    for(var i = 0; i < subplotIds.length; i++) {
        var id = subplotIds[i];
        var subplotObj = fullLayout[id]._subplot;
        var viewInitial = subplotObj.viewInitial;
        var viewKeys = Object.keys(viewInitial);

        for(var j = 0; j < viewKeys.length; j++) {
            var key = viewKeys[j];
            aObj[id + '.' + key] = viewInitial[key];
        }
    }

    Registry.call('_guiRelayout', gd, aObj);
}
