// Copyright 2012 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.labs.structs.MultimapTest');
goog.setTestOnly('goog.labs.structs.MultimapTest');

goog.require('goog.labs.structs.Multimap');
goog.require('goog.testing.jsunit');
goog.require('goog.userAgent');

var map;


function setUp() {
  map = new goog.labs.structs.Multimap();
}

function shouldRunTests() {
  if (goog.userAgent.IE) {
    return goog.userAgent.isVersionOrHigher(9);
  }
  return true;
}

function testGetCountWithEmptyMultimap() {
  assertEquals(0, map.getCount());
  assertTrue(map.isEmpty());
}


function testClone() {
  map.add('k', 'v');
  map.addAllValues('k2', ['v', 'v1', 'v2']);

  var map2 = map.clone();

  assertSameElements(['v'], map.get('k'));
  assertSameElements(['v', 'v1', 'v2'], map.get('k2'));

  assertSameElements(['v'], map2.get('k'));
  assertSameElements(['v', 'v1', 'v2'], map2.get('k2'));
}


function testAdd() {
  map.add('key', 'v');
  assertEquals(1, map.getCount());
  map.add('key', 'v2');
  assertEquals(2, map.getCount());
  map.add('key', 'v3');
  assertEquals(3, map.getCount());

  var values = map.get('key');
  assertEquals(3, values.length);
  assertContains('v', values);
  assertContains('v2', values);
  assertContains('v3', values);
}


function testAddValues() {
  map.addAllValues('key', ['v', 'v2', 'v3']);
  assertSameElements(['v', 'v2', 'v3'], map.get('key'));

  map.add('key2', 'a');
  map.addAllValues('key2', ['v', 'v2', 'v3']);
  assertSameElements(['a', 'v', 'v2', 'v3'], map.get('key2'));
}


function testAddAllWithMultimap() {
  map.add('k', 'v');
  map.addAllValues('k2', ['v', 'v1', 'v2']);

  var map2 = new goog.labs.structs.Multimap();
  map2.add('k2', 'v');
  map2.addAllValues('k3', ['a', 'a1', 'a2']);

  map.addAllFromMultimap(map2);
  assertSameElements(['v'], map.get('k'));
  assertSameElements(['v', 'v1', 'v2', 'v'], map.get('k2'));
  assertSameElements(['a', 'a1', 'a2'], map.get('k3'));
}


function testReplaceValues() {
  map.add('key', 'v');
  map.add('key', 'v2');

  map.replaceValues('key', [0, 1, 2]);
  assertSameElements([0, 1, 2], map.get('key'));
  assertEquals(3, map.getCount());

  map.replaceValues('key', ['v']);
  assertSameElements(['v'], map.get('key'));
  assertEquals(1, map.getCount());

  map.replaceValues('key', []);
  assertSameElements([], map.get('key'));
  assertEquals(0, map.getCount());
}


function testRemove() {
  map.add('key', 'v');
  map.add('key', 'v2');
  map.add('key', 'v3');

  assertTrue(map.remove('key', 'v'));
  var values = map.get('key');
  assertEquals(2, map.getCount());
  assertEquals(2, values.length);
  assertContains('v2', values);
  assertContains('v3', values);
  assertFalse(map.remove('key', 'v'));

  assertTrue(map.remove('key', 'v2'));
  values = map.get('key');
  assertEquals(1, map.getCount());
  assertEquals(1, values.length);
  assertContains('v3', values);
  assertFalse(map.remove('key', 'v2'));

  assertTrue(map.remove('key', 'v3'));
  map.remove('key', 'v3');
  assertTrue(map.isEmpty());
  assertEquals(0, map.get('key').length);
  assertFalse(map.remove('key', 'v2'));
}


function testRemoveWithNaN() {
  map.add('key', NaN);
  map.add('key', NaN);

  assertTrue(map.remove('key', NaN));
  var values = map.get('key');
  assertEquals(1, values.length);
  assertTrue(isNaN(values[0]));

  assertTrue(map.remove('key', NaN));
  assertEquals(0, map.get('key').length);
  assertFalse(map.remove('key', NaN));
}


function testRemoveWithNegativeZero() {
  map.add('key', 0);
  map.add('key', -0);

  assertTrue(map.remove('key', -0));
  var values = map.get('key');
  assertEquals(1, values.length);
  assertTrue(1 / values[0] === 1 / 0);
  assertFalse(map.remove('key', -0));

  map.add('key', -0);

  assertTrue(map.remove('key', 0));
  var values = map.get('key');
  assertEquals(1, values.length);
  assertTrue(1 / values[0] === 1 / -0);
  assertFalse(map.remove('key', 0));

  assertTrue(map.remove('key', -0));
  assertEquals(0, map.get('key').length);
}


