// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('goog.mathTest');
goog.setTestOnly('goog.mathTest');

goog.require('goog.math');
goog.require('goog.testing.jsunit');

function testRandomInt() {
  assertEquals(0, goog.math.randomInt(0));
  assertEquals(0, goog.math.randomInt(1));

  var r = goog.math.randomInt(3);
  assertTrue(0 <= r && r < 3);
}

function testUniformRandom() {
  assertEquals(5.2, goog.math.uniformRandom(5.2, 5.2));
  assertEquals(-6, goog.math.uniformRandom(-6, -6));

  var r = goog.math.uniformRandom(-0.5, 0.5);
  assertTrue(-0.5 <= r && r < 0.5);
}

function testClamp() {
  assertEquals(3, goog.math.clamp(3, -5, 5));
  assertEquals(5, goog.math.clamp(5, -5, 5));
  assertEquals(-5, goog.math.clamp(-5, -5, 5));

  assertEquals(-5, goog.math.clamp(-22, -5, 5));
  assertEquals(5, goog.math.clamp(6, -5, 5));
}

function testModulo() {
  assertEquals(0, goog.math.modulo(256, 8));

  assertEquals(7, goog.math.modulo(7, 8));
  assertEquals(7, goog.math.modulo(23, 8));
  assertEquals(7, goog.math.modulo(-1, 8));

  // Safari 5.1.7 has a bug in its JS engine where modulo is computed
  // incorrectly when using variables. We avoid using
  // goog.testing.ExpectedFailure here since it pulls in a bunch of
  // extra dependencies for maintaining a DOM console.
  var a = 1;
  var b = -5;
  if (a % b === 1 % -5) {
    assertEquals(-4, goog.math.modulo(1, -5));
    assertEquals(-4, goog.math.modulo(6, -5));
  }
  assertEquals(-4, goog.math.modulo(-4, -5));
}

function testLerp() {
  assertEquals(0, goog.math.lerp(0, 0, 0));
  assertEquals(3, goog.math.lerp(0, 6, 0.5));
  assertEquals(3, goog.math.lerp(-1, 1, 2));
}

function testNearlyEquals() {
  assertTrue(
      'Numbers inside default tolerance should be equal',
      goog.math.nearlyEquals(0.000001, 0.000001001));
  assertFalse(
      'Numbers outside default tolerance should be unequal',
      goog.math.nearlyEquals(0.000001, 0.000003));
  assertTrue(
      'Numbers inside custom tolerance should be equal',
      goog.math.nearlyEquals(0.001, 0.002, 0.1));
  assertFalse(
      'Numbers outside custom tolerance should be unequal',
      goog.math.nearlyEquals(0.001, -0.1, 0.1));
  assertTrue(
      'Integer tolerance greater than one should succeed',
      goog.math.nearlyEquals(87, 85, 3));
}

function testStandardAngleInRadians() {
  assertRoughlyEquals(0, goog.math.standardAngleInRadians(2 * Math.PI), 1e-10);
  assertRoughlyEquals(
      Math.PI, goog.math.standardAngleInRadians(Math.PI), 1e-10);
  assertRoughlyEquals(
      Math.PI, goog.math.standardAngleInRadians(-1 * Math.PI), 1e-10);
  assertRoughlyEquals(
      Math.PI / 2, goog.math.standardAngleInRadians(-1.5 * Math.PI), 1e-10);
  assertRoughlyEquals(
      Math.PI, goog.math.standardAngleInRadians(5 * Math.PI), 1e-10);
  assertEquals(0.01, goog.math.standardAngleInRadians(0.01));
  assertEquals(0, goog.math.standardAngleInRadians(0));
}

function testStandardAngle() {
  assertEquals(359.5, goog.math.standardAngle(-360.5));
  assertEquals(0, goog.math.standardAngle(-360));
  assertEquals(359.5, goog.math.standardAngle(-0.5));
  assertEquals(0, goog.math.standardAngle(0));
  assertEquals(0.5, goog.math.standardAngle(0.5));
  assertEquals(0, goog.math.standardAngle(360));
  assertEquals(1, goog.math.standardAngle(721));
}

