'use strict';

var isNumeric = require('fast-isnumeric');
var isArrayOrTypedArray = require('./array').isArrayOrTypedArray;

/**
 * aggNums() returns the result of an aggregate function applied to an array of
 * values, where non-numerical values have been tossed out.
 *
 * @param {function} f - aggregation function (e.g., Math.min)
 * @param {Number} v - initial value (continuing from previous calls)
 *      if there's no continuing value, use null for selector-type
 *      functions (max,min), or 0 for summations
 * @param {Array} a - array to aggregate (may be nested, we will recurse,
 *                    but all elements must have the same dimension)
 * @param {Number} len - maximum length of a to aggregate
 * @return {Number} - result of f applied to a starting from v
 */
exports.aggNums = function(f, v, a, len) {
    var i,
        b;
    if(!len || len > a.length) len = a.length;
    if(!isNumeric(v)) v = false;
    if(isArrayOrTypedArray(a[0])) {
        b = new Array(len);
        for(i = 0; i < len; i++) b[i] = exports.aggNums(f, v, a[i]);
        a = b;
    }

    for(i = 0; i < len; i++) {
        if(!isNumeric(v)) v = a[i];
        else if(isNumeric(a[i])) v = f(+v, +a[i]);
    }
    return v;
};

/**
 * mean & std dev functions using aggNums, so it handles non-numerics nicely
 * even need to use aggNums instead of .length, to toss out non-numerics
 */
exports.len = function(data) {
    return exports.aggNums(function(a) { return a + 1; }, 0, data);
};

exports.mean = function(data, len) {
    if(!len) len = exports.len(data);
    return exports.aggNums(function(a, b) { return a + b; }, 0, data) / len;
};

exports.geometricMean = function(data, len) {
    if(!len) len = exports.len(data);
    return Math.pow(exports.aggNums(function(a, b) { return a * b; }, 1, data), 1 / len);
};

exports.midRange = function(numArr) {
    if(numArr === undefined || numArr.length === 0) return undefined;
    return (exports.aggNums(Math.max, null, numArr) + exports.aggNums(Math.min, null, numArr)) / 2;
};

exports.variance = function(data, len, mean) {
    if(!len) len = exports.len(data);
    if(!isNumeric(mean)) mean = exports.mean(data, len);

    return exports.aggNums(function(a, b) {
        return a + Math.pow(b - mean, 2);
    }, 0, data) / len;
};

exports.stdev = function(data, len, mean) {
    return Math.sqrt(exports.variance(data, len, mean));
};

/**
 * median of a finite set of numbers
 * reference page: https://en.wikipedia.org/wiki/Median#Finite_set_of_numbers
**/
exports.median = function(data) {
    var b = data.slice().sort();
    return exports.interp(b, 0.5);
};

/**
 * interp() computes a percentile (quantile) for a given distribution.
 * We interpolate the distribution (to compute quantiles, we follow method #10 here:
 * http://jse.amstat.org/v14n3/langford.html).
 * Typically the index or rank (n * arr.length) may be non-integer.
 * For reference: ends are clipped to the extreme values in the array;
 * For box plots: index you get is half a point too high (see
 * http://en.wikipedia.org/wiki/Percentile#Nearest_rank) but note that this definition
 * indexes from 1 rather than 0, so we subtract 1/2 (instead of add).
 *
 * @param {Array} arr - This array contains the values that make up the distribution.
 * @param {Number} n - Between 0 and 1, n = p/100 is such that we compute the p^th percentile.
 * For example, the 50th percentile (or median) corresponds to n = 0.5
 * @return {Number} - percentile
 */
exports.interp = function(arr, n) {
    if(!isNumeric(n)) throw 'n should be a finite number';
    n = n * arr.length - 0.5;
    if(n < 0) return arr[0];
    if(n > arr.length - 1) return arr[arr.length - 1];
    var frac = n % 1;
    return frac * arr[Math.ceil(n)] + (1 - frac) * arr[Math.floor(n)];
};
