package fr.umlv.tpnote.test;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.AbstractMap.SimpleImmutableEntry;

import junit.framework.Assert;

import org.junit.Test;

import fr.umlv.tpnote.BiMap;
import fr.umlv.tpnote.SimpleBiMap;

public class BiMapTest {
  
  @Test
  public void getAndPut() {
    BiMap<String,String> biMap = new SimpleBiMap<>();
    biMap.put("foo", "bar");
    Assert.assertEquals("bar", biMap.getValueFromKey("foo"));
    Assert.assertEquals("foo", biMap.getKeyFromValue("bar"));
    Assert.assertNull(biMap.getValueFromKey("bar"));
    Assert.assertNull(biMap.getKeyFromValue("foo"));
  }
  
  @Test
  public void put() {
    BiMap<String,Integer> biMap = new SimpleBiMap<>();
    biMap.put("foo", 1);
    biMap.put("foo", 2);
    Assert.assertEquals(2, (int)biMap.getValueFromKey("foo"));
    Assert.assertEquals("foo", biMap.getKeyFromValue(2));
    Assert.assertNull(biMap.getKeyFromValue(1));
  }
  
  @Test
  public void put2() {
    BiMap<String,Integer> biMap = new SimpleBiMap<>();
    biMap.put("foo", 1);
    biMap.put("bar", 1);
    Assert.assertEquals(1, (int)biMap.getValueFromKey("bar"));
    Assert.assertEquals("bar", biMap.getKeyFromValue(1));
    Assert.assertNull(biMap.getValueFromKey("foo"));
  }
  
  @Test(expected=NullPointerException.class)
  public void putNullKey() {
    BiMap<Object,Object> biMap = new SimpleBiMap<>();
    biMap.put(null, "");
  }
  
  @Test(expected=NullPointerException.class)
  public void putNullValue() {
    BiMap<Object,Object> biMap = new SimpleBiMap<>();
    biMap.put("", null);
  }
  
  @Test(expected=NullPointerException.class)
  public void getNullKey() {
    BiMap<Object,Object> biMap = new SimpleBiMap<>();
    biMap.getValueFromKey(null);
  }
  
  @Test(expected=NullPointerException.class)
  public void getNullValue() {
    BiMap<Object,Object> biMap = new SimpleBiMap<>();
    biMap.getKeyFromValue(null);
  }
  
  @Test
  public void size() {
    BiMap<String,Integer> biMap = new SimpleBiMap<>();
    Assert.assertEquals(0, biMap.size());
    biMap.put("1", 1);
    Assert.assertEquals(1, biMap.size());
    biMap.put("2", 2);
    Assert.assertEquals(2, biMap.size());
  }
  
  @Test
  public void size2() {
    BiMap<Integer,Integer> biMap = new SimpleBiMap<>();
    Assert.assertEquals(0, biMap.size());
    biMap.put(1, 1);
    Assert.assertEquals(1, biMap.size());
    biMap.put(1, 1);
    Assert.assertEquals(1, biMap.size());
    biMap.put(1, 2);
    Assert.assertEquals(1, biMap.size());
    biMap.put(3, 2);
    Assert.assertEquals(1, biMap.size());
    biMap.put(3, 4);
    Assert.assertEquals(1, biMap.size());
  }
  
  @Test
  public void size3() {
    BiMap<Integer, Integer> biMap = new SimpleBiMap<>();
    for(int i=0; i<100; i++) {
      biMap.put(i%7, i%8);
    } 
    Assert.assertEquals(7, biMap.size());
  }
  
