package fr.umlv.exam2.test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Random;

import junit.framework.Assert;

import org.junit.Test;

import fr.umlv.exam2.MultiMap;
import fr.umlv.exam2.MultiMaps;


public class MultiMapTest {
  @Test(expected=NullPointerException.class)
  public void nullAdd() {
    MultiMaps.newMultiMap().add(null, "foo");
  }
  
  @Test(expected=NullPointerException.class)
  public void nullAdd2() {
    MultiMaps.newMultiMap().add("bar", null);
  }
  
  @Test
  public void add() {
    MultiMap<String, String> multiMap = MultiMaps.newMultiMap();
    Assert.assertEquals(0, multiMap.size());
    multiMap.add("foo", "bar");
    Assert.assertEquals(1, multiMap.size());
    multiMap.add("foo", "baz");
    Assert.assertEquals(2, multiMap.size());
    multiMap.add("foo", "bar");
    Assert.assertEquals(3, multiMap.size());
    Assert.assertEquals(Arrays.asList("bar", "baz", "bar"), multiMap.get("foo"));
  }
  
  @Test(expected=NullPointerException.class)
  public void nullGet() {
    MultiMaps.newMultiMap().get(null);
  }
  
  @Test
  public void emptyGet() {
    MultiMap<Object, Object> multiMap = MultiMaps.newMultiMap();
    Assert.assertTrue(multiMap.get("foo").isEmpty());
  }
  
  @Test
  public void getAndAdd() {
    MultiMap<Integer, Integer> multiMap = MultiMaps.newMultiMap();
    for(int i=0; i<1000; i++) {
      multiMap.add(i%10, i);
    }

    Assert.assertEquals(1000, multiMap.size());

    for(int i=0; i<1000; i++) {
      List<Integer> set = multiMap.get(i%10);
      for(int value: set) {
        Assert.assertEquals(i%10, value%10);
      }
    }
  }
  