function testToRadians() {
  assertEquals(-Math.PI, goog.math.toRadians(-180));
  assertEquals(0, goog.math.toRadians(0));
  assertEquals(Math.PI, goog.math.toRadians(180));
}

function testToDegrees() {
  assertEquals(-180, goog.math.toDegrees(-Math.PI));
  assertEquals(0, goog.math.toDegrees(0));
  assertEquals(180, goog.math.toDegrees(Math.PI));
}

function testAngleDx() {
  assertRoughlyEquals(0, goog.math.angleDx(0, 0), 1e-10);
  assertRoughlyEquals(0, goog.math.angleDx(90, 0), 1e-10);
  assertRoughlyEquals(100, goog.math.angleDx(0, 100), 1e-10);
  assertRoughlyEquals(0, goog.math.angleDx(90, 100), 1e-10);
  assertRoughlyEquals(-100, goog.math.angleDx(180, 100), 1e-10);
  assertRoughlyEquals(0, goog.math.angleDx(270, 100), 1e-10);
}

function testAngleDy() {
  assertRoughlyEquals(0, goog.math.angleDy(0, 0), 1e-10);
  assertRoughlyEquals(0, goog.math.angleDy(90, 0), 1e-10);
  assertRoughlyEquals(0, goog.math.angleDy(0, 100), 1e-10);
  assertRoughlyEquals(100, goog.math.angleDy(90, 100), 1e-10);
  assertRoughlyEquals(0, goog.math.angleDy(180, 100), 1e-10);
  assertRoughlyEquals(-100, goog.math.angleDy(270, 100), 1e-10);
}

function testAngle() {
  assertRoughlyEquals(0, goog.math.angle(10, 10, 20, 10), 1e-10);
  assertRoughlyEquals(90, goog.math.angle(10, 10, 10, 20), 1e-10);
  assertRoughlyEquals(225, goog.math.angle(10, 10, 0, 0), 1e-10);
  assertRoughlyEquals(270, goog.math.angle(10, 10, 10, 0), 1e-10);

  // 0 is the conventional result, but mathematically this is undefined.
  assertEquals(0, goog.math.angle(10, 10, 10, 10));
}

function testAngleDifference() {
  assertEquals(10, goog.math.angleDifference(30, 40));
  assertEquals(-10, goog.math.angleDifference(40, 30));
  assertEquals(180, goog.math.angleDifference(10, 190));
  assertEquals(180, goog.math.angleDifference(190, 10));
  assertEquals(20, goog.math.angleDifference(350, 10));
  assertEquals(-20, goog.math.angleDifference(10, 350));
  assertEquals(100, goog.math.angleDifference(350, 90));
  assertEquals(-80, goog.math.angleDifference(350, 270));
  assertEquals(0, goog.math.angleDifference(15, 15));
}

function testSign() {
  assertEquals(0, goog.math.sign(0));
  assertEquals(-1, goog.math.sign(-3));
  assertEquals(1, goog.math.sign(3));
  assertEquals(1, goog.math.sign(0.0001));
  assertEquals(-1, goog.math.sign(-0.0001));
  assertEquals(1, goog.math.sign(3141592653589793));
}

function testSignOfSpecialFloatValues() {
  assertEquals(-1, goog.math.sign(-Infinity));
  assertEquals(1, goog.math.sign(Infinity));
  assertNaN(goog.math.sign(NaN));
  assertEquals(0, goog.math.sign(0));
  assertFalse(goog.math.isNegativeZero(goog.math.sign(0)));
  assertEquals(0, goog.math.sign(-0));
  assertTrue(goog.math.isNegativeZero(goog.math.sign(-0)));
}

