package zemberek.core.collections;

import com.google.common.base.Stopwatch;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;

public class IntValueMapTest {

  @Test
  public void constructorTest() {
    IntValueMap set = new IntValueMap();
    Assert.assertEquals(0, set.size());
    set = new IntValueMap(1);
    Assert.assertEquals(0, set.size());
  }

  @Test
  public void constructorTest2() {
    IntValueMap<String> set = new IntValueMap<>(1);
    set.addOrIncrement("foo");
    Assert.assertEquals(1, set.size());
    set.remove("foo");
    Assert.assertEquals(0, set.size());
    Assert.assertFalse(set.contains("foo"));
  }


  @Test
  public void putTest() {
    IntValueMap<String> table = new IntValueMap<>();
    table.put("foo", 1);
    Assert.assertEquals(1, table.size());
    table.put("foo", 2);
    Assert.assertEquals(1, table.size());

    table = new IntValueMap<>();
    for (int i = 0; i < 1000; i++) {
      table.put(String.valueOf(i), i + 1);
      Assert.assertEquals(i + 1, table.size());
    }

    table = new IntValueMap<>();
    for (int i = 0; i < 1000; i++) {
      table.put(String.valueOf(i), i + 1);
      table.put(String.valueOf(i), i + 1);
      Assert.assertEquals(i + 1, table.size());
    }
  }

  @Test
  public void expandTest() {
    IntValueMap<String> table = new IntValueMap<>();

    // we put 0..9999 keys with 1..10000 values
    for (int i = 0; i < 10000; i++) {
      table.put(String.valueOf(i), i + 1);
      Assert.assertEquals(i + 1, table.size());
    }
    // we remove the first half
    for (int i = 0; i < 5000; i++) {
      table.remove(String.valueOf(i));
      Assert.assertEquals(10000 - i - 1, table.size());
    }
    // now we check if remaining values are intact
    for (int i = 5000; i < 10000; i++) {
      Assert.assertEquals(i + 1, table.get(String.valueOf(i)));
    }
  }

  @Test
  public void collisionTest() {
    IntValueMap<Integer> v = new IntValueMap<>(16);
    v.put(3, 5);
    v.put(19, 9);
    v.put(35, 13);
    Assert.assertEquals(3, v.keyCount);

    Assert.assertEquals(5, v.get(3));
    Assert.assertEquals(9, v.get(19));
    Assert.assertEquals(13, v.get(35));

    v.remove(19);
    Assert.assertEquals(2, v.keyCount);

    Assert.assertEquals(5, v.get(3));
    Assert.assertEquals(0, v.get(19));
    Assert.assertEquals(13, v.get(35));

    v.addOrIncrement(35);
    Assert.assertEquals(2, v.keyCount);
    Assert.assertEquals(14, v.get(35));

    v.remove(35);
    Assert.assertEquals(1, v.keyCount);
    v.put(19, 5);
    Assert.assertEquals(2, v.keyCount);
    v.addOrIncrement(35);
    Assert.assertEquals(3, v.keyCount);

    Assert.assertEquals(1, v.get(35));
    Assert.assertEquals(5, v.get(19));
  }

  @Test
  public void removeTest() {
    IntValueMap<String> table = new IntValueMap<>();
    table.put(String.valueOf(1), 1);
    Assert.assertEquals(1, table.size());
    table.remove(String.valueOf(1));
    Assert.assertEquals(0, table.size());

    table = new IntValueMap<>();
    for (int i = 0; i < 1000; i++) {
      table.put(String.valueOf(i), i + 1);
    }
    for (int i = 0; i < 1000; i++) {
      table.remove(String.valueOf(i));
      Assert.assertEquals(0, table.get(String.valueOf(i)));
      Assert.assertEquals(1000 - i - 1, table.size());
    }

    table = new IntValueMap<>(8);
    table.put(String.valueOf(1), 1);
    table.put(String.valueOf(9), 1);
    Assert.assertEquals(2, table.size());
    table.remove(String.valueOf(9));
    Assert.assertEquals(1, table.size());
    Assert.assertEquals(0, table.get(String.valueOf(9)));
  }