function testRemoveAll() {
  map.add('key', 'v');
  map.add('key', 'v2');
  map.add('key', 'v3');
  map.add('key', 'v4');
  map.add('key2', 'v');

  assertTrue(map.removeAll('key'));
  assertSameElements([], map.get('key'));
  assertSameElements(['v'], map.get('key2'));
  assertFalse(map.removeAll('key'));
  assertEquals(1, map.getCount());

  assertTrue(map.removeAll('key2'));
  assertSameElements([], map.get('key2'));
  assertFalse(map.removeAll('key2'));
  assertTrue(map.isEmpty());
}


function testAddWithDuplicateValue() {
  map.add('key', 'v');
  map.add('key', 'v');
  map.add('key', 'v');
  assertArrayEquals(['v', 'v', 'v'], map.get('key'));
}


function testContainsEntry() {
  assertFalse(map.containsEntry('k', 'v'));
  assertFalse(map.containsEntry('k', 'v2'));
  assertFalse(map.containsEntry('k2', 'v'));

  map.add('k', 'v');
  assertTrue(map.containsEntry('k', 'v'));
  assertFalse(map.containsEntry('k', 'v2'));
  assertFalse(map.containsEntry('k2', 'v'));

  map.add('k', 'v2');
  assertTrue(map.containsEntry('k', 'v'));
  assertTrue(map.containsEntry('k', 'v2'));
  assertFalse(map.containsEntry('k2', 'v'));

  map.add('k2', 'v');
  assertTrue(map.containsEntry('k', 'v'));
  assertTrue(map.containsEntry('k', 'v2'));
  assertTrue(map.containsEntry('k2', 'v'));
}


function testContainsKey() {
  assertFalse(map.containsKey('k'));
  assertFalse(map.containsKey('k2'));

  map.add('k', 'v');
  assertTrue(map.containsKey('k'));
  map.add('k2', 'v');
  assertTrue(map.containsKey('k2'));

  map.remove('k', 'v');
  assertFalse(map.containsKey('k'));
  map.remove('k2', 'v');
  assertFalse(map.containsKey('k2'));
}


function testContainsValue() {
  assertFalse(map.containsValue('v'));
  assertFalse(map.containsValue('v2'));

  map.add('key', 'v');
  assertTrue(map.containsValue('v'));
  map.add('key', 'v2');
  assertTrue(map.containsValue('v2'));
}


function testGetEntries() {
  map.add('key', 'v');
  map.add('key', 'v2');
  map.add('key2', 'v3');

  var entries = map.getEntries();
  assertEquals(3, entries.length);
  assertContainsEntry(['key', 'v'], entries);
  assertContainsEntry(['key', 'v2'], entries);
  assertContainsEntry(['key2', 'v3'], entries);
}


function testGetKeys() {
  map.add('key', 'v');
  map.add('key', 'v2');
  map.add('key2', 'v3');
  map.add('key3', 'v4');
  map.removeAll('key3');

  assertSameElements(['key', 'key2'], map.getKeys());
}


function testGetValues() {
  map.add('key', 'v');
  map.add('key', 'v2');
  map.add('key2', 'v2');
  map.add('key3', 'v4');
  map.removeAll('key3');

  assertSameElements(['v', 'v2', 'v2'], map.getValues());
}


function testGetReturnsDefensiveCopyOfUnderlyingData() {
  map.add('key', 'v');
  map.add('key', 'v2');
  map.add('key', 'v3');

  var values = map.get('key');
  values.push('v4');
  assertFalse(map.containsEntry('key', 'v4'));
}


function testClear() {
  map.add('key', 'v');
  map.add('key', 'v2');
  map.add('key2', 'v3');

  map.clear();
  assertTrue(map.isEmpty());
  assertSameElements([], map.getEntries());
}


/**
 * @param {T} entry
 * @param {!Array<T>} entryList
 * @template T
 */
function assertContainsEntry(entry, entryList) {
  for (var i = 0; i < entryList.length; ++i) {
    if (entry[0] == entryList[i][0] && entry[1] === entryList[i][1]) {
      return;
    }
  }
  fail('Did not find entry: ' + entry + ' in: ' + entryList);
}
