'use strict';

var Lib = require('../../lib');

// look for either subplot or xaxis and yaxis attributes
// does not handle splom case
exports.getSubplot = function(trace) {
    return trace.subplot || (trace.xaxis + trace.yaxis) || trace.geo;
};

// is trace in given list of subplots?
// does handle splom case
exports.isTraceInSubplots = function(trace, subplots) {
    if(trace.type === 'splom') {
        var xaxes = trace.xaxes || [];
        var yaxes = trace.yaxes || [];
        for(var i = 0; i < xaxes.length; i++) {
            for(var j = 0; j < yaxes.length; j++) {
                if(subplots.indexOf(xaxes[i] + yaxes[j]) !== -1) {
                    return true;
                }
            }
        }
        return false;
    }

    return subplots.indexOf(exports.getSubplot(trace)) !== -1;
};

// convenience functions for mapping all relevant axes
exports.flat = function(subplots, v) {
    var out = new Array(subplots.length);
    for(var i = 0; i < subplots.length; i++) {
        out[i] = v;
    }
    return out;
};

exports.p2c = function(axArray, v) {
    var out = new Array(axArray.length);
    for(var i = 0; i < axArray.length; i++) {
        out[i] = axArray[i].p2c(v);
    }
    return out;
};

exports.getDistanceFunction = function(mode, dx, dy, dxy) {
    if(mode === 'closest') return dxy || exports.quadrature(dx, dy);
    return mode.charAt(0) === 'x' ? dx : dy;
};

exports.getClosest = function(cd, distfn, pointData) {
    // do we already have a point number? (array mode only)
    if(pointData.index !== false) {
        if(pointData.index >= 0 && pointData.index < cd.length) {
            pointData.distance = 0;
        } else pointData.index = false;
    } else {
        // apply the distance function to each data point
        // this is the longest loop... if this bogs down, we may need
        // to create pre-sorted data (by x or y), not sure how to
        // do this for 'closest'
        for(var i = 0; i < cd.length; i++) {
            var newDistance = distfn(cd[i]);
            if(newDistance <= pointData.distance) {
                pointData.index = i;
                pointData.distance = newDistance;
            }
        }
    }
    return pointData;
};

/*
 * pseudo-distance function for hover effects on areas: inside the region
 * distance is finite (`passVal`), outside it's Infinity.
 *
 * @param {number} v0: signed difference between the current position and the left edge
 * @param {number} v1: signed difference between the current position and the right edge
 * @param {number} passVal: the value to return on success
 */
exports.inbox = function(v0, v1, passVal) {
    return (v0 * v1 < 0 || v0 === 0) ? passVal : Infinity;
};

exports.quadrature = function(dx, dy) {
    return function(di) {
        var x = dx(di);
        var y = dy(di);
        return Math.sqrt(x * x + y * y);
    };
};

/** Fill event data point object for hover and selection.
 *  Invokes _module.eventData if present.
 *
 * N.B. note that point 'index' corresponds to input data array index
 *  whereas 'number' is its post-transform version.
 *
 * If the hovered/selected pt corresponds to an multiple input points
 * (e.g. for histogram and transformed traces), 'pointNumbers` and 'pointIndices'
 * are include in the event data.
 *
 * @param {object} pt
 * @param {object} trace
 * @param {object} cd
 * @return {object}
 */
exports.makeEventData = function(pt, trace, cd) {
    // hover uses 'index', select uses 'pointNumber'
    var pointNumber = 'index' in pt ? pt.index : pt.pointNumber;

    var out = {
        data: trace._input,
        fullData: trace,
        curveNumber: trace.index,
        pointNumber: pointNumber
    };

    if(trace._indexToPoints) {
        var pointIndices = trace._indexToPoints[pointNumber];

        if(pointIndices.length === 1) {
            out.pointIndex = pointIndices[0];
        } else {
            out.pointIndices = pointIndices;
        }
    } else {
        out.pointIndex = pointNumber;
    }

    if(trace._module.eventData) {
        out = trace._module.eventData(out, pt, trace, cd, pointNumber);
    } else {
        if('xVal' in pt) out.x = pt.xVal;
        else if('x' in pt) out.x = pt.x;

        if('yVal' in pt) out.y = pt.yVal;
        else if('y' in pt) out.y = pt.y;

        if(pt.xa) out.xaxis = pt.xa;
        if(pt.ya) out.yaxis = pt.ya;
        if(pt.zLabelVal !== undefined) out.z = pt.zLabelVal;
    }

    exports.appendArrayPointValue(out, trace, pointNumber);

    return out;
};

/** Appends values inside array attributes corresponding to given point number
 *
 * @param {object} pointData : point data object (gets mutated here)
 * @param {object} trace : full trace object
 * @param {number|Array(number)} pointNumber : point number. May be a length-2 array
 *     [row, col] to dig into 2D arrays
 */
exports.appendArrayPointValue = function(pointData, trace, pointNumber) {
    var arrayAttrs = trace._arrayAttrs;

    if(!arrayAttrs) {
        return;
    }

    for(var i = 0; i < arrayAttrs.length; i++) {
        var astr = arrayAttrs[i];
        var key = getPointKey(astr);

        if(pointData[key] === undefined) {
            var val = Lib.nestedProperty(trace, astr).get();
            var pointVal = getPointData(val, pointNumber);

            if(pointVal !== undefined) pointData[key] = pointVal;
        }
    }
};

/**
 * Appends values inside array attributes corresponding to given point number array
 * For use when pointData references a plot entity that arose (or potentially arose)
 * from multiple points in the input data
 *
 * @param {object} pointData : point data object (gets mutated here)
 * @param {object} trace : full trace object
 * @param {Array(number)|Array(Array(number))} pointNumbers : Array of point numbers.
 *     Each entry in the array may itself be a length-2 array [row, col] to dig into 2D arrays
 */
exports.appendArrayMultiPointValues = function(pointData, trace, pointNumbers) {
    var arrayAttrs = trace._arrayAttrs;

    if(!arrayAttrs) {
        return;
    }

    for(var i = 0; i < arrayAttrs.length; i++) {
        var astr = arrayAttrs[i];
        var key = getPointKey(astr);

        if(pointData[key] === undefined) {
            var val = Lib.nestedProperty(trace, astr).get();
            var keyVal = new Array(pointNumbers.length);

            for(var j = 0; j < pointNumbers.length; j++) {
                keyVal[j] = getPointData(val, pointNumbers[j]);
            }
            pointData[key] = keyVal;
        }
    }
};

var pointKeyMap = {
    ids: 'id',
    locations: 'location',
    labels: 'label',
    values: 'value',
    'marker.colors': 'color',
    parents: 'parent'
};

function getPointKey(astr) {
    return pointKeyMap[astr] || astr;
}

function getPointData(val, pointNumber) {
    if(Array.isArray(pointNumber)) {
        if(Array.isArray(val) && Array.isArray(val[pointNumber[0]])) {
            return val[pointNumber[0]][pointNumber[1]];
        }
    } else {
        return val[pointNumber];
    }
}

var xyHoverMode = {
    x: true,
    y: true
};

var unifiedHoverMode = {
    'x unified': true,
    'y unified': true
};

exports.isUnifiedHover = function(hovermode) {
    if(typeof hovermode !== 'string') return false;
    return !!unifiedHoverMode[hovermode];
};

exports.isXYhover = function(hovermode) {
    if(typeof hovermode !== 'string') return false;
    return !!xyHoverMode[hovermode];
};