  @Test
  public void incremenTest() {
    IntValueMap<Integer> table = new IntValueMap<>();

    int res = table.addOrIncrement(1);
    Assert.assertEquals(1, res);
    Assert.assertEquals(1, table.get(1));

    table.put(1, 2);
    res = table.addOrIncrement(1);
    Assert.assertEquals(3, res);
    Assert.assertEquals(3, table.get(1));

    table = new IntValueMap<>();
    for (int i = 0; i < 1000; i++) {
      res = table.addOrIncrement(1);
      Assert.assertEquals(i + 1, res);
      Assert.assertEquals(i + 1, table.get(1));
      Assert.assertEquals(1, table.size());
    }
  }

  @Test
  public void decremenTest() {
    IntValueMap<Integer> set = new IntValueMap<>();

    int res = set.decrement(1);
    Assert.assertEquals(-1, res);
    final int val = 5;
    set.put(1, val);
    set.put(9, val);
    for (int i = 0; i < val; i++) {
      res = set.decrement(1);
      int expected = val - i - 1;
      Assert.assertEquals(expected, res);
      Assert.assertEquals(expected, set.get(1));
    }
    Assert.assertEquals(2, set.size());
    res = set.decrement(1);
    Assert.assertEquals(-1, res);

    set = new IntValueMap<>();
    for (int i = 0; i < 1000; i++) {
      set.put(i, 1);
    }

    for (int i = 0; i < 1000; i++) {
      res = set.decrement(i);
      Assert.assertEquals(0, res);
      Assert.assertEquals(0, set.get(i));
    }

    set = new IntValueMap<>(8);
    set.put(1, 1);
    set.put(9, 1);
    Assert.assertEquals(2, set.size());
    set.decrement(9);
    Assert.assertEquals(0, set.get(9));
    Assert.assertEquals(2, set.size());
  }

  @Test
  public void getTest() {
    IntValueMap<Integer> table = new IntValueMap<>();
    table.put(1, 2);
    Assert.assertEquals(2, table.get(1));
    Assert.assertEquals(0, table.get(2));
    table.put(1, 3);
    Assert.assertEquals(3, table.get(1));

    table = new IntValueMap<>();
    for (int i = 0; i < 1000; i++) {
      table.put(i, i + 1);
    }
    for (int i = 0; i < 1000; i++) {
      Assert.assertEquals(i + 1, table.get(i));
    }
  }

  @Test
  public void copyOfValuesTest() {
    IntValueMap<String> set = new IntValueMap<>();
    for (int i = 0; i < 1000; i++) {
      set.put(String.valueOf(i), i + 1);
    }
    int[] values = set.copyOfValues();
    Assert.assertEquals(1000, values.length);
    Arrays.sort(values);
    for (int i = 0; i < 1000; i++) {
      Assert.assertEquals(i + 1, values[i]);
    }

    set.remove("768");
    set.remove("0");
    set.remove("999");

    values = set.copyOfValues();
    Assert.assertEquals(997, values.length);
    Arrays.sort(values);

    Assert.assertTrue(Arrays.binarySearch(values, 769) < 0);
    Assert.assertTrue(Arrays.binarySearch(values, 1) < 0);
    Assert.assertTrue(Arrays.binarySearch(values, 1000) < 0);
  }

  @Test
  public void keyIteratorStressTest() {
    Random rand = new Random(1);
    for (int i = 0; i < 5; i++) {
      IntValueMap<Integer> siv = new IntValueMap<>();
      Set<Integer> uniqueKeys = new HashSet<>();
      for (int j = 0; j < 500_000; j++) {
        uniqueKeys.add(rand.nextInt(500_000));
      }
      for (int k : uniqueKeys) {
        siv.put(k, k+1);
      }


      int itLen = 0;
      for(Integer k  : siv) {
        itLen++;
      }
      Assert.assertEquals(itLen, uniqueKeys.size());

      Set<Integer> readValues = new HashSet<>();
      Set<Integer> readKeys = new HashSet<>();
      for (Integer key : siv) {
        readValues.add(siv.get(key)-1);
        readKeys.add(key);
      }
      Assert.assertEquals(uniqueKeys, readValues);
      Assert.assertEquals(uniqueKeys, readKeys);

      int j = 0;
      for(Integer ke : uniqueKeys) {
        if(j>50_000) {
          break;
        }


      }

    }
  }

