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

goog.require('goog.iter');
goog.require('goog.structs');
goog.require('goog.structs.Map');
goog.require('goog.testing.jsunit');

function stringifyMap(m) {
  var keys = goog.structs.getKeys(m);
  var s = '';
  for (var i = 0; i < keys.length; i++) {
    s += keys[i] + m[keys[i]];
  }
  return s;
}

function getMap() {
  var m = new goog.structs.Map;
  m.set('a', 0);
  m.set('b', 1);
  m.set('c', 2);
  m.set('d', 3);
  return m;
}

function testGetCount() {
  var m = getMap();
  assertEquals('count, should be 4', m.getCount(), 4);
  m.remove('d');
  assertEquals('count, should be 3', m.getCount(), 3);
}

function testKeys() {
  var m = getMap();
  assertEquals(
      'getKeys, The keys should be a,b,c', m.getKeys().join(','), 'a,b,c,d');
}

function testValues() {
  var m = getMap();
  assertEquals(
      'getValues, The values should be 0,1,2', m.getValues().join(','),
      '0,1,2,3');
}

function testContainsKey() {
  var m = getMap();
  assertTrue("containsKey, Should contain the 'a' key", m.containsKey('a'));
  assertFalse(
      "containsKey, Should not contain the 'e' key", m.containsKey('e'));
}

function testClear() {
  var m = getMap();
  m.clear();
  assertTrue('cleared so it should be empty', m.isEmpty());
  assertTrue("cleared so it should not contain 'a' key", !m.containsKey('a'));
}

function testAddAll() {
  var m = new goog.structs.Map;
  m.addAll({a: 0, b: 1, c: 2, d: 3});
  assertTrue('addAll so it should not be empty', !m.isEmpty());
  assertTrue("addAll so it should contain 'c' key", m.containsKey('c'));

  var m2 = new goog.structs.Map;
  m2.addAll(m);
  assertTrue('addAll so it should not be empty', !m2.isEmpty());
  assertTrue("addAll so it should contain 'c' key", m2.containsKey('c'));

  m2.addAll(null);  // Ensure that passing a null object does not err.
}

function testConstructor() {
  var m = getMap();
  var m2 = new goog.structs.Map(m);
  assertTrue('constr with Map so it should not be empty', !m2.isEmpty());
  assertTrue(
      "constr with Map so it should contain 'c' key", m2.containsKey('c'));
}


function testConstructorWithVarArgs() {
  var m = new goog.structs.Map('a', 1);
  assertTrue('constr with var_args so it should not be empty', !m.isEmpty());
  assertEquals('constr with var_args', 1, m.get('a'));

  m = new goog.structs.Map('a', 1, 'b', 2);
  assertTrue('constr with var_args so it should not be empty', !m.isEmpty());
  assertEquals('constr with var_args', 1, m.get('a'));
  assertEquals('constr with var_args', 2, m.get('b'));

  assertThrows('Odd number of arguments is not allowed', function() {
    var m = new goog.structs.Map('a', 1, 'b');
  });
}

function testClone() {
  var m = getMap();
  var m2 = m.clone();
  assertTrue('clone so it should not be empty', !m2.isEmpty());
  assertTrue("clone so it should contain 'c' key", m2.containsKey('c'));
}


function testRemove() {
  var m = new goog.structs.Map();
  for (var i = 0; i < 1000; i++) {
    m.set(i, 'foo');
  }

  for (var i = 0; i < 1000; i++) {
    assertTrue(m.keys_.length <= 2 * m.getCount());
    m.remove(i);
  }
  assertTrue(m.isEmpty());
  assertEquals('', m.getKeys().join(''));
}


function testForEach() {
  var m = getMap();
  var s = '';
  goog.structs.forEach(m, function(val, key, m2) {
    assertNotUndefined(key);
    assertEquals(m, m2);
    s += key + val;
  });
  assertEquals(s, 'a0b1c2d3');
}

