package org.infinispan.commons.util;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

import org.junit.Test;

public class HashMapTest {
   @Test
   public void testCreateHopscotch() {
      testCreate(new HopscotchHashMap<>(32));
   }

   @Test
   public void testCreateHopscotchWithRemovals() {
      testCreateWithRemovals(new HopscotchHashMap<>(32));
   }

   private void testCreate(Map<Integer, Integer> testedMap) {
      HashMap<Integer, Integer> map = new HashMap<>();
      ThreadLocalRandom random = ThreadLocalRandom.current();
      for (int i = 0; i < 10000; ++i) {
         Integer key;
         do {
            key = random.nextInt();
         } while (map.containsKey(key));
         Integer value = random.nextInt();
         testedMap.put(key, value);
         assertEquals("Contents differs with " + (i + 1) + " entries", value, testedMap.get(key));
         assertEquals(i + 1, testedMap.size());
         map.put(key, value);
         for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            Integer actual = testedMap.get(entry.getKey());
            assertEquals(entry.getValue(), actual);
         }
      }
      assertEquals(map, testedMap);
      // test re-inserts
      for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
         testedMap.put(entry.getKey(), entry.getValue());
         assertEquals(map.size(), testedMap.size());
      }
      assertEquals(map, testedMap);
   }

   private void testCreateWithRemovals(Map<Integer, Integer> testedMap) {
      HashMap<Integer, Integer> map = new HashMap<>();
      ThreadLocalRandom random = ThreadLocalRandom.current();
      int[] keys = new int[10000];
      Arrays.fill(keys, -1);
      for (int i = 0; i < 10000; ++i) {
         if (map.size() > 0 && random.nextInt(5) == 0) {
            int ki, key;
            do {
               key = keys[ki = random.nextInt(i)];
            } while (key < 0);
            keys[ki] = -1;
            Integer value = map.remove(key);
            assertNotNull(value);
            Integer removed = testedMap.remove(key);
            assertEquals(value, removed);
            assertEquals(map.size(), testedMap.size());
         } else {
            int key;
            do {
               key = Math.abs(random.nextInt());
            } while (map.containsKey(key));
            Integer value = random.nextInt();
            keys[i] = key;
            testedMap.put(key, value);
            assertEquals("Contents differs with " + (i + 1) + " entries", value, testedMap.get(key));
            map.put(key, value);
            assertEquals(map.size(), testedMap.size());
         }
         for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            assertEquals(entry.getValue(), testedMap.get(entry.getKey()));
         }
      }
      assertEquals(map, testedMap);
   }
}
