/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.collection.suite.maps;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Resource;
import net.ranides.assira.collection.sets.HashSet;
import net.ranides.assira.test.TMap;
import net.ranides.assira.test.TMap.TItem;
import net.ranides.assira.test.TMap.TItems;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.junit.TestContract;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
@SuppressWarnings("PMD.MethodNameRules")
public final class MapTester<K,V> {
        
    private static final int BASIC_PUT = 128;
    
    private static final int BASIC_PUT_REPLACE = 40;
    
    private static final int BASIC_PUT_RANDOM = 220;
    
    private static final int BASIC_REMOVE_ALL = 97;
    
    private static final int BASIC_REMOVE_RANDOM = 273;
    
    @Resource(name = "map!")
    private TMap<K,V> $var;
    
    @Resource(name = "map.default!")
    private V $default = null;
    
    @TestContract
    public void basicPut(Map<K, V> target) {
        for(TItem<K,V> item : $var.range(BASIC_PUT)) {
            target.put(item.key(), item.value());
        }
        assertEquals(BASIC_PUT, target.size());
    }

    @TestContract
    public void basicPut_HC(Map<K, V> target) {
        basicPut(target);
        
        int prev = target.size();
        TItem<K,V> item1 = $var.item(1001,0);
        TItem<K,V> item2 = $var.item(1001,1);
        target.put(item1.key(), item1.value());
        assertEquals(prev + 1, target.size());
        
        target.put(item1.key(), item1.value());
        assertEquals(prev + 1, target.size());
        
        target.put(item2.key(), item2.value()); // identical hash but not eq
        assertEquals(prev + 2, target.size());
    }
	
    @TestContract
    public void basicPutReplace_HC(Map<K, V> target) {
        int prev = target.size();
        for (TItem<K,V> item : $var.urange(0, BASIC_PUT_REPLACE)) {
            target.put(item.key(), item.value());
        }
        assertEquals(prev + (2 * BASIC_PUT_REPLACE), target.size());
    }
    
    @TestContract
    public void basicPutReplace(Map<K, V> target) {
        int prev = target.size();
        for (TItem<K,V> item : $var.range(BASIC_PUT_REPLACE)) {
            target.put(item.key(), item.value());
        }
        assertEquals(prev + BASIC_PUT_REPLACE, target.size());
    }
	
	@TestContract
    public void basicPutRandom_HC(Map<K, V> target) {
        int prev = target.size();
        for (TItem<K,V> item : $var.urange(0, BASIC_PUT_RANDOM)) {
            target.put(item.key(), item.value());
        }
        assertEquals(prev + (2 * BASIC_PUT_RANDOM), target.size());
    }
    
    @TestContract
    public void basicPutRandom(Map<K, V> target) {
        int prev = target.size();
        for (TItem<K,V> item : $var.range(0, BASIC_PUT_RANDOM)) {
            target.put(item.key(), item.value());
        }
        assertEquals(prev + BASIC_PUT_RANDOM, target.size());
    }
	    
    @TestContract
    public void basicRemove_HC(Map<K, V> target) {
        $var.list(1,2,3).into(target);
        
        assertNotRemove(target, $var.item(1,1));
        
        assertRemove(target, $var.item(1));
        assertRemove(target, $var.item(2));
        assertRemove(target, $var.item(3));
        
        assertEquals(0, target.size());
        
        assertNotRemove(target, $var.item(3));
    }
    