  @Test
  public void iterator() {
    BiMap<String,Integer> biMap = new SimpleBiMap<>();
    HashSet<Entry<String,Integer>> set = new HashSet<>();
    for(int i=0; i<10_000; i++) {
      String key = Integer.toString(i);
      Integer value = i;
      biMap.put(key, value);
      set.add(new SimpleImmutableEntry<>(key, value));
    }
    
    HashSet<Entry<String,Integer>> set2 = new HashSet<>();
    Iterator<Entry<String,Integer>> it = biMap.iterator();
    Assert.assertTrue(it.hasNext());
    for(int i=0; i<10_000; i++) {
      if (i%5 == 0) {
        for(int j=0; j<i%7; j++) {
          Assert.assertTrue(it.hasNext());
        }
      }
      set2.add(it.next());
    }
    Assert.assertFalse(it.hasNext());
    
    Assert.assertEquals(set, set2);
    
    try {
      it.next();
      Assert.fail();
    } catch(NoSuchElementException e) {
      // ok
    }
  }
  
  @Test
  public void emptyIterator() {
    Assert.assertFalse(new SimpleBiMap<>().iterator().hasNext());
    try {
      new SimpleBiMap<>().iterator().next();
      Assert.fail();
    } catch(NoSuchElementException e) {
      // ok
    }
  }
  
  @Test
  public void iteratorRemove() {
    BiMap<Integer,String> biMap = new SimpleBiMap<>();
    for(int i=0; i<10_000; i++) {
      biMap.put(i, Integer.toString(i));
    }
    
    Iterator<Entry<Integer,String>> it = biMap.iterator();
    for(int i=0; i<10_000; i++) {
      Entry<Integer, String> entry = it.next();
      if (entry.getKey()%3 == 0) {
        it.remove();
        Assert.assertNull(biMap.getEntryByKey(entry.getKey()));
        Assert.assertNull(biMap.getEntryByValue(entry.getValue()));
      }
    }
    Assert.assertEquals(6_666, biMap.size());
    
    it = biMap.iterator();
    while(it.hasNext()) {
      Assert.assertTrue(it.next().getKey() % 3 != 0);
    }
  }
  
  @Test(expected=IllegalStateException.class)
  public void iteratorRemove2() {
    new SimpleBiMap<>().iterator().remove();
  }
  
  @Test
  public void orderedIterator() {
    BiMap<Integer,Integer> biMap = new SimpleBiMap<>();
    for(int i=0; i<10_000; i++) {
      Integer value = i;
      biMap.put(value, value);
    }
    
    Iterator<Entry<Integer,Integer>> it = biMap.iterator();
    for(int i=0; i<10_000; i++) {
      Entry<Integer, Integer> entry = it.next();
      Assert.assertEquals(i, (int)entry.getKey());
      Assert.assertEquals(i, (int)entry.getValue());
    }
  }
  
  @Test
  public void forEach() {
    BiMap<Integer,Integer> biMap = new SimpleBiMap<>();
    for(int i=0; i<10_000; i++) {
      Integer value = i;
      biMap.put(value, value);
    }
    
    for(Entry<Integer,Integer> entry: biMap) {
      Integer key = entry.getKey();
      Integer value = entry.getValue();
      Assert.assertEquals(key, biMap.getKeyFromValue(value));
      Assert.assertEquals(value, biMap.getValueFromKey(key));
    }
  }
  
  @Test
  public void putAll() {
    BiMap<Object, Object> biMap = new SimpleBiMap<>();
    biMap.put(1, 2);
    
    BiMap<Integer, String> biMap2 = new SimpleBiMap<>();
    biMap2.put(1, "foo");
    biMap.putAll(biMap2);
    Assert.assertEquals(1, biMap.size());
    Assert.assertEquals("foo", biMap.getValueFromKey(1));
    Assert.assertEquals(1, (int)biMap.getKeyFromValue("foo"));
    
    BiMap<String, String> biMap3 = new SimpleBiMap<>();
    biMap3.put("bar", "foo");
    biMap.putAll(biMap3);
    Assert.assertEquals(1, biMap.size());
    Assert.assertEquals("foo", biMap.getValueFromKey("bar"));
    Assert.assertEquals("bar", biMap.getKeyFromValue("foo"));
  }
  
