// ./t_test/mean.js
import sum from "./sum.js";

/**
 * The mean, _also known as average_,
 * is the sum of all values over the number of values.
 * This is a [measure of central tendency](https://en.wikipedia.org/wiki/Central_tendency):
 * a method of finding a typical or central value of a set of numbers.
 *
 * This runs in `O(n)`, linear time, with respect to the length of the array.
 *
 * @param {Array<number>} x sample of one or more data points
 * @throws {Error} if the length of x is less than one
 * @returns {number} mean
 * @example
 * mean([0, 10]); // => 5
 */
function mean(x) {
    if (x.length === 0) {
        throw new Error("mean requires at least one data point");
    }

    return sum(x) / x.length;
}

export default mean;

// ./t_test/standard_deviation.js
import variance from "./variance.js";

/**
 * The [standard deviation](http://en.wikipedia.org/wiki/Standard_deviation)
 * is the square root of the variance. This is also known as the population
 * standard deviation. It's useful for measuring the amount
 * of variation or dispersion in a set of values.
 *
 * Standard deviation is only appropriate for full-population knowledge: for
 * samples of a population, {@link sampleStandardDeviation} is
 * more appropriate.
 *
 * @param {Array<number>} x input
 * @returns {number} standard deviation
 * @example
 * variance([2, 4, 4, 4, 5, 5, 7, 9]); // => 4
 * standardDeviation([2, 4, 4, 4, 5, 5, 7, 9]); // => 2
 */
function standardDeviation(x) {
    if (x.length === 1) {
        return 0;
    }
    const v = variance(x);
    return Math.sqrt(v);
}

export default standardDeviation;

// ./t_test/sum.js
/**
 * Our default sum is the [Kahan-Babuska algorithm](https://pdfs.semanticscholar.org/1760/7d467cda1d0277ad272deb2113533131dc09.pdf).
 * This method is an improvement over the classical
 * [Kahan summation algorithm](https://en.wikipedia.org/wiki/Kahan_summation_algorithm).
 * It aims at computing the sum of a list of numbers while correcting for
 * floating-point errors. Traditionally, sums are calculated as many
 * successive additions, each one with its own floating-point roundoff. These
 * losses in precision add up as the number of numbers increases. This alternative
 * algorithm is more accurate than the simple way of calculating sums by simple
 * addition.
 *
 * This runs in `O(n)`, linear time, with respect to the length of the array.
 *
 * @param {Array<number>} x input
 * @return {number} sum of all input numbers
 * @example
 * sum([1, 2, 3]); // => 6
 */
function sum(x) {
    // If the array is empty, we needn't bother computing its sum
    if (x.length === 0) {
        return 0;
    }

    // Initializing the sum as the first number in the array
    let sum = x[0];

    // Keeping track of the floating-point error correction
    let correction = 0;

    let transition;

    if (typeof sum !== "number") {
        return Number.NaN;
    }

    for (let i = 1; i < x.length; i++) {
        if (typeof x[i] !== "number") {
            return Number.NaN;
        }
        transition = sum + x[i];

        // Here we need to update the correction in a different fashion
        // if the new absolute value is greater than the absolute sum
        if (Math.abs(sum) >= Math.abs(x[i])) {
            correction += sum - transition + x[i];
        } else {
            correction += x[i] - transition + sum;
        }

        sum = transition;
    }

    // Returning the corrected sum
    return sum + correction;
}

export default sum;

// ./t_test/sum_nth_power_deviations.js
import mean from "./mean.js";

/**
 * The sum of deviations to the Nth power.
 * When n=2 it's the sum of squared deviations.
 * When n=3 it's the sum of cubed deviations.
 *
 * @param {Array<number>} x
 * @param {number} n power
 * @returns {number} sum of nth power deviations
 *
 * @example
 * var input = [1, 2, 3];
 * // since the variance of a set is the mean squared
 * // deviations, we can calculate that with sumNthPowerDeviations:
 * sumNthPowerDeviations(input, 2) / input.length;
 */