function testFilter() {
  var m = getMap();

  var m2 = goog.structs.filter(m, function(val, key, m3) {
    assertNotUndefined(key);
    assertEquals(m, m3);
    return val > 1;
  });
  assertEquals(stringifyMap(m2), 'c2d3');
}


function testMap() {
  var m = getMap();
  var m2 = goog.structs.map(m, function(val, key, m3) {
    assertNotUndefined(key);
    assertEquals(m, m3);
    return val * val;
  });
  assertEquals(stringifyMap(m2), 'a0b1c4d9');
}

function testSome() {
  var m = getMap();
  var b = goog.structs.some(m, function(val, key, m2) {
    assertNotUndefined(key);
    assertEquals(m, m2);
    return val > 1;
  });
  assertTrue(b);
  var b = goog.structs.some(m, function(val, key, m2) {
    assertNotUndefined(key);
    assertEquals(m, m2);
    return val > 100;
  });
  assertFalse(b);
}

function testEvery() {
  var m = getMap();
  var b = goog.structs.every(m, function(val, key, m2) {
    assertNotUndefined(key);
    assertEquals(m, m2);
    return val >= 0;
  });
  assertTrue(b);
  b = goog.structs.every(m, function(val, key, m2) {
    assertNotUndefined(key);
    assertEquals(m, m2);
    return val > 1;
  });
  assertFalse(b);
}

function testContainsValue() {
  var m = getMap();
  assertTrue(m.containsValue(3));
  assertFalse(m.containsValue(4));
}

function testObjectProperties() {
  var m = new goog.structs.Map;

  assertEquals(m.get('toString'), undefined);
  assertEquals(m.get('valueOf'), undefined);
  assertEquals(m.get('eval'), undefined);
  assertEquals(m.get('toSource'), undefined);
  assertEquals(m.get('prototype'), undefined);
  assertEquals(m.get(':foo'), undefined);

  m.set('toString', 'once');
  m.set('valueOf', 'upon');
  m.set('eval', 'a');
  m.set('toSource', 'midnight');
  m.set('prototype', 'dreary');
  m.set('hasOwnProperty', 'dark');
  m.set(':foo', 'happy');

  assertEquals(m.get('toString'), 'once');
  assertEquals(m.get('valueOf'), 'upon');
  assertEquals(m.get('eval'), 'a');
  assertEquals(m.get('toSource'), 'midnight');
  assertEquals(m.get('prototype'), 'dreary');
  assertEquals(m.get('hasOwnProperty'), 'dark');
  assertEquals(m.get(':foo'), 'happy');

  var keys = m.getKeys().join(',');
  assertEquals(
      keys, 'toString,valueOf,eval,toSource,prototype,hasOwnProperty,:foo');

  var values = m.getValues().join(',');
  assertEquals(values, 'once,upon,a,midnight,dreary,dark,happy');
}

function testDuplicateKeys() {
  var m = new goog.structs.Map;

  m.set('a', 1);
  m.set('b', 2);
  m.set('c', 3);
  m.set('d', 4);
  m.set('e', 5);
  m.set('f', 6);
  assertEquals(6, m.getKeys().length);
  m.set('foo', 1);
  assertEquals(7, m.getKeys().length);
  m.remove('foo');
  assertEquals(6, m.getKeys().length);
  m.set('foo', 2);
  assertEquals(7, m.getKeys().length);
  m.remove('foo');
  m.set('foo', 3);
  m.remove('foo');
  m.set('foo', 4);
  assertEquals(7, m.getKeys().length);
}

function testGetKeyIterator() {
  var m = new goog.structs.Map;
  m.set('a', 1);
  m.set('b', 2);
  m.set('c', 3);
  m.set('d', 4);
  m.set('e', 5);

  var iter = m.getKeyIterator();
  assertEquals('Should contain the keys', 'abcde', goog.iter.join(iter, ''));

  m.remove('b');
  m.remove('d');
  iter = m.getKeyIterator();
  assertEquals(
      'Should not contain the removed keys', 'ace', goog.iter.join(iter, ''));
}