  @Test
  public void asMap() {
    MultiMap<String, Integer> multiMap = MultiMaps.newMultiMap();
    for(int i=0; i<1000; i++) {
      multiMap.add(Integer.toString(i%10), i);
    }
    
    Map<String, List<Integer>> map = multiMap.asMap();
    Assert.assertEquals(10, map.size());
    Assert.assertEquals(10, map.keySet().size());
    Assert.assertEquals(10, map.entrySet().size());
    Assert.assertEquals(new HashSet<>(Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")), map.keySet());
  }
  
  @Test
  public void asMapView() {
    MultiMap<String, Integer> multiMap = MultiMaps.newMultiMap();
    Map<String, List<Integer>> map = multiMap.asMap();
    
    multiMap.add("foo", 2);
    multiMap.add("foo", 42);
    
    Assert.assertEquals(Arrays.asList(2, 42), map.get("foo"));
  }
  
  @Test(expected=UnsupportedOperationException.class)
  public void asMapPut() {
    MultiMaps.newMultiMap().asMap().put("foo", Collections.emptyList());
  }
  
  @Test(expected=UnsupportedOperationException.class)
  public void asMapRemove() {
    MultiMap<String, String> multiMap = MultiMaps.newMultiMap();
    multiMap.add("foo", "bar");
    multiMap.asMap().remove("foo");
  }
  
  @Test
  public void asMapKeySet() {
    MultiMap<String, Integer> multiMap = MultiMaps.newMultiMap();
    multiMap.add("foo", 2);
    multiMap.add("foo", 42);
    
    Assert.assertEquals(Collections.singleton("foo"), multiMap.asMap().keySet());
  }
  
  @Test(expected=UnsupportedOperationException.class)
  public void asMapKeySet2() {
    MultiMap<String, Integer> multiMap = MultiMaps.newMultiMap();
    multiMap.add("foo", 2);
    multiMap.add("foo", 42);
    
    multiMap.asMap().keySet().remove("foo");
  }
  
  @Test
  public void asMapEntrySet() {
    MultiMap<String, Integer> multiMap = MultiMaps.newMultiMap();
    multiMap.add("foo", 2);
    multiMap.add("foo", 42);
    
    Iterator<Entry<String, List<Integer>>> it = multiMap.asMap().entrySet().iterator();
    Assert.assertTrue(it.hasNext());
    
    Entry<String, List<Integer>> entry = it.next();
    Assert.assertEquals("foo", entry.getKey());
    Assert.assertEquals(Arrays.asList(2, 42), entry.getValue());
  }
  
  @Test
  public void asMapValuesIterator() {
    MultiMap<Integer, Integer> multiMap = MultiMaps.newMultiMap();
    for(int i=0; i<60; i++) {
      multiMap.add(i%11, i);
    }
    
    Iterator<List<Integer>> it = multiMap.asMap().values().iterator();
    for(int i=0; i<11; i++) {
      if (i%3 == 0) {
        for(int j=0; j<5; j++) {
          Assert.assertTrue(it.hasNext());
        }
      }
      
      List<Integer> list = it.next();
      for(int value: list) {
        Assert.assertEquals(i, value%11);
      }
    }
    Assert.assertFalse(it.hasNext());
    try {
      it.next();
      Assert.fail();
    } catch(NoSuchElementException e) {
      // do nothing
    }
  }
  
  @Test
  public void asMapKeySetIterator2() {
    MultiMap<String, Integer> multiMap = MultiMaps.newMultiMap();
    multiMap.add("foo", 2);
    multiMap.add("foo", 42);
    
    Iterator<String> it = multiMap.asMap().keySet().iterator();
    Assert.assertTrue(it.hasNext());
    
    Assert.assertEquals("foo", it.next());
    try {
      it.remove();
      Assert.fail();
    } catch(UnsupportedOperationException e) {
      // nothing
    }
  }
  
  @Test(expected=UnsupportedOperationException.class)
  public void asMapEntrySetIterator() {
    MultiMap<String, Integer> multiMap = MultiMaps.newMultiMap();
    multiMap.add("foo", 2);
    multiMap.add("foo", 42);
    
    Entry<String, List<Integer>> entry = multiMap.asMap().entrySet().iterator().next();
    entry.setValue(Collections.singletonList(23));
  }
  
  @Test
  public void addAll() {
    MultiMap<Integer, Integer> multiMap = MultiMaps.newMultiMap();
    multiMap.add(1, 2);
    MultiMap<Object, Integer> multiMap2 = MultiMaps.newMultiMap();
    multiMap2.add(2, 3);
    multiMap2.addAll(multiMap);
    Assert.assertEquals(2, multiMap2.size());
    Assert.assertEquals(Collections.singletonList(2), multiMap2.get(1));
    Assert.assertEquals(Collections.singletonList(3), multiMap2.get(2));
  }
  
  @Test
  public void addAll2() {
    MultiMap<String, String> multiMap = MultiMaps.newMultiMap();
    multiMap.add("booh", "hoob");
    MultiMap<Object, String> multiMap2 = MultiMaps.newMultiMap();
    multiMap2.add("booh", "bar");
    multiMap2.addAll(multiMap);
    Assert.assertEquals(2, multiMap2.size());
    Assert.assertEquals(Arrays.asList("bar", "hoob"), multiMap2.get("booh"));
  }
  
  @Test
  public void iterator() {
    Random random = new Random(0);
    MultiMap<Integer, Integer> multiMap = MultiMaps.newMultiMap();
    for(int i=0; i<100; i++) {
      multiMap.add(random.nextInt(10), i);
    }
    ArrayList<Integer> list = new ArrayList<>();
    Iterator<Integer> it = multiMap.iterator();
    for(int i=0; i<multiMap.size(); i++) {
      if (i%7 == 0) {
        for(int j=0; j<10; j++) {
          Assert.assertTrue(it.hasNext());
        }
      }
      list.add(it.next());
    }
    Assert.assertFalse(it.hasNext());
    
    Collections.sort(list);
    for(int i=0; i<100; i++) {
      Assert.assertEquals(i, (int)list.get(i));
    }
  }
  
  @Test(expected=NoSuchElementException.class)
  public void iterator2() {
    MultiMap<Integer, Integer> multiMap = MultiMaps.newMultiMap();
    multiMap.iterator().next();
  }
  
  @Test(expected=IllegalStateException.class)
  public void iteratorRemove() {
    MultiMap<Object, Object> multiMap = MultiMaps.newMultiMap();
    Iterator<Object> it = multiMap.iterator();
    it.remove();
  }
  
  @Test
  public void iteratorRemove2() {
    MultiMap<Object, Object> multiMap = MultiMaps.newMultiMap();
    multiMap.add("foo", 3);
    Iterator<Object> it = multiMap.iterator();
    it.next();
    it.remove();
    Assert.assertFalse(it.hasNext());
    Assert.assertEquals(0, multiMap.size());
  }
}
