package fr.umlv.universe;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.junit.Assert;
import org.junit.Test;
 
public class UniverseTest {
  @Test
  public void emptyConstructor() {
    Assert.assertTrue(new Universe<String>().isEmpty());
  }

  @Test
  public void universeManyMethodFactory() {
    Assert.assertEquals(1, Universe.universe("foo").size());
    Assert.assertEquals(2, Universe.universe(1, 42).size());
    Assert.assertEquals(3, Universe.universe("foo", "bar", "baz").size());
    Assert.assertEquals(2, Universe.universe(12L, 24L, 12L, 24L).size());
    Assert.assertEquals(2, Universe.universe(new String("zab"), new String("zab")).size());
  }
  
  @Test
  public void universeCollectionMethodFactory() {
    List<String> list = Collections.nCopies(100, "zarro");
    Universe<String> universe = Universe.universe(list);
    Assert.assertEquals(1, universe.size());
  }
  
  public void universeAdd() {
    Universe<Object> universe = new Universe<>();
    Assert.assertTrue(universe.isEmpty());
    universe.add("foobar");
    Assert.assertEquals(1, universe.size());
    universe.add("foobar");
    Assert.assertEquals(1, universe.size());
  }
  
  @Test(expected=NullPointerException.class)
  public void universeAddNull() {
    new Universe<Object>().add(null);
  }
  
  @Test
  public void universeIsASet() {
    HashSet<String> result = new HashSet<>(Arrays.asList("h", "e", "l", "l"));
    Set<Object> universe = new Universe<>();
    universe.addAll(result);
    Assert.assertEquals(result, universe);
  }
  
  @Test
  public void universeContainsItself() {
    Universe<Object> universe = Universe.<Object>universe(7, 3);
    Assert.assertTrue(universe.containsAll(universe));
  }
  
  @Test
  public void universeSetConstructors() {
    Universe<String> universe = Universe.universe("foo", "bar");
    Assert.assertTrue(universe.newSet().isEmpty());
    Assert.assertEquals(1, universe.newSet("foo").size());
    Assert.assertEquals(1, universe.newSet(Arrays.asList("bar")).size());
  }
  
  @Test(expected=IllegalArgumentException.class)
  public void universeSetConstructorNotInSet1() {
    Universe<String> universe = Universe.universe("baz");
    Set<String> set = universe.newSet();
    set.add("zab");
  }
  
  @Test(expected=IllegalArgumentException.class)
  public void universeSetConstructorNotInSet2() {
    Universe<Integer> universe = Universe.universe(-1, 42);
    universe.newSet(-42);
  }
  
  @Test(expected=IllegalArgumentException.class)
  public void universeSetConstructorNotInSet3() {
    Universe<Double> universe = Universe.universe(12.);
    universe.newSet(Arrays.asList(21.));
  }
  
  @Test
  public void universeSetConstructorAll() {
    Universe<Integer> universe = Universe.universe(123, 321);
    Set<Integer> set = universe.newSet(universe);
    Assert.assertEquals(universe, set);
  }
  
  @Test
  public void universeSetAdd() {
    Universe<Object> universe = Universe.<Object>universe("zoo", 100);
    Set<Object> set = universe.newSet();
    set.add("zoo");
    Assert.assertEquals(Collections.singleton("zoo"), set);
  }
  
  @Test
  public void universeSetIterator() {
    Universe<Integer> universe = Universe.universe(1,4,5,7);
    Set<Integer> set = universe.newSet(universe);
    Iterator<Integer> iterator = set.iterator();
    Assert.assertTrue(iterator.hasNext());
    Assert.assertTrue(iterator.hasNext());
    
    HashSet<Integer> hashSet = new HashSet<>();
    for(int i=0; i<universe.size(); i++) {
      hashSet.add(iterator.next());
    }
    Assert.assertFalse(iterator.hasNext());
    Assert.assertEquals(set, hashSet);
  }
  
  @Test
  public void universeSetIteratorRemove() {
    Universe<Integer> universe = Universe.universe(1,4,5,7);
    Set<Integer> set = universe.newSet(universe);
    Iterator<Integer> iterator = set.iterator();
    for(int i=0; i<universe.size(); i++) {
      int value = iterator.next();
      if (value % 2 == 1) {
        iterator.remove();
      }
    }
    Assert.assertEquals(Collections.singleton(4), set);
  }
  