function testLongestCommonSubsequence() {
  var func = goog.math.longestCommonSubsequence;

  assertArrayEquals([2], func([1, 2], [2, 1]));
  assertArrayEquals([1, 2], func([1, 2, 5], [2, 1, 2]));
  assertArrayEquals(
      [1, 2, 3, 4, 5],
      func([1, 0, 2, 3, 8, 4, 9, 5], [8, 1, 2, 4, 3, 6, 4, 5]));
  assertArrayEquals([1, 1, 1, 1, 1], func([1, 1, 1, 1, 1], [1, 1, 1, 1, 1]));
  assertArrayEquals([5], func([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]));
  assertArrayEquals(
      [1, 8, 11], func([1, 6, 8, 11, 13], [1, 3, 5, 8, 9, 11, 12]));
}

function testLongestCommonSubsequenceWithCustomComparator() {
  var func = goog.math.longestCommonSubsequence;

  var compareFn = function(a, b) { return a.field == b.field; };

  var a1 = {field: 'a1', field2: 'hello'};
  var a2 = {field: 'a2', field2: 33};
  var a3 = {field: 'a3'};
  var a4 = {field: 'a3'};

  assertArrayEquals([a1, a2], func([a1, a2, a3], [a3, a1, a2], compareFn));
  assertArrayEquals([a1, a3], func([a1, a3], [a1, a4], compareFn));
  // testing the same arrays without compare function
  assertArrayEquals([a1], func([a1, a3], [a1, a4]));
}

function testLongestCommonSubsequenceWithCustomCollector() {
  var func = goog.math.longestCommonSubsequence;

  var collectorFn = function(a, b) { return b; };

  assertArrayEquals(
      [1, 2, 4, 6, 7], func(
                           [1, 0, 2, 3, 8, 4, 9, 5], [8, 1, 2, 4, 3, 6, 4, 5],
                           null, collectorFn));
}

function testSum() {
  assertEquals(
      'sum() must return 0 if there are no arguments', 0, goog.math.sum());
  assertEquals(
      'sum() must return its argument if there is only one', 17,
      goog.math.sum(17));
  assertEquals(
      'sum() must handle positive integers', 10, goog.math.sum(1, 2, 3, 4));
  assertEquals(
      'sum() must handle real numbers', -2.5, goog.math.sum(1, -2, 3, -4.5));
  assertTrue(
      'sum() must return NaN if one of the arguments isn\'t numeric',
      isNaN(goog.math.sum(1, 2, 'foo', 3)));
}

function testAverage() {
  assertTrue(
      'average() must return NaN if there are no arguments',
      isNaN(goog.math.average()));
  assertEquals(
      'average() must return its argument if there is only one', 17,
      goog.math.average(17));
  assertEquals(
      'average() must handle positive integers', 3,
      goog.math.average(1, 2, 3, 4, 5));
  assertEquals(
      'average() must handle real numbers', -0.625,
      goog.math.average(1, -2, 3, -4.5));
  assertTrue(
      'average() must return NaN if one of the arguments isn\'t ' +
          'numeric',
      isNaN(goog.math.average(1, 2, 'foo', 3)));
}

function testSampleVariance() {
  assertEquals(
      'sampleVariance() must return 0 if there are no samples', 0,
      goog.math.sampleVariance());
  assertEquals(
      'sampleVariance() must return 0 if there is only one ' +
          'sample',
      0, goog.math.sampleVariance(17));
  assertRoughlyEquals(
      'sampleVariance() must handle positive integers', 48,
      goog.math.sampleVariance(3, 7, 7, 19), 0.0001);
  assertRoughlyEquals(
      'sampleVariance() must handle real numbers', 12.0138,
      goog.math.sampleVariance(1.23, -2.34, 3.14, -4.56), 0.0001);
}

function testStandardDeviation() {
  assertEquals(
      'standardDeviation() must return 0 if there are no samples', 0,
      goog.math.standardDeviation());
  assertEquals(
      'standardDeviation() must return 0 if there is only one ' +
          'sample',
      0, goog.math.standardDeviation(17));
  assertRoughlyEquals(
      'standardDeviation() must handle positive integers', 6.9282,
      goog.math.standardDeviation(3, 7, 7, 19), 0.0001);
  assertRoughlyEquals(
      'standardDeviation() must handle real numbers', 3.4660,
      goog.math.standardDeviation(1.23, -2.34, 3.14, -4.56), 0.0001);
}

