// Copyright 2007 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.structs.AvlTreeTest');
goog.setTestOnly('goog.structs.AvlTreeTest');

goog.require('goog.array');
goog.require('goog.structs.AvlTree');
goog.require('goog.testing.jsunit');


/**
 * This test verifies that we can insert strings into the AvlTree and have
 * them be stored and sorted correctly by the default comparator.
 */
function testInsertsWithDefaultComparator() {
  var tree = new goog.structs.AvlTree();
  var values = ['bill', 'blake', 'elliot', 'jacob', 'john', 'myles', 'ted'];

  // Insert strings into tree out of order
  tree.add(values[4]);
  tree.add(values[3]);
  tree.add(values[0]);
  tree.add(values[6]);
  tree.add(values[5]);
  tree.add(values[1]);
  tree.add(values[2]);

  // Verify strings are stored in sorted order
  var i = 0;
  tree.inOrderTraverse(function(value) {
    assertEquals(values[i], value);
    i += 1;
  });
  assertEquals(i, values.length);

  // Verify that no nodes are visited if the start value is larger than all
  // values
  tree.inOrderTraverse(function(value) { fail(); }, 'zed');

  // Verify strings are stored in sorted order
  i = values.length;
  tree.reverseOrderTraverse(function(value) {
    i--;
    assertEquals(values[i], value);
  });
  assertEquals(i, 0);

  // Verify that no nodes are visited if the start value is smaller than all
  // values
  tree.reverseOrderTraverse(function(value) { fail(); }, 'aardvark');
}


/**
 * This test verifies that we can insert strings into and remove strings from
 * the AvlTree and have the only the non-removed values be stored and sorted
 * correctly by the default comparator.
 */
function testRemovesWithDefaultComparator() {
  var tree = new goog.structs.AvlTree();
  var values = ['bill', 'blake', 'elliot', 'jacob', 'john', 'myles', 'ted'];

  // Insert strings into tree out of order
  tree.add('frodo');
  tree.add(values[4]);
  tree.add(values[3]);
  tree.add(values[0]);
  tree.add(values[6]);
  tree.add('samwise');
  tree.add(values[5]);
  tree.add(values[1]);
  tree.add(values[2]);
  tree.add('pippin');

  // Remove strings from tree
  assertEquals(tree.remove('samwise'), 'samwise');
  assertEquals(tree.remove('pippin'), 'pippin');
  assertEquals(tree.remove('frodo'), 'frodo');
  assertEquals(tree.remove('merry'), null);


  // Verify strings are stored in sorted order
  var i = 0;
  tree.inOrderTraverse(function(value) {
    assertEquals(values[i], value);
    i += 1;
  });
  assertEquals(i, values.length);
}


/**
 * This test verifies that we can insert values into and remove values from
 * the AvlTree and have them be stored and sorted correctly by a custom
 * comparator.
 */
function testInsertsAndRemovesWithCustomComparator() {
  var tree = new goog.structs.AvlTree(function(a, b) { return a - b; });

  var NUM_TO_INSERT = 37;
  var valuesToRemove = [1, 0, 6, 7, 36];

  // Insert ints into tree out of order
  var values = [];
  for (var i = 0; i < NUM_TO_INSERT; i += 1) {
    tree.add(i);
    values.push(i);
  }

  for (var i = 0; i < valuesToRemove.length; i += 1) {
    assertEquals(tree.remove(valuesToRemove[i]), valuesToRemove[i]);
    goog.array.remove(values, valuesToRemove[i]);
  }
  assertEquals(tree.remove(-1), null);
  assertEquals(tree.remove(37), null);

  // Verify strings are stored in sorted order
  var i = 0;
  tree.inOrderTraverse(function(value) {
    assertEquals(values[i], value);
    i += 1;
  });
  assertEquals(i, values.length);
}


/**
 * This test verifies that we can insert values into and remove values from
 * the AvlTree and have it maintain the AVL-Tree upperbound on its height.
 */
function testAvlTreeHeight() {
  var tree = new goog.structs.AvlTree(function(a, b) { return a - b; });

  var NUM_TO_INSERT = 2000;
  var NUM_TO_REMOVE = 500;

  // Insert ints into tree out of order
  for (var i = 0; i < NUM_TO_INSERT; i += 1) {
    tree.add(i);
  }

  // Remove valuse
  for (var i = 0; i < NUM_TO_REMOVE; i += 1) {
    tree.remove(i);
  }

  assertTrue(
      tree.getHeight() <=
      1.4405 * (Math.log(NUM_TO_INSERT - NUM_TO_REMOVE + 2) / Math.log(2)) -
          1.3277);
}


/**
 * This test verifies that we can insert values into and remove values from
 * the AvlTree and have its contains method correctly determine the values it
 * is contains.
 */
function testAvlTreeContains() {
  var tree = new goog.structs.AvlTree();
  var values = ['bill', 'blake', 'elliot', 'jacob', 'john', 'myles', 'ted'];

  // Insert strings into tree out of order
  tree.add('frodo');
  tree.add(values[4]);
  tree.add(values[3]);
  tree.add(values[0]);
  tree.add(values[6]);
  tree.add('samwise');
  tree.add(values[5]);
  tree.add(values[1]);
  tree.add(values[2]);
  tree.add('pippin');

  // Remove strings from tree
  assertEquals(tree.remove('samwise'), 'samwise');
  assertEquals(tree.remove('pippin'), 'pippin');
  assertEquals(tree.remove('frodo'), 'frodo');

  for (var i = 0; i < values.length; i += 1) {
    assertTrue(tree.contains(values[i]));
  }
  assertFalse(tree.contains('samwise'));
  assertFalse(tree.contains('pippin'));
  assertFalse(tree.contains('frodo'));
}