function sumNthPowerDeviations(x, n) {
    const meanValue = mean(x);
    let sum = 0;
    let tempValue;
    let i;

    // This is an optimization: when n is 2 (we're computing a number squared),
    // multiplying the number by itself is significantly faster than using
    // the Math.pow method.
    if (n === 2) {
        for (i = 0; i < x.length; i++) {
            tempValue = x[i] - meanValue;
            sum += tempValue * tempValue;
        }
    } else {
        for (i = 0; i < x.length; i++) {
            sum += Math.pow(x[i] - meanValue, n);
        }
    }

    return sum;
}

export default sumNthPowerDeviations;

// ./t_test/t_test.js
import mean from "./mean.js";
import standardDeviation from "./standard_deviation.js";

/**
 * This is to compute [a one-sample t-test](https://en.wikipedia.org/wiki/Student%27s_t-test#One-sample_t-test), comparing the mean
 * of a sample to a known value, x.
 *
 * in this case, we're trying to determine whether the
 * population mean is equal to the value that we know, which is `x`
 * here. Usually the results here are used to look up a
 * [p-value](http://en.wikipedia.org/wiki/P-value), which, for
 * a certain level of significance, will let you determine that the
 * null hypothesis can or cannot be rejected.
 *
 * @param {Array<number>} x sample of one or more numbers
 * @param {number} expectedValue expected value of the population mean
 * @returns {number} value
 * @example
 * tTest([1, 2, 3, 4, 5, 6], 3.385).toFixed(2); // => '0.16'
 */
function tTest(x, expectedValue) {
    // The mean of the sample
    const sampleMean = mean(x);

    // The standard deviation of the sample
    const sd = standardDeviation(x);

    // Square root the length of the sample
    const rootN = Math.sqrt(x.length);

    // returning the t value
    return (sampleMean - expectedValue) / (sd / rootN);
}

export default tTest;

// ./t_test/variance.js
import sumNthPowerDeviations from "./sum_nth_power_deviations.js";

/**
 * The [variance](http://en.wikipedia.org/wiki/Variance)
 * is the sum of squared deviations from the mean.
 *
 * This is an implementation of variance, not sample variance:
 * see the `sampleVariance` method if you want a sample measure.
 *
 * @param {Array<number>} x a population of one or more data points
 * @returns {number} variance: a value greater than or equal to zero.
 * zero indicates that all values are identical.
 * @throws {Error} if x's length is 0
 * @example
 * variance([1, 2, 3, 4, 5, 6]); // => 2.9166666666666665
 */
function variance(x) {
    if (x.length === 0) {
        throw new Error("variance requires at least one data point");
    }

    // Find the mean of squared deviations between the
    // mean value and each value.
    return sumNthPowerDeviations(x, 2) / x.length;
}

export default variance;


// ./validate/map.js
// We use a global `WeakMap` to store class-specific information (such as
// options) instead of storing it as a symbol property on each error class to
// ensure:
//  - This is not exposed to users or plugin authors
//  - This does not change how the error class is printed
// We use a `WeakMap` instead of an object since the key should be the error
// class, not its `name`, because classes might have duplicate names.
export const classesData = new WeakMap()

// The same but for error instances
export const instancesData = new WeakMap()

// ./validate/validate.js
import { classesData } from './map.js'

// We forbid subclasses that are not known, i.e. not passed to
// `ErrorClass.subclass()`
//  - They would not be validated at load time
//  - The class would not be normalized until its first instantiation
//     - E.g. its `prototype.name` might be missing
//  - The list of `ErrorClasses` would be potentially incomplete
//     - E.g. `ErrorClass.parse()` would not be able to parse an error class
//       until its first instantiation
// This usually happens if a class was:
//  - Not passed to the `custom` option of `*Error.subclass()`
//  - But was extended from a known class
export const validateSubclass = (ErrorClass) => {
  if (classesData.has(ErrorClass)) {
    return
  }

  const { name } = ErrorClass
  const { name: parentName } = Object.getPrototypeOf(ErrorClass)
  throw new Error(
    `"new ${name}()" must not be directly called.
This error class should be created like this instead:
  export const ${name} = ${parentName}.subclass('${name}')`,
  )
}


// ./zone/MathUtil.js
const PI = 3.1415926;
const INFINITY = Infinity;

