'use strict';

var Registry = require('../../registry');
var Lib = require('../../lib');
var pushUnique = Lib.pushUnique;

var SHOWISOLATETIP = true;

module.exports = function handleClick(g, gd, numClicks) {
    var fullLayout = gd._fullLayout;

    if(gd._dragged || gd._editing) return;

    var itemClick = fullLayout.legend.itemclick;
    var itemDoubleClick = fullLayout.legend.itemdoubleclick;
    var groupClick = fullLayout.legend.groupclick;

    if(numClicks === 1 && itemClick === 'toggle' && itemDoubleClick === 'toggleothers' &&
        SHOWISOLATETIP && gd.data && gd._context.showTips
    ) {
        Lib.notifier(Lib._(gd, 'Double-click on legend to isolate one trace'), 'long');
        SHOWISOLATETIP = false;
    } else {
        SHOWISOLATETIP = false;
    }

    var mode;
    if(numClicks === 1) mode = itemClick;
    else if(numClicks === 2) mode = itemDoubleClick;
    if(!mode) return;

    var toggleGroup = groupClick === 'togglegroup';

    var hiddenSlices = fullLayout.hiddenlabels ?
        fullLayout.hiddenlabels.slice() :
        [];

    var legendItem = g.data()[0][0];
    if(legendItem.groupTitle && legendItem.noClick) return;

    var fullData = gd._fullData;
    var shapesWithLegend = (fullLayout.shapes || []).filter(function(d) { return d.showlegend; });
    var allLegendItems = fullData.concat(shapesWithLegend);

    var fullTrace = legendItem.trace;
    if(fullTrace._isShape) {
        fullTrace = fullTrace._fullInput;
    }

    var legendgroup = fullTrace.legendgroup;

    var i, j, kcont, key, keys, val;
    var dataUpdate = {};
    var dataIndices = [];
    var carrs = [];
    var carrIdx = [];

    function insertDataUpdate(traceIndex, value) {
        var attrIndex = dataIndices.indexOf(traceIndex);
        var valueArray = dataUpdate.visible;
        if(!valueArray) {
            valueArray = dataUpdate.visible = [];
        }

        if(dataIndices.indexOf(traceIndex) === -1) {
            dataIndices.push(traceIndex);
            attrIndex = dataIndices.length - 1;
        }

        valueArray[attrIndex] = value;

        return attrIndex;
    }

    var updatedShapes = (fullLayout.shapes || []).map(function(d) {
        return d._input;
    });

    var shapesUpdated = false;

    function insertShapesUpdate(shapeIndex, value) {
        updatedShapes[shapeIndex].visible = value;
        shapesUpdated = true;
    }

    function setVisibility(fullTrace, visibility) {
        if(legendItem.groupTitle && !toggleGroup) return;

        var fullInput = fullTrace._fullInput || fullTrace;
        var isShape = fullInput._isShape;
        var index = fullInput.index;
        if(index === undefined) index = fullInput._index;

        if(Registry.hasTransform(fullInput, 'groupby')) {
            var kcont = carrs[index];
            if(!kcont) {
                var groupbyIndices = Registry.getTransformIndices(fullInput, 'groupby');
                var lastGroupbyIndex = groupbyIndices[groupbyIndices.length - 1];
                kcont = Lib.keyedContainer(fullInput, 'transforms[' + lastGroupbyIndex + '].styles', 'target', 'value.visible');
                carrs[index] = kcont;
            }

            var curState = kcont.get(fullTrace._group);

            // If not specified, assume visible. This happens if there are other style
            // properties set for a group but not the visibility. There are many similar
            // ways to do this (e.g. why not just `curState = fullTrace.visible`??? The
            // answer is: because it breaks other things like groupby trace names in
            // subtle ways.)
            if(curState === undefined) {
                curState = true;
            }

            if(curState !== false) {
                // true -> legendonly. All others toggle to true:
                kcont.set(fullTrace._group, visibility);
            }
            carrIdx[index] = insertDataUpdate(index, fullInput.visible === false ? false : true);
        } else {
            // false -> false (not possible since will not be visible in legend)
            // true -> legendonly
            // legendonly -> true
            var nextVisibility = fullInput.visible === false ? false : visibility;

            if(isShape) {
                insertShapesUpdate(index, nextVisibility);
            } else {
                insertDataUpdate(index, nextVisibility);
            }
        }
    }

    var thisLegend = fullTrace.legend;

    var fullInput = fullTrace._fullInput;
    var isShape = fullInput && fullInput._isShape;

    if(!isShape && Registry.traceIs(fullTrace, 'pie-like')) {
        var thisLabel = legendItem.label;
        var thisLabelIndex = hiddenSlices.indexOf(thisLabel);

        if(mode === 'toggle') {
            if(thisLabelIndex === -1) hiddenSlices.push(thisLabel);
            else hiddenSlices.splice(thisLabelIndex, 1);
        } else if(mode === 'toggleothers') {
            var changed = thisLabelIndex !== -1;
            var unhideList = [];
            for(i = 0; i < gd.calcdata.length; i++) {
                var cdi = gd.calcdata[i];
                for(j = 0; j < cdi.length; j++) {
                    var d = cdi[j];
                    var dLabel = d.label;

                    // ensure we toggle slices that are in this legend)
                    if(thisLegend === cdi[0].trace.legend) {
                        if(thisLabel !== dLabel) {
                            if(hiddenSlices.indexOf(dLabel) === -1) changed = true;
                            pushUnique(hiddenSlices, dLabel);
                            unhideList.push(dLabel);
                        }
                    }
                }
            }

            if(!changed) {
                for(var q = 0; q < unhideList.length; q++) {
                    var pos = hiddenSlices.indexOf(unhideList[q]);
                    if(pos !== -1) {
                        hiddenSlices.splice(pos, 1);
                    }
                }
            }
        }

        Registry.call('_guiRelayout', gd, 'hiddenlabels', hiddenSlices);
    } else {
        var hasLegendgroup = legendgroup && legendgroup.length;
        var traceIndicesInGroup = [];
        var tracei;
        if(hasLegendgroup) {
            for(i = 0; i < allLegendItems.length; i++) {
                tracei = allLegendItems[i];
                if(!tracei.visible) continue;
                if(tracei.legendgroup === legendgroup) {
                    traceIndicesInGroup.push(i);
                }
            }
        }

        if(mode === 'toggle') {
            var nextVisibility;

            switch(fullTrace.visible) {
                case true:
                    nextVisibility = 'legendonly';
                    break;
                case false:
                    nextVisibility = false;
                    break;
                case 'legendonly':
                    nextVisibility = true;
                    break;
            }

            if(hasLegendgroup) {
                if(toggleGroup) {
                    for(i = 0; i < allLegendItems.length; i++) {
                        var item = allLegendItems[i];
                        if(item.visible !== false && item.legendgroup === legendgroup) {
                            setVisibility(item, nextVisibility);
                        }
                    }
                } else {
                    setVisibility(fullTrace, nextVisibility);
                }
            } else {
                setVisibility(fullTrace, nextVisibility);
            }
        } else if(mode === 'toggleothers') {
            // Compute the clicked index. expandedIndex does what we want for expanded traces
            // but also culls hidden traces. That means we have some work to do.
            var isClicked, isInGroup, notInLegend, otherState, _item;
            var isIsolated = true;
            for(i = 0; i < allLegendItems.length; i++) {
                _item = allLegendItems[i];
                isClicked = _item === fullTrace;
                notInLegend = _item.showlegend !== true;
                if(isClicked || notInLegend) continue;

                isInGroup = (hasLegendgroup && _item.legendgroup === legendgroup);

                if(!isInGroup && _item.legend === thisLegend && _item.visible === true && !Registry.traceIs(_item, 'notLegendIsolatable')) {
                    isIsolated = false;
                    break;
                }
            }

            for(i = 0; i < allLegendItems.length; i++) {
                _item = allLegendItems[i];

                // False is sticky; we don't change it. Also ensure we don't change states of itmes in other legend
                if(_item.visible === false || _item.legend !== thisLegend) continue;

                if(Registry.traceIs(_item, 'notLegendIsolatable')) {
                    continue;
                }

                switch(fullTrace.visible) {
                    case 'legendonly':
                        setVisibility(_item, true);
                        break;
                    case true:
                        otherState = isIsolated ? true : 'legendonly';
                        isClicked = _item === fullTrace;
                        // N.B. consider traces that have a set legendgroup as toggleable
                        notInLegend = (_item.showlegend !== true && !_item.legendgroup);
                        isInGroup = isClicked || (hasLegendgroup && _item.legendgroup === legendgroup);
                        setVisibility(_item, (isInGroup || notInLegend) ? true : otherState);
                        break;
                }
            }
        }

        for(i = 0; i < carrs.length; i++) {
            kcont = carrs[i];
            if(!kcont) continue;
            var update = kcont.constructUpdate();

            var updateKeys = Object.keys(update);
            for(j = 0; j < updateKeys.length; j++) {
                key = updateKeys[j];
                val = dataUpdate[key] = dataUpdate[key] || [];
                val[carrIdx[i]] = update[key];
            }
        }

        // The length of the value arrays should be equal and any unspecified
        // values should be explicitly undefined for them to get properly culled
        // as updates and not accidentally reset to the default value. This fills
        // out sparse arrays with the required number of undefined values:
        keys = Object.keys(dataUpdate);
        for(i = 0; i < keys.length; i++) {
            key = keys[i];
            for(j = 0; j < dataIndices.length; j++) {
                // Use hasOwnProperty to protect against falsy values:
                if(!dataUpdate[key].hasOwnProperty(j)) {
                    dataUpdate[key][j] = undefined;
                }
            }
        }

        if(shapesUpdated) {
            Registry.call('_guiUpdate', gd, dataUpdate, {shapes: updatedShapes}, dataIndices);
        } else {
            Registry.call('_guiRestyle', gd, dataUpdate, dataIndices);
        }
    }
};
