const ZSCORE_CRITICAL_THRESHOLD = 1.96; // 95-percentile
const ZSCORE_NINETYNINTH = 2.326347875; // 99-percentile
const ONE_DIV_SQRT_2PI = 0.3989422804014327; // V8 float of 1/SQRT(2 * PI)
const MAX_P_PRECISION = Math.exp(-16); // Rought estimation of V8 precision, -16 is 1.1253517471925912e-7
const MIN_P = -6.44357455534; // v8 float 0.0...0
const MAX_P = 6.44357455534; // v8 float 1.0...0

const getMean = (numArr) => {
  let total = 0;
  let length = numArr.length; // store local to reduce potential prop lookups

  for(let i = 0; i < length; i++) {
    total += numArr[i];
  }

  return total / length;
};

const getStandardDeviation = (numArr, mean) => {
  return Math.sqrt(numArr.reduce((sq, n) => (
    sq + Math.pow(n - mean, 2)
  ), 0) / (numArr.length - 1));
};

const getInformationFromValues = (numArr) => {
  let mean = getMean(numArr);

  return {
    mean,
    standardDeviation: getStandardDeviation(numArr, mean),
  }
};

const getZScore = (value, mean, sDeviation) => ( sDeviation !== 0 ? (value - mean) / sDeviation : 0 );

const getFastPValue = (zScore) => {
  if(zScore <= MIN_P) {
   return 0;
  }
  if(zScore >= MAX_P) {
   return 1;
  }

  let factorialK = 1;
  let k = 0;
  let sum = 0;
  let term = 1;

  while(Math.abs(term) > MAX_P_PRECISION) {
    term = ONE_DIV_SQRT_2PI * Math.pow(-1 , k) * Math.pow(zScore , k) / (2 * k + 1) / Math.pow(2 , k) * Math.pow(zScore, k + 1) / factorialK;
    sum += term;
    k++;
    factorialK *= k;
  }
  sum += 0.5;

  return sum;
};


const getWeight = (zScore, pValue) => (zScore * pValue);

module.exports = {
  getInformationFromValues,
  getZScore,
  getFastPValue,
  getWeight,
};