/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

goog.module('goog.labs.structs.MultimapTest');
goog.setTestOnly();

const Multimap = goog.require('goog.labs.structs.Multimap');
const testSuite = goog.require('goog.testing.testSuite');

let map;

/**
 * @param {T} entry
 * @param {!Array<T>} entryList
 * @template T
 */
function assertContainsEntry(entry, entryList) {
  for (let 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}`);
}
testSuite({
  setUp() {
    map = new Multimap();
  },

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

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

    const 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'));
  },

  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());

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

  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'));
  },

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

    const map2 = new 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'));
  },

  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());
  },

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

    assertTrue(map.remove('key', 'v'));
    let 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'));
  },

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

    assertTrue(map.remove('key', NaN));
    const 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));
  },

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

    assertTrue(map.remove('key', -0));
    let 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));
    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);
  },

  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());
  },

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

  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'));
  },

  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'));
  },

  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'));
  },

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

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

  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());
  },

  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());
  },

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

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

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

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