  @Test
  public void asInverseBiMap() {
    BiMap<Integer, String> biMap = new SimpleBiMap<>();
    BiMap<String, Integer> inverseBiMap = biMap.asInverseBiMap();
    biMap.put(1, "2");
    Assert.assertEquals(1, inverseBiMap.size());
    Assert.assertEquals(1, (int)inverseBiMap.getValueFromKey("2"));
    Assert.assertEquals("2", inverseBiMap.getKeyFromValue(1));
  }
  
  @Test
  public void asInverseInverseBiMap() {
    BiMap<Integer, String> biMap = new SimpleBiMap<>();
    for(int i=0; i<10_000; i++) {
      biMap.put(i, Integer.toHexString(i));
    }
    
    BiMap<Integer, String> biMap2 = biMap.asInverseBiMap().asInverseBiMap();
    for(Entry<Integer,String> entry: biMap2) {
      Integer key = entry.getKey();
      String value = entry.getValue();
      Assert.assertEquals(key, biMap.getKeyFromValue(value));
      Assert.assertEquals(value, biMap.getValueFromKey(key));
    }
  }
  
  @Test
  public void getEntryByKey() {
    BiMap<String, Object> biMap = new SimpleBiMap<>();
    biMap.put("foo", "bar");
    Entry<String, Object> entry = biMap.getEntryByKey("foo");
    Assert.assertNotNull(entry);
    Assert.assertEquals("foo", entry.getKey());
    Assert.assertEquals("bar", entry.getValue());
    
    Object oldValue = entry.setValue(42);
    Assert.assertEquals(42, (int)entry.getValue());
    Assert.assertEquals("bar", oldValue);
    Assert.assertEquals(42, (int)biMap.getValueFromKey("foo"));
  }
  
  @Test
  public void getEntryByKey2() {
    BiMap<Integer, Integer> biMap = new SimpleBiMap<>();
    biMap.put(123, 321);
    biMap.put(3, 456);
    Entry<Integer, Integer> entry = biMap.getEntryByKey(123);
    entry.setValue(456);
    Assert.assertEquals(456, (int)biMap.getValueFromKey(123));
    Assert.assertEquals(123, (int)biMap.getKeyFromValue(456));
    Assert.assertEquals(1, biMap.size());
  }
  
  @Test
  public void getEntryByKeyNotPresent() {
    BiMap<Integer, Integer> biMap = new SimpleBiMap<>();
    biMap.put(666, 888);
    Assert.assertNull(biMap.getEntryByKey(888));
  }
  
  @Test(expected=NullPointerException.class)
  public void getEntryByNullKey() {
    new SimpleBiMap<>().getEntryByKey(null);
  }
  
  @Test
  public void getEntryByValue() {
    BiMap<String, Object> biMap = new SimpleBiMap<>();
    biMap.put("foo", "bar");
    Entry<Object, String> entry = biMap.getEntryByValue("bar");
    Assert.assertNotNull(entry);
    Assert.assertEquals("bar", entry.getKey());
    Assert.assertEquals("foo", entry.getValue());
    
    String oldValue = entry.setValue("baz");
    Assert.assertEquals("baz", entry.getValue());
    Assert.assertEquals("foo", oldValue);
    Assert.assertEquals("baz", biMap.getKeyFromValue("bar"));
  }
  
  @Test
  public void getEntryByValue2() {
    BiMap<Integer, Integer> biMap = new SimpleBiMap<>();
    biMap.put(123, 321);
    biMap.put(654, 3);
    Entry<Integer, Integer> entry = biMap.getEntryByValue(321);
    entry.setValue(654);
    Assert.assertEquals(654, (int)biMap.getKeyFromValue(321));
    Assert.assertEquals(321, (int)biMap.getValueFromKey(654));
    Assert.assertEquals(1, biMap.size());
  }
  
  @Test
  public void getEntryByValueNotPresent() {
    BiMap<Integer, Integer> biMap = new SimpleBiMap<>();
    biMap.put(666, 888);
    Assert.assertNull(biMap.getEntryByValue(666));
  }
  
  @Test(expected=NullPointerException.class)
  public void getEntryByNullValue() {
    new SimpleBiMap<>().getEntryByValue(null);
  }
}