const MathUtil = {
  PI: PI,
  PIx2: PI * 2,
  PI_2: PI / 2,
  PI_180: PI / 180,
  N180_PI: 180 / PI,
  Infinity: -999,

  isInfinity(num) {
    return num === this.Infinity || num === INFINITY;
  },

  randomAToB(a, b, isInt = false) {
    if (!isInt) return a + Math.random() * (b - a);
    else return ((Math.random() * (b - a)) >> 0) + a;
  },

  randomFloating(center, f, isInt) {
    return this.randomAToB(center - f, center + f, isInt);
  },

  randomColor() {
    return "#" + ("00000" + ((Math.random() * 0x1000000) << 0).toString(16)).slice(-6);
  },

  randomZone(display) {},

  floor(num, k = 4) {
    const digits = Math.pow(10, k);
    return Math.floor(num * digits) / digits;
  },

  degreeTransform(a) {
    return (a * PI) / 180;
  },

  toColor16(num) {
    return `#${num.toString(16)}`;
  }
};

export default MathUtil;

// ./zone/Vector2D.js
import MathUtil from "./MathUtil";

export default class Vector2D {
  constructor(x, y) {
    this.x = x || 0;
    this.y = y || 0;
  }

  set(x, y) {
    this.x = x;
    this.y = y;
    return this;
  }

  setX(x) {
    this.x = x;
    return this;
  }

  setY(y) {
    this.y = y;
    return this;
  }

  getGradient() {
    if (this.x !== 0) return Math.atan2(this.y, this.x);
    else if (this.y > 0) return MathUtil.PI_2;
    else if (this.y < 0) return -MathUtil.PI_2;
  }

  copy(v) {
    this.x = v.x;
    this.y = v.y;

    return this;
  }

  add(v, w) {
    if (w !== undefined) {
      return this.addVectors(v, w);
    }

    this.x += v.x;
    this.y += v.y;

    return this;
  }

  addXY(a, b) {
    this.x += a;
    this.y += b;

    return this;
  }

  addVectors(a, b) {
    this.x = a.x + b.x;
    this.y = a.y + b.y;

    return this;
  }

  sub(v, w) {
    if (w !== undefined) {
      return this.subVectors(v, w);
    }

    this.x -= v.x;
    this.y -= v.y;

    return this;
  }

  subVectors(a, b) {
    this.x = a.x - b.x;
    this.y = a.y - b.y;

    return this;
  }

  divideScalar(s) {
    if (s !== 0) {
      this.x /= s;
      this.y /= s;
    } else {
      this.set(0, 0);
    }

    return this;
  }

  multiplyScalar(s) {
    this.x *= s;
    this.y *= s;

    return this;
  }

  negate() {
    return this.multiplyScalar(-1);
  }

  dot(v) {
    return this.x * v.x + this.y * v.y;
  }

  lengthSq() {
    return this.x * this.x + this.y * this.y;
  }

  length() {
    return Math.sqrt(this.x * this.x + this.y * this.y);
  }

  normalize() {
    return this.divideScalar(this.length());
  }

  distanceTo(v) {
    return Math.sqrt(this.distanceToSquared(v));
  }

  rotate(tha) {
    const x = this.x;
    const y = this.y;

    this.x = x * Math.cos(tha) + y * Math.sin(tha);
    this.y = -x * Math.sin(tha) + y * Math.cos(tha);

    return this;
  }

  distanceToSquared(v) {
    const dx = this.x - v.x;
    const dy = this.y - v.y;

    return dx * dx + dy * dy;
  }

  lerp(v, alpha) {
    this.x += (v.x - this.x) * alpha;
    this.y += (v.y - this.y) * alpha;

    return this;
  }

  equals(v) {
    return v.x === this.x && v.y === this.y;
  }

  clear() {
    this.x = 0.0;
    this.y = 0.0;
    return this;
  }

  clone() {
    return new Vector2D(this.x, this.y);
  }
}

// ./zone/Zone.js
import Vector2D from "./Vector2D";

export default class Zone {
  constructor() {
    this.vector = new Vector2D(0, 0);
    this.random = 0;
    this.crossType = "dead";
    this.alert = true;
  }

  getPosition() {}

  crossing(particle) {}

  destroy() {
    this.vector = null;
  }
}