/**
 * This test verifies that we can insert values into and remove values from
 * the AvlTree and have its indexOf method correctly determine the in-order
 * index of the values it contains.
 */
function testAvlTreeIndexOf() {
  var tree = new goog.structs.AvlTree();
  var values = ['bill', 'blake', 'elliot', 'jacob', 'john', 'myles', 'ted'];

  // Insert strings into tree out of order
  tree.add('frodo');
  tree.add(values[4]);
  tree.add(values[3]);
  tree.add(values[0]);
  tree.add(values[6]);
  tree.add('samwise');
  tree.add(values[5]);
  tree.add(values[1]);
  tree.add(values[2]);
  tree.add('pippin');

  // Remove strings from tree
  assertEquals('samwise', tree.remove('samwise'));
  assertEquals('pippin', tree.remove('pippin'));
  assertEquals('frodo', tree.remove('frodo'));

  for (var i = 0; i < values.length; i += 1) {
    assertEquals(i, tree.indexOf(values[i]));
  }
  assertEquals(-1, tree.indexOf('samwise'));
  assertEquals(-1, tree.indexOf('pippin'));
  assertEquals(-1, tree.indexOf('frodo'));
}


/**
 * This test verifies that we can insert values into and remove values from
 * the AvlTree and have its minValue and maxValue routines return the correct
 * min and max values contained by the tree.
 */
function testMinAndMaxValues() {
  var tree = new goog.structs.AvlTree(function(a, b) { return a - b; });

  var NUM_TO_INSERT = 2000;
  var NUM_TO_REMOVE = 500;

  // Insert ints into tree out of order
  for (var i = 0; i < NUM_TO_INSERT; i += 1) {
    tree.add(i);
  }

  // Remove valuse
  for (var i = 0; i < NUM_TO_REMOVE; i += 1) {
    tree.remove(i);
  }

  assertEquals(tree.getMinimum(), NUM_TO_REMOVE);
  assertEquals(tree.getMaximum(), NUM_TO_INSERT - 1);
}


/**
 * This test verifies that we can insert values into and remove values from
 * the AvlTree and traverse the tree in reverse order using the
 * reverseOrderTraverse routine.
 */
function testReverseOrderTraverse() {
  var tree = new goog.structs.AvlTree(function(a, b) { return a - b; });

  var NUM_TO_INSERT = 2000;
  var NUM_TO_REMOVE = 500;

  // Insert ints into tree out of order
  for (var i = 0; i < NUM_TO_INSERT; i += 1) {
    tree.add(i);
  }

  // Remove valuse
  for (var i = 0; i < NUM_TO_REMOVE; i += 1) {
    tree.remove(i);
  }

  var i = NUM_TO_INSERT - 1;
  tree.reverseOrderTraverse(function(value) {
    assertEquals(value, i);
    i -= 1;
  });
  assertEquals(i, NUM_TO_REMOVE - 1);
}


/**
 * Verifies correct behavior of getCount(). See http://b/4347755
 */
function testGetCountBehavior() {
  var tree = new goog.structs.AvlTree();
  tree.add(1);
  tree.remove(1);
  assertEquals(0, tree.getCount());

  var values = ['bill', 'blake', 'elliot', 'jacob', 'john', 'myles', 'ted'];

  // Insert strings into tree out of order
  tree.add('frodo');
  tree.add(values[4]);
  tree.add(values[3]);
  tree.add(values[0]);
  tree.add(values[6]);
  tree.add('samwise');
  tree.add(values[5]);
  tree.add(values[1]);
  tree.add(values[2]);
  tree.add('pippin');
  assertEquals(10, tree.getCount());
  assertEquals(
      tree.root_.left.count + tree.root_.right.count + 1, tree.getCount());

  // Remove strings from tree
  assertEquals('samwise', tree.remove('samwise'));
  assertEquals('pippin', tree.remove('pippin'));
  assertEquals('frodo', tree.remove('frodo'));
  assertEquals(null, tree.remove('merry'));
  assertEquals(7, tree.getCount());

  assertEquals(
      tree.root_.left.count + tree.root_.right.count + 1, tree.getCount());
}


/**
 * This test verifies that getKthOrder gets the correct value.
 */
function testGetKthOrder() {
  var tree = new goog.structs.AvlTree(function(a, b) { return a - b; });

  var NUM_TO_INSERT = 2000;
  var NUM_TO_REMOVE = 500;

  // Insert ints into tree out of order
  for (var i = 0; i < NUM_TO_INSERT; i += 1) {
    tree.add(i);
  }

  // Remove values.
  for (var i = 0; i < NUM_TO_REMOVE; i += 1) {
    tree.remove(i);
  }
  for (var k = 0; k < tree.getCount(); ++k) {
    assertEquals(NUM_TO_REMOVE + k, tree.getKthValue(k));
  }
}