  @Test
  public void stressTest() {
    Random rand = new Random(System.currentTimeMillis());
    for (int i = 0; i < 10; i++) {
      IntValueMap<Integer> siv = new IntValueMap<>();
      int kc = 0;
      for (int j = 0; j < 100000; j++) {
        int key = rand.nextInt(1000);
        boolean exist = siv.contains(key);
        int operation = rand.nextInt(8);
        switch (operation) {
          case 0: // insert
            int value = rand.nextInt(10) + 1;
            if (!exist) {
              siv.put(key, value);
              kc++;
            }
            break;
          case 1:
            if (exist) {
              siv.remove(key);
              kc--;
            }
            break;
          case 2:
            siv.addOrIncrement(key);
            if (!exist) {
              kc++;
            }
            break;
          case 3:
            siv.get(key);
            break;
          case 4:
            if (!exist) {
              kc++;
            }
            siv.decrement(key);
            break;
          case 6:
            value = rand.nextInt(10) + 1;
            siv.incrementByAmount(key, value);
            if (!exist) {
              kc++;
            }
            break;
          case 7:
            value = rand.nextInt(10) + 1;
            siv.incrementByAmount(key, -value);
            if (!exist) {
              kc++;
            }
            break;
        }
      }
      System.out.println(i + " Calculated=" + kc + " Actual=" + siv.keyCount);
    }
  }

  @Test
  @Ignore("Not a unit test")
  public void performanceAgainstMap() {
    Random r = new Random();
    int[][] keyVals = new int[100000][2];
    final int itCount = 100;
    for (int i = 0; i < keyVals.length; i++) {
      keyVals[i][0] = r.nextInt(500000);
      keyVals[i][1] = r.nextInt(5000) + 1;
    }
    Stopwatch sw = Stopwatch.createStarted();
    for (int j = 0; j < itCount; j++) {

      HashMap<Integer, Integer> map = new HashMap<>();

      for (int[] keyVal : keyVals) {
        map.put(keyVal[0], keyVal[1]);
      }

      for (int[] keyVal : keyVals) {
        map.get(keyVal[0]);
      }

      for (int[] keyVal : keyVals) {
        if (map.containsKey(keyVal[0])) {
          map.put(keyVal[0], map.get(keyVal[0]) + 1);
        }
      }

      for (int[] keyVal : keyVals) {
        if (map.containsKey(keyVal[0])) {
          int count = map.get(keyVal[0]);
          if (count == 1) {
            map.remove(keyVal[0]);
          } else {
            map.put(keyVal[0], count - 1);
          }
        }
      }
    }
    System.out.println("Map Elapsed:" + sw.elapsed(TimeUnit.MILLISECONDS));

    IntValueMap<Integer> countTable = new IntValueMap<>();
    sw = Stopwatch.createStarted();

    for (int j = 0; j < itCount; j++) {

      for (int[] keyVal : keyVals) {
        countTable.put(keyVal[0], keyVal[1]);
      }
      for (int[] keyVal : keyVals) {
        countTable.get(keyVal[0]);
      }

      for (int[] keyVal : keyVals) {
        countTable.addOrIncrement(keyVal[0]);
      }

      for (int[] keyVal : keyVals) {
        countTable.decrement(keyVal[0]);
      }
    }
    System.out.println("Count Elapsed:" + sw.elapsed(TimeUnit.MILLISECONDS));
  }

  @Test
  @Ignore("Not a unit test")
  public void perfStrings() {
    for (int i = 0; i < 5; i++) {
      Set<String> strings = uniqueStrings(1000000, 7);
      Stopwatch sw = Stopwatch.createStarted();
      Set<String> newSet = new HashSet<>(strings);
      System.out.println("Java Set : " + sw.elapsed(TimeUnit.MILLISECONDS));
      System.out.println("Size  = " + newSet.size());
      sw.reset().start();
      IntValueMap<String> cs = new IntValueMap<>(strings.size() * 2);
      cs.addOrIncrementAll(strings);
      System.out.println("Count Add : " + sw.elapsed(TimeUnit.MILLISECONDS));
    }
  }

  private Set<String> uniqueStrings(int amount, int stringLength) {
    Set<String> set = new HashSet<>(amount);
    Random r = new Random();
    while (set.size() < amount) {
      StringBuilder sb = new StringBuilder(stringLength);
      for (int i = 0; i < stringLength; i++) {
        sb.append((char) (r.nextInt(26) + 'a'));
      }
      set.add(sb.toString());
    }
    return set;
  }
}
    