function testIsInt() {
  assertFalse(goog.math.isInt(12345.67));
  assertFalse(goog.math.isInt(0.123));
  assertFalse(goog.math.isInt(.1));
  assertFalse(goog.math.isInt(-23.43));
  assertFalse(goog.math.isInt(-.1));
  assertFalse(goog.math.isInt(1e-1));
  assertTrue(goog.math.isInt(1));
  assertTrue(goog.math.isInt(0));
  assertTrue(goog.math.isInt(-2));
  assertTrue(goog.math.isInt(-2.0));
  assertTrue(goog.math.isInt(10324231));
  assertTrue(goog.math.isInt(1.));
  assertTrue(goog.math.isInt(1e3));
}

function testIsFiniteNumber() {
  assertFalse(goog.math.isFiniteNumber(NaN));
  assertFalse(goog.math.isFiniteNumber(-Infinity));
  assertFalse(goog.math.isFiniteNumber(+Infinity));
  assertTrue(goog.math.isFiniteNumber(0));
  assertTrue(goog.math.isFiniteNumber(1));
  assertTrue(goog.math.isFiniteNumber(Math.PI));
}

function testIsNegativeZero() {
  assertFalse(goog.math.isNegativeZero(0));
  assertTrue(goog.math.isNegativeZero(-0));
  assertFalse(goog.math.isNegativeZero(1));
  assertFalse(goog.math.isNegativeZero(-1));
  assertFalse(goog.math.isNegativeZero(-Number.MIN_VALUE));
}

function testLog10Floor() {
  // The greatest floating point number that is less than 1.
  var oneMinusEpsilon = 1 - Math.pow(2, -53);
  for (var i = -30; i <= 30; i++) {
    assertEquals(i, goog.math.log10Floor(parseFloat('1e' + i)));
    assertEquals(
        i - 1, goog.math.log10Floor(parseFloat('1e' + i) * oneMinusEpsilon));
  }
  assertEquals(-Infinity, goog.math.log10Floor(0));
  assertTrue(isNaN(goog.math.log10Floor(-1)));
}

function testSafeFloor() {
  assertEquals(0, goog.math.safeFloor(0));
  assertEquals(0, goog.math.safeFloor(1e-15));
  assertEquals(0, goog.math.safeFloor(-1e-15));
  assertEquals(-1, goog.math.safeFloor(-3e-15));
  assertEquals(4, goog.math.safeFloor(5 - 3e-15));
  assertEquals(5, goog.math.safeFloor(5 - 1e-15));
  assertEquals(-5, goog.math.safeFloor(-5 - 1e-15));
  assertEquals(-6, goog.math.safeFloor(-5 - 3e-15));
  assertEquals(3, goog.math.safeFloor(2.91, 0.1));
  assertEquals(2, goog.math.safeFloor(2.89, 0.1));
  // Tests some real life examples with the default epsilon value.
  assertEquals(0, goog.math.safeFloor(Math.log(1000) / Math.LN10 - 3));
  assertEquals(21, goog.math.safeFloor(Math.log(1e+21) / Math.LN10));
}

function testSafeCeil() {
  assertEquals(0, goog.math.safeCeil(0));
  assertEquals(0, goog.math.safeCeil(1e-15));
  assertEquals(0, goog.math.safeCeil(-1e-15));
  assertEquals(1, goog.math.safeCeil(3e-15));
  assertEquals(6, goog.math.safeCeil(5 + 3e-15));
  assertEquals(5, goog.math.safeCeil(5 + 1e-15));
  assertEquals(-4, goog.math.safeCeil(-5 + 3e-15));
  assertEquals(-5, goog.math.safeCeil(-5 + 1e-15));
  assertEquals(3, goog.math.safeCeil(3.09, 0.1));
  assertEquals(4, goog.math.safeCeil(3.11, 0.1));
}