  @Test
  public void universeSetRemove() {
    Universe<String> universe = Universe.universe("1", "2");
    Set<String> set = universe.newSet(universe);
    Assert.assertTrue(set.remove("2"));
    Assert.assertFalse(set.remove("3"));
    Assert.assertEquals(Collections.singleton("1"), set);
  }
  
  @Test
  public void universeSetClear() {
    Universe<String> universe = Universe.universe("1", "2");
    Set<String> set = universe.newSet(universe);
    set.clear();
    Assert.assertTrue(set.isEmpty());
  }
  
  @Test
  public void universeSetAddAll() {
    Universe<Object> universe = Universe.<Object>universe("zoo", 100);
    Set<Object> set = universe.newSet();
    Assert.assertTrue(set.addAll(Arrays.asList(100, "zoo")));
    Assert.assertEquals(universe, set);
  }
  
  @Test
  public void universeSetAddAll2() {
    Universe<Integer> universe = Universe.universe(1, 34, 5, 7);
    Set<Integer> set1 = universe.newSet(34, 7);
    Set<Integer> set2 = universe.newSet(1, 5);
    Assert.assertTrue(set1.addAll(set2));
    Assert.assertEquals(universe, set1);
  }
  
  @Test
  public void universeSetRemoveAll() {
    Universe<String> universe = Universe.universe("1877", "1921");
    Set<String> set = universe.newSet(universe);
    Assert.assertTrue(set.removeAll(Arrays.asList("1921", "1877", "1757")));
    Assert.assertTrue(set.isEmpty());
  }
  
  @Test
  public void universeSetRemoveAll2() {
    Universe<Integer> universe = Universe.universe(23, 56, 78, 69);
    Set<Integer> set = universe.newSet(universe);
    Assert.assertTrue(set.removeAll(universe.newSet(69)));
    Assert.assertEquals(universe.newSet(23, 56, 78), set);
  }
  
  @Test
  public void universeSetRemoveAll3() {
    Universe<Integer> universe = Universe.universe(23, 56, 78, 69);
    Set<Integer> set = universe.newSet(universe);
    Assert.assertTrue(set.removeAll(universe));
    Assert.assertTrue(set.isEmpty());
  }
  
  @Test
  public void universeSetRetainAll() {
    Universe<String> universe = Universe.universe("1877", "1921");
    Set<String> set = universe.newSet(universe);
    Assert.assertTrue(set.retainAll(Arrays.asList("1921", "1757")));
    Assert.assertEquals(Collections.singleton("1921"), set);
  }
  
  @Test
  public void universeSetRetainAll2() {
    Universe<String> universe = Universe.universe("1877", "1921");
    Set<String> set = universe.newSet(universe);
    Assert.assertTrue(set.retainAll(universe.newSet("1921")));
    Assert.assertEquals(Collections.singleton("1921"), set);
  }
  
  @Test
  public void universeSetRetainAll3() {
    Universe<Integer> universe = Universe.universe(23, 56, 78, 69);
    Set<Integer> set = universe.newSet(universe);
    Assert.assertFalse(set.retainAll(universe));
    Assert.assertEquals(universe, set);
  }
  
  @Test
  public void universeMapConstructors() {
    Universe<Integer> universe = Universe.universe(1, 11, 111, 1111);
    Assert.assertTrue(universe.newMap().isEmpty());
    
    HashMap<Integer, Integer> hashMap = new HashMap<>();
    for(Integer value: universe) {
      hashMap.put(value, (int)Math.log10(value));
    }
    Map<Integer, Integer> map1 = universe.newMap(0, 1, 2, 3);
    Assert.assertEquals(hashMap, map1);
    
    Map<Integer, Integer> map2 = Collections.singletonMap(11, 2);
    Assert.assertEquals(map2, universe.newMap(map2));
  }
  
  @Test
  public void universeMapGet() {
    Universe<String> universe = Universe.universe("dog", "bark");
    Map<String, Boolean> map = universe.newMap(Collections.singletonMap("dog", true));
    Assert.assertTrue(map.get("dog"));
    Assert.assertNull(map.get("bark"));
    Assert.assertNull(map.get("cat"));
    Assert.assertNull(map.get(null));
  }
  
  @Test
  public void universeMapPut() {
    Universe<String> universe = Universe.universe("dog", "cat");
    Map<String, String> map = universe.newMap();
    Assert.assertNull(map.put("dog", "god"));
    Assert.assertEquals(1, map.size());
    Assert.assertEquals("god", map.get("dog"));
    Assert.assertEquals("god", map.put("dog", "alpha"));
    Assert.assertEquals(1, map.size());
    Assert.assertEquals("alpha", map.get("dog"));
  }
  