    @TestContract
    public void basicRemove(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3});
        
        assertNotRemove(target, $var.item(5));
        
        assertRemove(target, $var.item(1));
        assertRemove(target, $var.item(2));
        assertRemove(target, $var.item(3));
        
        assertEquals(0, target.size());
        
        assertNotRemove(target, $var.item(3));
    }
    
    @TestContract
    public void basicRemovePair(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3});
        
        assertNotRemove(target, $var.item(5));
        
        assertRemovePair(target,    $var.item(1).key(), $var.item(1).value());
        assertNotRemovePair(target, $var.item(2).key(), $var.item(41).value());
        assertNotRemovePair(target, $var.item(2).key(), new Object());
        assertRemovePair(target,    $var.item(2).key(), $var.item(2).value());
        assertRemovePair(target,    $var.item(3).key(), $var.item(3).value());
        
        assertEquals(0, target.size());
        
        assertNotRemovePair(target, $var.item(3).key(), $var.item(9).value());
        assertNotRemovePair(target, $var.item(3).key(), $var.item(3).value());
    }
   
    @TestContract
    public void basicRemoveAll(Function<int[], Map<K, V>> f) {
        TItems<K,V> items = $var.range(0, BASIC_REMOVE_ALL);
        Map<K,V> target = f.apply(items.indexes());
        
        assertEquals(BASIC_REMOVE_ALL, target.size());
        
        for(int i=0; i<BASIC_REMOVE_ALL; i++) {
            assertEquals(items.at(i).value(), target.remove(items.at(i).key()));
            assertEquals(BASIC_REMOVE_ALL-i-1, target.size());
        }
        assertEquals(0, target.size());
    }
    
    @TestContract
    public void basicRemoveRandom(Function<int[], Map<K, V>> f) {
        TItems<K,V> items = $var.range(0, BASIC_REMOVE_RANDOM);
        Map<K,V> target = f.apply(items.indexes());
        
        assertEquals(BASIC_REMOVE_RANDOM, target.size());

        items.shuffle(777);
        for(int i=0; i<BASIC_REMOVE_RANDOM; i++) {
            assertEquals(items.at(i).value(), target.remove(items.at(i).key()));
            assertEquals(BASIC_REMOVE_RANDOM-i-1, target.size());
        }
        assertEquals(0, target.size());
    }
    
    @TestContract
    public void basicGet_HC(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3});
        
        assertEquals(3, target.size());
        assertEquals($default, target.get($var.item(1,1).key()));
        
        assertEquals($var.item(1).value(), target.get($var.item(1).key()));
        assertEquals($var.item(2).value(), target.get($var.item(2).key()));
        assertEquals($var.item(3).value(), target.get($var.item(3).key()));
    }
    
    @TestContract
    public void basicGet(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3});
        
        assertEquals(3, target.size());
        assertEquals($default, target.get($var.item(4).key()));
        
        assertEquals($var.item(1).value(), target.get($var.item(1).key()));
        assertEquals($var.item(2).value(), target.get($var.item(2).key()));
        assertEquals($var.item(3).value(), target.get($var.item(3).key()));
    }
    
    @TestContract
    public void logicRemove_HC(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3});
        
        assertEquals(null, target.remove($var.item(1,1).key()));
        assertEquals(3, target.size());
		
        assertContains(target, $var.item(1));
        assertRemove(target, $var.item(1));
        assertNotContains(target, $var.item(1));
        
        assertContains(target, $var.item(2));
        assertRemove(target, $var.item(2));
        assertNotContains(target, $var.item(2));
        
        assertContains(target, $var.item(3));
        assertRemove(target, $var.item(3));
        assertNotContains(target, $var.item(3));
    }
    
    @TestContract
    public void logicRemove(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3});
        
        assertEquals(null, target.remove($var.item(5).key()));
        assertEquals(3, target.size());
		
        assertContains(target, $var.item(1));
        assertRemove(target, $var.item(1));
        assertNotContains(target, $var.item(1));
        
        assertContains(target, $var.item(2));
        assertRemove(target, $var.item(2));
        assertNotContains(target, $var.item(2));
        
        assertContains(target, $var.item(3));
        assertRemove(target, $var.item(3));
        assertNotContains(target, $var.item(3));
    }
    
    @TestContract
    public void basicContains_HC(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2});
        
        assertEquals(true, target.containsValue($var.item(1).value()));
        assertEquals(true, target.containsValue($var.item(2).value()));
        
        assertEquals(false, target.containsValue($var.item(1,1).value()));
        assertEquals(false, target.containsValue($var.item(3).value()));
    }
    
    @TestContract
    public void basicContains(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2});
        
        assertEquals(true, target.containsValue($var.item(1).value()));
        assertEquals(true, target.containsValue($var.item(2).value()));
        assertEquals(false, target.containsValue($var.item(3).value()));
    }
    
    @TestContract
    public void basicClear(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3});
        
		assertSize(3, target);
		assertEquals(false, target.isEmpty());
		
		target.clear();
		assertSize(0, target);
		assertEquals(true, target.isEmpty());
		
		target.clear();
		assertSize(0, target);
		assertEquals(true, target.isEmpty());
	}
	
	@TestContract
    public void basicClear_HC(Map<K, V> target) {
		assertEquals(0, target.size());

        $var.list().item(1).item(1,1).item(2).into(target);
		assertSize(3, target);
		assertEquals(false, target.isEmpty());
		
		target.clear();
		assertSize(0, target);
		assertEquals(true, target.isEmpty());
		
		target.clear();
		assertSize(0, target);
		assertEquals(true, target.isEmpty());
	}
    
    @TestContract
    public void basicEntrySet_Contains_HC(Map<K, V> target) {
        Set<Entry<K, V>> set = target.entrySet();
		
        assertEquals(0, set.size());
		
        $var.list().item(1).item(1,1).item(2).item(3).into(target);
		assertEquals(4, set.size());
        assertEquals(true, set.contains($var.item(1).entry()));
        assertEquals(true, set.contains($var.item(1,1).entry()));
        assertEquals(true, set.contains($var.item(2).entry()));
        assertEquals(true, set.contains($var.item(3).entry()));
        
        assertEquals(false, set.contains($var.item(4).entry()));
        assertEquals(false, set.contains(new Object()));
        
	}
    
    @TestContract
    public void basicEntrySet_Contains(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3,5});
        
        Set<Entry<K, V>> set = target.entrySet();
		
		assertEquals(4, set.size());
        assertEquals(true, set.contains($var.item(1).entry()));
        assertEquals(true, set.contains($var.item(2).entry()));
        assertEquals(true, set.contains($var.item(3).entry()));
        assertEquals(true, set.contains($var.item(5).entry()));
        
        assertEquals(false, set.contains($var.item(4).entry()));
        assertEquals(false, set.contains(new Object()));
        
	}
    
    @TestContract
    public void basicEntrySet_Remove(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3,7});
        
        Set<Entry<K, V>> set = target.entrySet();
				
        assertEquals(false, set.remove($var.item(1).entry(5)));
        assertEquals(true, set.remove($var.item(1).entry()));
        assertEquals(false, set.remove($var.item(4).entry()));
        assertEquals(false, set.remove(new Object()));
        
        assertEquals(false, set.contains($var.item(1).entry()));
        assertEquals(true, set.contains($var.item(2).entry()));
        assertEquals(true, set.contains($var.item(3).entry()));
        assertEquals(true, set.contains($var.item(7).entry()));
        
	}
    
    @TestContract
    public void basicEntrySet_Remove_HC(Map<K, V> target) {
        Set<Entry<K, V>> set = target.entrySet();
		
        $var.list().item(1).item(1,1).item(2).item(3).into(target);
		
        assertEquals(false, set.remove($var.item(1).entry(5)));
        assertEquals(true, set.remove($var.item(1).entry()));
        assertEquals(false, set.remove($var.item(4).entry()));
        assertEquals(false, set.remove(new Object()));
        
        assertEquals(false, set.contains($var.item(1).entry()));
        assertEquals(true, set.contains($var.item(1,1).entry()));
        assertEquals(true, set.contains($var.item(2).entry()));
        assertEquals(true, set.contains($var.item(3).entry()));
        
	}
    
    @TestContract
    public void basicEntrySet_Iterator_HC(Map<K, V> target) {
        Set<Entry<K, V>> set = target.entrySet();
		
        assertEquals(0, set.size());
		$var.list().item(1).item(1,1).item(2).item(3).into(target);
		assertEquals(4, set.size());

        Iterator<Entry<K, V>> iterator = set.iterator();
        while(iterator.hasNext()) {
            Entry<K, V> entry = iterator.next();
            assertEquals(target.get(entry.getKey()), entry.getValue());
        }
        
        assertThrows(NoSuchElementException.class, ()-> iterator.next());
	}
    
    @TestContract
    public void basicEntrySet_Iterator(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3,5});
        
        Set<Entry<K, V>> set = target.entrySet();
		
        assertEquals(4, set.size());

        Iterator<Entry<K, V>> iterator = set.iterator();
        while(iterator.hasNext()) {
            Entry<K, V> entry = iterator.next();
            assertEquals(target.get(entry.getKey()), entry.getValue());
        }
        
        assertThrows(NoSuchElementException.class, ()-> iterator.next());
	}
    
    @TestContract
    public void basicEntrySet_Iterator_SetValue_HC(Map<K, V> target) {
        Set<Entry<K, V>> set = target.entrySet();
                
		assertEquals(0, set.size());
		$var.list().item(1).item(1,1).item(2).item(3).into(target);
        K k1 = $var.item(1).key();
		K k2 = $var.item(2).key();
        V v1 = $var.item(101).value();
        V v2 = $var.item(102).value();
		
		Iterator<Entry<K, V>> iterator = set.iterator();
		while(iterator.hasNext()) {
			Entry<K, V> entry = iterator.next();
			if(k1.equals(entry.getKey())) {
                entry.setValue(v1);
            }
            if(k2.equals(entry.getKey())) {
                entry.setValue(v2);
            }
		}
		assertContains(target, $var.item(1).key(), v1);
		assertContains(target, $var.item(2).key(), v2);
		assertContains(target, $var.item(1,1));
		assertContains(target, $var.item(3));
	}
    
    @TestContract
    public void basicEntrySet_Iterator_SetValue(Map<K, V> target) {
        Set<Entry<K, V>> set = target.entrySet();
                
		assertEquals(0, set.size());
		$var.list(1,2,3,5).into(target);
        K k1 = $var.item(1).key();
		K k2 = $var.item(2).key();
        V v1 = $var.item(101).value();
        V v2 = $var.item(102).value();
		
		Iterator<Entry<K, V>> iterator = set.iterator();
		while(iterator.hasNext()) {
			Entry<K, V> entry = iterator.next();
			if(k1.equals(entry.getKey())) {
                entry.setValue(v1);
            }
            if(k2.equals(entry.getKey())) {
                entry.setValue(v2);
            }
		}
		assertContains(target, $var.item(1).key(), v1);
		assertContains(target, $var.item(2).key(), v2);
		assertContains(target, $var.item(5));
		assertContains(target, $var.item(3));
	}
    
    @TestContract
    public void basicEntrySet_Iterator_Remove_HC(Map<K, V> target) {
        Set<Entry<K, V>> set = target.entrySet();
        
		assertEquals(0, set.size());
		$var.list().item(1).item(1,1).item(2).item(3).into(target);
        K k1 = $var.item(1).key();
		K k2 = $var.item(2).key();
		
		Iterator<Entry<K, V>> iterator = set.iterator();
		while(iterator.hasNext()) {
			Entry<K, V> entry = iterator.next();
            K key = entry.getKey();
            
			if(k1.equals(key)) {
                iterator.remove();
            }
            if(k2.equals(key)) {
                iterator.remove();
                assertThrows(IllegalStateException.class, ()-> iterator.remove());
            }
		}
        
        assertEquals(2, target.size());
        assertNotContains(target, $var.item(1));
        assertNotContains(target, $var.item(2));
		assertContains(target, $var.item(1,1));
		assertContains(target, $var.item(3));
        
        set.clear();
        assertEquals(0, target.size());
	}
    
    @TestContract
    public void basicEntrySet_Iterator_Remove(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3,5});
        
        Set<Entry<K, V>> set = target.entrySet();
        
        K k1 = $var.item(1).key();
		K k2 = $var.item(2).key();
		
		Iterator<Entry<K, V>> iterator = set.iterator();
		while(iterator.hasNext()) {
			Entry<K, V> entry = iterator.next();
            K key = entry.getKey();
            
			if(k1.equals(key)) {
                iterator.remove();
            }
            if(k2.equals(key)) {
                iterator.remove();
                assertThrows(IllegalStateException.class, ()-> iterator.remove());
            }
		}
        
        assertEquals(2, target.size());
        assertNotContains(target, $var.item(1));
        assertNotContains(target, $var.item(2));
		assertContains(target, $var.item(3));
		assertContains(target, $var.item(5));
        
        set.clear();
        assertEquals(0, target.size());
	}
    
    @TestContract
    public void basicEntrySet_Iterator_RemoveAll__HC(Map<K, V> target) {
        Set<Entry<K, V>> set = target.entrySet();
        
		assertEquals(0, set.size());
        $var.urange(1024).into(target);
            
		Iterator<Entry<K, V>> iterator = set.iterator();
		while(iterator.hasNext()) {
			iterator.next();
            iterator.remove();
		}
        assertEquals(0, target.size());
	}
    
    @TestContract
    public void basicEntrySet_Iterator_RemoveAll(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply($var.range(1024).indexes());
        Set<Entry<K, V>> set = target.entrySet();
                    
		Iterator<Entry<K, V>> iterator = set.iterator();
		while(iterator.hasNext()) {
			iterator.next();
            iterator.remove();
		}
        assertEquals(0, target.size());
	}
    
    @TestContract
    public void basicEquals_HC(Supplier<Map<K, V>> generator) {
        Map<K, V> map1 = generator.get();
        Map<K, V> map2 = generator.get();
        Map<K, V> map3 = generator.get();
        Map<K, V> map4 = generator.get();
        Map<K, V> map5 = generator.get();
        
        $var.list().item(5,0).item(2).item(3).into(map1);
        $var.list().item(5,0).item(2).item(3).into(map2);
        $var.list().item(5,1).item(2).item(3).into(map3);
        $var.list()          .item(2).item(3).into(map4);
        $var.list().item(5,1)        .item(3).item(4).into(map5);
        
        // entry(5,1) should have identical value as entry(5,0) because we want identical hashcode
        map3.put($var.item(5,1).key(), $var.item(5,0).value());

        assertEquality(map1, map2, map3, map4);
        assertEquality(map1, map2, map3, map5);
    }
    
    @TestContract
    public void basicEquals(Function<int[], Map<K, V>> f) {
        Map<K, V> map1 = f.apply(new int[]{5,2,3});
        Map<K, V> map2 = f.apply(new int[]{5,2,3});
        Map<K, V> map4 = f.apply(new int[]{2,3});
        Map<K, V> map5 = f.apply(new int[]{7,3,4});
        
        assertEquality(map1, map2, map4);
        assertEquality(map1, map2, map5);
    }
    
    @TestContract
    public void basicEquals_Entry_HC(Map<K, V> target) {
        $var.list().item(5).item(5).item(5,1).item(2).into(target);
        
        // entry(5,1) should have identical value as entry(5,0) because we want identical hashcode
        target.put($var.item(5,1).key(), $var.item(5,0).value());
        
        assertEquality(
            findEntry(target, $var.item(5).key()), 
            findEntry(target, $var.item(5).key()), 
            findEntry(target, $var.item(5,1).key()), 
            findEntry(target, $var.item(2).key())
        );
    }
    
    @TestContract
    public void basicEquals_Entry(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{5,5,6,2});
        
		Entry<K,V> m1 = findEntry(target, $var.item(5).key());
		Entry<K,V> m2 = findEntry(target, $var.item(5).key());
		Entry<K,V> m3 = findEntry(target, $var.item(2).key());
		Entry<K,V> e1 = $var.item(5).entry();
		Entry<K,V> e2 = $var.item(5).entry();
		Entry<K,V> e3 = $var.item(2).entry();
		
        assertEquality(m1,m2,m3);
		
		assertTrue( m1.equals(m1));
		assertTrue( m1.equals(m2));
		assertTrue( m1.equals(e1));
		assertTrue( m1.equals(e2));
		
		assertFalse( m1.equals(m3));
		assertFalse( m1.equals(e3));
		assertFalse( m1.equals("qq"));
    }
    
    private Entry<K,V> findEntry(Map<K, V> target, K key) {
        for(Entry<K,V> e : target.entrySet()) {
            if(key.equals(e.getKey())) {
                return e;
            }
        }
        fail("Entry not found: " + key);
        return null;
    }
    
    @TestContract
    public void basicSerializable_HC(Map<K, V> target) {
        $var.list().item(1).item(1,1).item(2).item(3).into(target);
        assertSerializable(target);
    }
    
    @TestContract
    public void basicSerializable(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3,4});
        assertSerializable(target);
    }
    
    @TestContract
    public void basicKeySet_Contains_HC(Map<K, V> target) {
        Set<K> set = target.keySet();
		
        assertEquals(0, set.size());
		
        $var.list().item(1).item(1,1).item(2).item(3).into(target);
		assertEquals(4, set.size());
        assertEquals(true, set.contains($var.item(1).key()));
        assertEquals(true, set.contains($var.item(1,1).key()));
        assertEquals(true, set.contains($var.item(2).key()));
        assertEquals(true, set.contains($var.item(3).key()));
        
        assertEquals(false, set.contains($var.item(5).key()));
        assertEquals(false, set.contains(new Object()));
	}
    
    @TestContract
    public void basicKeySet_Contains(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3,5});
        Set<K> set = target.keySet();
		
		assertEquals(4, set.size());
        assertEquals(true, set.contains($var.item(1).key()));
        assertEquals(true, set.contains($var.item(2).key()));
        assertEquals(true, set.contains($var.item(3).key()));
        assertEquals(true, set.contains($var.item(5).key()));
        
        assertEquals(false, set.contains($var.item(6).key()));
        assertEquals(false, set.contains(new Object()));
	}
    
    @TestContract
    public void basicKeySet_Remove_HC(Map<K, V> target) {
        Set<K> set = target.keySet();
		
        basicEntrySet_Contains_HC(target);
		
        assertEquals(true, set.remove($var.item(1).key()));
        assertEquals(false, set.remove($var.item(4).key()));
        assertEquals(false, set.remove(new Object()));
        
        assertEquals(false, set.contains($var.item(1).key()));
        assertEquals(true, set.contains($var.item(1,1).key()));
        assertEquals(true, set.contains($var.item(2).key()));
        assertEquals(true, set.contains($var.item(3).key()));
	}
    
    @TestContract
    public void basicKeySet_Remove(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3,5});
        Set<K> set = target.keySet();
				
        assertEquals(true, set.remove($var.item(1).key()));
        assertEquals(false, set.remove($var.item(4).key()));
        assertEquals(false, set.remove(new Object()));
        
        assertEquals(false, set.contains($var.item(1).key()));
        assertEquals(true, set.contains($var.item(5).key()));
        assertEquals(true, set.contains($var.item(2).key()));
        assertEquals(true, set.contains($var.item(3).key()));
	}
        
    @TestContract
    public void basicKeySet_Iterator_HC(Map<K, V> target) {
        Set<K> set = target.keySet();
		
        assertEquals(0, set.size());
		$var.list().item(1).item(1,1).item(2).item(3).into(target);
		assertEquals(4, set.size());

        Iterator<K> iterator = set.iterator();
        while(iterator.hasNext()) {
            K key = iterator.next();
            assertEquals(true, target.containsKey(key));
        }
        
        assertThrows(NoSuchElementException.class, ()-> iterator.next());
	}
    
    @TestContract
    public void basicKeySet_Iterator(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3,5});
        Set<K> set = target.keySet();
		
		assertEquals(4, set.size());

        Iterator<K> iterator = set.iterator();
        while(iterator.hasNext()) {
            K key = iterator.next();
            assertEquals(true, target.containsKey(key));
        }
        
        assertThrows(NoSuchElementException.class, ()-> iterator.next());
	}
    
    @TestContract
    public void basicKeySet_Iterator_Remove_HC(Map<K, V> target) {
        Set<K> set = target.keySet();
        
		assertEquals(0, set.size());
		$var.list().item(1).item(1,1).item(2).item(3).into(target);
		K k1 = $var.item(1).key();
		K k2 = $var.item(2).key();
        
		Iterator<K> iterator = set.iterator();
		while(iterator.hasNext()) {
			K key = iterator.next();
			if(k1.equals(key)) {
                iterator.remove();
            }
            if(k2.equals(key)) {
                iterator.remove();
                assertThrows(IllegalStateException.class, ()-> iterator.remove());
            }
		}
        
        assertEquals(2, target.size());
        assertNotContains(target, $var.item(1));
        assertNotContains(target, $var.item(2));
		assertContains(target, $var.item(1,1));
		assertContains(target, $var.item(3));
        
        set.clear();
        assertEquals(0, target.size());
	}
    
    @TestContract
    public void basicKeySet_Iterator_Remove(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3,5});
        Set<K> set = target.keySet();
        
		K k1 = $var.item(1).key();
		K k2 = $var.item(2).key();
        
		Iterator<K> iterator = set.iterator();
		while(iterator.hasNext()) {
			K key = iterator.next();
			if(k1.equals(key)) {
                iterator.remove();
            }
            if(k2.equals(key)) {
                iterator.remove();
                assertThrows(IllegalStateException.class, ()-> iterator.remove());
            }
		}
        
        assertEquals(2, target.size());
        assertNotContains(target, $var.item(1));
        assertNotContains(target, $var.item(2));
		assertContains(target, $var.item(5));
		assertContains(target, $var.item(3));
        
        set.clear();
        assertEquals(0, target.size());
	}
    
    @TestContract
    public void randomKeySet_Iterator_Remove(Function<int[], Map<K, V>> f) {
        Random rand = new Random(777);
        for(int i=0; i<100; i++) {
            randomKeySet_Iterator_Remove(f,rand.nextInt(1000));
        }
        randomKeySet_Iterator_Remove(f,32);
        randomKeySet_Iterator_Remove(f,128);
        randomKeySet_Iterator_Remove(f,273);
        randomKeySet_Iterator_Remove(f,1027);
        randomKeySet_Iterator_Remove(f,10000);
	}
    
    private void randomKeySet_Iterator_Remove(Function<int[], Map<K, V>> f, int size) {
        // HashMaps mają przekombinowaną procedurę usuwania pozycji
        // nie możemy napisać jakiegoś testu, który sprawdzałby jej poprawność
        // bez totalnie sztucznego podstawiania kluczy i hashcode'ów
        //
        // taka metoda byłaby bezużyteczna z praktycznego punktu widzenia
        // bo testowałaby tylko jedną konkretną implementację dla jednego konkretnego rozmiaru
        // usuwamy więc losowo z dużej mapy, licząc, że większość kodu ruszy do akcji
        Map<K,V> target = f.apply($var.range(size).indexes());
        Set<K> set = target.keySet();
        
        Set<K> keys = new HashSet<K>(set);
        Random rand = new Random(777);
        
        for(int i=0; i<10; i++) {
            Iterator<K> iterator = set.iterator();
            while(iterator.hasNext()) {
                K key = iterator.next();
                if( rand.nextBoolean() ) {
                    iterator.remove();
                    keys.remove(key);
                }
            }
            assertEquals(keys.size(), set.size());
            assertContentEquals(keys, set);
        }

	}
    
    @TestContract
    public void basicValues_Contains_HC(Map<K, V> target) {
        Collection<V> set = target.values();
		
        assertEquals(0, set.size());
		
        $var.list().item(1).item(1,1).item(2).item(3).into(target);
		assertEquals(4, set.size());
        assertEquals(true, set.contains($var.item(1).value()));
        assertEquals(true, set.contains($var.item(1,1).value()));
        assertEquals(true, set.contains($var.item(2).value()));
        assertEquals(true, set.contains($var.item(3).value()));
        
        assertEquals(false, set.contains($var.item(4).value()));
        assertEquals(false, set.contains(new Object()));
	}
    @TestContract
    public void basicValues_Contains(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3,5});
        Collection<V> set = target.values();
		
		assertEquals(4, set.size());
        assertEquals(true, set.contains($var.item(1).value()));
        assertEquals(true, set.contains($var.item(2).value()));
        assertEquals(true, set.contains($var.item(3).value()));
        assertEquals(true, set.contains($var.item(5).value()));
        
        assertEquals(false, set.contains($var.item(4).value()));
        assertEquals(false, set.contains(new Object()));
	}
    
    @TestContract
    public void basicValues_Remove_HC(Map<K, V> target) {
        Collection<V> set = target.values();
		
        basicValues_Contains_HC(target);
		
        assertEquals(true, set.remove($var.item(1).value()));
        assertEquals(false, set.remove($var.item(4).value()));
        assertEquals(false, set.remove(new Object()));
        
        assertEquals(false, set.contains($var.item(1).value()));
        assertEquals(true, set.contains($var.item(1,1).value()));
        assertEquals(true, set.contains($var.item(2).value()));
        assertEquals(true, set.contains($var.item(3).value()));
	}
    
    @TestContract
    public void basicValues_Remove(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3,5});
        Collection<V> set = target.values();
				
        assertEquals(true, set.remove($var.item(1).value()));
        assertEquals(false, set.remove($var.item(4).value()));
        assertEquals(false, set.remove(new Object()));
        
        assertEquals(false, set.contains($var.item(1).value()));
        assertEquals(true, set.contains($var.item(5).value()));
        assertEquals(true, set.contains($var.item(2).value()));
        assertEquals(true, set.contains($var.item(3).value()));
	}
    
    @TestContract
    public void basicValues_Iterator_HC(Map<K, V> target) {
        Collection<V> set = target.values();
		
        assertEquals(0, set.size());
		$var.list().item(1).item(1,1).item(2).item(3).into(target);
		assertEquals(4, set.size());

        Iterator<V> iterator = set.iterator();
        while(iterator.hasNext()) {
            V var = iterator.next();
            assertEquals(true, target.containsValue(var));
        }
        
        assertThrows(NoSuchElementException.class, ()-> iterator.next());
	}
    
    @TestContract
    public void basicValues_Iterator(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3,5});
        Collection<V> set = target.values();

		assertEquals(4, set.size());

        Iterator<V> iterator = set.iterator();
        while(iterator.hasNext()) {
            V var = iterator.next();
            assertEquals(true, target.containsValue(var));
        }
        
        assertThrows(NoSuchElementException.class, ()-> iterator.next());
	}
    
    @TestContract
    public void basicValues_Iterator_Remove_HC(Map<K, V> target) {
        Collection<V> set = target.values();
        
		assertEquals(0, set.size());
		$var.list().item(1).item(1,1).item(2).item(3).into(target);
		
		Iterator<V> iterator = set.iterator();
		while(iterator.hasNext()) {
			V var = iterator.next();
			
            if(var.equals($var.item(1).value())) {
                iterator.remove();
            }
            if(var.equals($var.item(2).value())) {
                iterator.remove();
                assertThrows(IllegalStateException.class, ()-> iterator.remove());
            }
		}
        
        assertEquals(2, target.size());
        assertNotContains(target, $var.item(1));
        assertNotContains(target, $var.item(2));
		assertContains(target, $var.item(1,1));
		assertContains(target, $var.item(3));
        
        set.clear();
        assertEquals(0, target.size());
	}
    
    @TestContract
    public void basicValues_Iterator_Remove(Function<int[], Map<K, V>> f) {
        Map<K,V> target = f.apply(new int[]{1,2,3,5});
        Collection<V> set = target.values();

        Iterator<V> iterator = set.iterator();
        while(iterator.hasNext()) {
            V var = iterator.next();

            if(var.equals($var.item(1).value())) {
                iterator.remove();
            }
            if(var.equals($var.item(2).value())) {
                iterator.remove();
                assertThrows(IllegalStateException.class, ()-> iterator.remove());
            }
        }

        assertEquals(2, target.size());
        assertNotContains(target, $var.item(1));
        assertNotContains(target, $var.item(2));
        assertContains(target, $var.item(3));
        assertContains(target, $var.item(5));

        set.clear();
        assertEquals(0, target.size());
	}
    
    private void assertRemove(Map<K,V> target, TItem<K,V> item) {
        int size = target.size();
        assertEquals(item.value(), target.remove(item.key()));
        assertEquals(size-1, target.size());
    }
    
    private void assertNotRemove(Map<K,V> target, TItem<K,V> item) {
        int size = target.size();
        assertEquals(null, target.remove(item.key()));
        assertEquals(size, target.size());
    }
    
    private void assertRemovePair(Map<K,V> target, K key, Object value) {
        int size = target.size();
        assertTrue(target.remove(key, value));
        assertEquals(size-1, target.size());
    }
    
    private void assertNotRemovePair(Map<K,V> target, K key, Object value) {
        int size = target.size();
        assertFalse(target.remove(key, value));
        assertEquals(size, target.size());
    }
	
	private void assertContains(Map<K,V> target, K key, V value) {
        assertEquals(true, target.containsKey(key));
        assertEquals(value, target.get(key));
    }
    
    private void assertContains(Map<K,V> target, TItem<K,V> item) {
		assertContains(target, item.key(), item.value());
    }
    
    private void assertNotContains(Map<K,V> target, TItem<K,V> item) {
        assertEquals(false, target.containsKey(item.key()));
        assertEquals($default, target.get(item.key()));
    }
	
	private void assertSize(int size, Map<K,V> target) {
		assertEquals(size, target.size());
		assertEquals(size, target.entrySet().size());
		assertEquals(size, target.keySet().size());
		assertEquals(size, target.values().size());
	}
    
    private static <Q> void assertContentEquals(Collection<Q> a, Collection<Q> b) {
        assertTrue("assertContentEquals", a.containsAll(b) && b.containsAll(a));
    }
    
}