function testGetValueIterator() {
  var m = new goog.structs.Map;
  m.set('a', 1);
  m.set('b', 2);
  m.set('c', 3);
  m.set('d', 4);
  m.set('e', 5);

  var iter = m.getValueIterator();
  assertEquals('Should contain the values', '12345', goog.iter.join(iter, ''));

  m.remove('b');
  m.remove('d');
  iter = m.getValueIterator();
  assertEquals(
      'Should not contain the removed keys', '135', goog.iter.join(iter, ''));
}

function testDefaultIterator() {
  // The default iterator should behave like the value iterator

  var m = new goog.structs.Map;
  m.set('a', 1);
  m.set('b', 2);
  m.set('c', 3);
  m.set('d', 4);
  m.set('e', 5);

  assertEquals('Should contain the values', '12345', goog.iter.join(m, ''));

  m.remove('b');
  m.remove('d');
  assertEquals(
      'Should not contain the removed keys', '135', goog.iter.join(m, ''));
}

function testMutatedIterator() {
  var message = 'The map has changed since the iterator was created';

  var m = new goog.structs.Map;
  m.set('a', 1);
  m.set('b', 2);
  m.set('c', 3);
  m.set('d', 4);

  var iter = m.getValueIterator();
  m.set('e', 5);
  var ex = assertThrows(
      'Expected an exception since the map has changed',
      function() { iter.next(); });
  assertEquals(message, ex.message);

  m = new goog.structs.Map;
  m.set('a', 1);
  m.set('b', 2);
  m.set('c', 3);
  m.set('d', 4);

  iter = m.getValueIterator();
  m.remove('d');
  var ex = assertThrows(
      'Expected an exception since the map has changed',
      function() { iter.next(); });
  assertEquals(message, ex.message);

  m = new goog.structs.Map;
  m.set('a', 1);
  m.set('b', 2);
  m.set('c', 3);
  m.set('d', 4);

  iter = m.getValueIterator();
  m.set('d', 5);
  iter.next();
  // Changing an existing value is OK.
  iter.next();
}

function testTranspose() {
  var m = new goog.structs.Map;
  m.set('a', 1);
  m.set('b', 2);
  m.set('c', 3);
  m.set('d', 4);
  m.set('e', 5);

  var transposed = m.transpose();
  assertEquals(
      'Should contain the keys', 'abcde', goog.iter.join(transposed, ''));
}

function testToObject() {
  Object.prototype.b = 0;
  try {
    var map = new goog.structs.Map();
    map.set('a', 0);
    var obj = map.toObject();
    assertTrue('object representation has key "a"', obj.hasOwnProperty('a'));
    assertFalse(
        'object representation does not have key "b"', obj.hasOwnProperty('b'));
    assertEquals('value for key "a"', 0, obj['a']);
  } finally {
    delete Object.prototype.b;
  }
}

function testEqualsWithSameObject() {
  var map1 = getMap();
  assertTrue('maps are the same object', map1.equals(map1));
}

function testEqualsWithDifferentSizeMaps() {
  var map1 = getMap();
  var map2 = new goog.structs.Map();

  assertFalse('maps are different sizes', map1.equals(map2));
}

function testEqualsWithDefaultEqualityFn() {
  var map1 = new goog.structs.Map();
  var map2 = new goog.structs.Map();

  assertTrue('maps are both empty', map1.equals(map2));

  map1 = getMap();
  map2 = getMap();
  assertTrue('maps are the same', map1.equals(map2));

  map2.set('d', '3');
  assertFalse('maps have 3 and \'3\'', map1.equals(map2));
}

function testEqualsWithCustomEqualityFn() {
  var map1 = new goog.structs.Map();
  var map2 = new goog.structs.Map();

  map1.set('a', 0);
  map1.set('b', 1);

  map2.set('a', '0');
  map2.set('b', '1');

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

  assertTrue('maps are equal with ==', map1.equals(map2, equalsFn));
}