  @Test
  public void universeMapContainsKey() {
    Universe<String> universe = Universe.universe("dog", "cat");
    Map<String, Integer> map = universe.newMap(1, 2);
    Assert.assertTrue(map.containsKey("dog"));
    Assert.assertTrue(map.containsKey("cat"));
  }
  
  @Test
  public void universeMapContainsKeyNull() {
    Universe<String> universe = Universe.universe("foo");
    Map<String, Object> map = universe.newMap();
    Assert.assertNull(map.get("foo"));
    Assert.assertFalse(map.containsKey("foo"));
  }
  
  @Test
  public void universeMapNullValue() {
    Universe<String> universe = Universe.universe("foo");
    Map<String, Object> map = universe.newMap(new Object[] {null});
    Assert.assertTrue(map.containsKey("foo"));
    Assert.assertNull(map.get("foo"));
    Assert.assertNull(map.values().iterator().next());
    Assert.assertNull(map.entrySet().iterator().next().getValue());
  }
  
  @Test
  public void universeMapRemove() {
    Universe<Integer> universe = Universe.universe(1, 3, 5, 8);
    Map<Integer, Integer> map = universe.newMap(2, 6, 10, 16);
    Assert.assertEquals(6, (int)map.remove(3));
    Assert.assertEquals(16, (int)map.remove(8));
    Assert.assertEquals(2, (int)map.remove(1));
    Assert.assertEquals(Collections.singletonMap(5, 10), map);
    Assert.assertNull(map.remove(4));
  }
  
  @Test
  public void universeMapPutAll() {
    Universe<Integer> universe = Universe.universe(1, 3, 5, 8);
    Map<Integer, Integer> map = universe.newMap();
    Map<Integer, Integer> singletonMap = Collections.singletonMap(5, -5);
    map.putAll(singletonMap);
    Assert.assertEquals(singletonMap, map);
    
    Map<Integer, Integer> map2 = universe.newMap(1, 3, 5, 8);
    map.putAll(map2);
    Assert.assertEquals(map2, map);
  }
  
  @Test
  public void universeMapKeySet() {
    Universe<Integer> universe = new Universe<>();
    ArrayList<Integer> list = new ArrayList<>();
    for(int i=0; i<200; i++) {
      Integer integer = i;
      universe.add(integer);
      list.add(integer);
    }
    Map<Integer, Integer> map = universe.newMap(list.toArray(new Integer[0]));
    Assert.assertEquals(universe, map.keySet());
  }
  
  @Test
  public void universeMapEntrySet() {
    Universe<Integer> universe = new Universe<>();
    HashMap<Integer, Integer> hashMap = new HashMap<>();
    for(int i=0; i<200; i++) {
      Integer integer = i;
      hashMap.put(integer, i%10);
      universe.add(integer);
    }
    Map<Integer, Integer> map = universe.newMap(hashMap);
    Assert.assertEquals(hashMap.entrySet(), map.entrySet());
  }
  
  @Test
  public void universeMapEntrySetIteratorRemove() {
    Universe<Integer> universe = new Universe<>();
    HashMap<Integer, String> hashMap = new HashMap<>();
    for(int i=0; i<200; i++) {
      Integer integer = i;
      hashMap.put(integer, String.valueOf(i));
      universe.add(integer);
    }
    Map<Integer, String> map = universe.newMap(hashMap);
    for(Iterator<Entry<Integer, String>> it = map.entrySet().iterator(); it.hasNext();) {
      Entry<Integer, String> entry = it.next();
      if (entry.getValue().length() == 1) {
        it.remove();
        hashMap.remove(entry.getKey());
      }
    }
    Assert.assertEquals(hashMap, map);
  }
  
  @Test
  public void universeMapEntrysetEntry() {
    Universe<Integer> universe = new Universe<>();
    HashMap<Integer, Integer> hashMap = new HashMap<>();
    for(int i=0; i<200; i++) {
      Integer integer = i;
      hashMap.put(integer, integer);
      universe.add(integer);
    }
    Map<Integer, Integer> map = universe.newMap(hashMap);
    for(Entry<Integer, Integer> entry: map.entrySet()) {
      Assert.assertSame(entry.getKey(), entry.getValue());
      
      Integer value = entry.getKey() * 2;
      entry.setValue(value);
      Assert.assertEquals(value, entry.getValue());
      
      hashMap.put(entry.getKey(), value);
    }
    
    Assert.assertEquals(hashMap, map);
  }
}
