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

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Resource;
import net.ranides.assira.collection.IntCollection;
import net.ranides.assira.collection.iterators.IntIterator;
import net.ranides.assira.collection.lookups.Lookup;
import net.ranides.assira.collection.lookups.Lookup.LookupEntry;
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 LookupTester<K> {
    
    @Resource(name = "map!")
    private TMap<K,Integer> $var;
	
	@Resource(name = "lookup.default!")
    private int $default = 0;
	
	private static final int DEFAULT = -4;
	
    private static final int BASIC_PUT_MANY = 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;
    
	@TestContract
    public void basicPut(Lookup<K> target) {
        int prev = target.size();
        for(TItem<K,?> item :$var.range(0, BASIC_PUT_MANY)) {
            target.put(item.key(), item.valueInt());
        }
        assertEquals(prev + BASIC_PUT_MANY, target.size());
    }
	
    @TestContract
    public void basicPut_HC(Lookup<K> target) {
        int prev = target.size();
        for(TItem<K,?> item :$var.range(0, BASIC_PUT_MANY)) {
            target.put(item.key(), item.valueInt());
        }
        assertEquals(prev + BASIC_PUT_MANY, target.size());
        
        logicPut_HC(target);
    }
	
	@TestContract
    public void logicPut(Lookup<K> target) {
        int prev = target.size();
        TItem<K,Integer> item1 = $var.item(1002);
        TItem<K,Integer> item2 = $var.item(1003);
        target.put(item1.key(), item1.valueInt());
        assertEquals(prev + 1, target.size());
        
        target.put(item1.key(), item1.valueInt());
        assertEquals(prev + 1, target.size());
        
        target.put(item2.key(), item2.valueInt()); // identical hash but not eq
        assertEquals(prev + 2, target.size());
    }
    
	@TestContract
    public void logicPut_HC(Lookup<K> target) {
        int prev = target.size();
        TItem<K,Integer> item1 = $var.item(1001,0);
        TItem<K,Integer> item2 = $var.item(1001,1);
        target.put(item1.key(), item1.valueInt());
        assertEquals(prev + 1, target.size());
        
        target.put(item1.key(), item1.valueInt());
        assertEquals(prev + 1, target.size());
        
        target.put(item2.key(), item2.valueInt()); // identical hash but not eq
        assertEquals(prev + 2, target.size());
    }
	
	@TestContract
    public void basicPutReplace(Lookup<K> target) {
        int prev = target.size();
        for (TItem<K,Integer> item : $var.range(0, BASIC_PUT_REPLACE).shuffle(777)) {
            target.put(item.key(), item.valueInt());
        }
        assertEquals(prev + BASIC_PUT_REPLACE, target.size());
    }
    
    @TestContract
    public void basicPutReplace_HC(Lookup<K> target) {
        int prev = target.size();
        for (TItem<K,Integer> item : $var.urange(0, BASIC_PUT_REPLACE)) {
            target.put(item.key(), item.valueInt());
        }
        assertEquals(prev + (2 * BASIC_PUT_REPLACE), target.size());
    }
	
	@TestContract
    public void basicPutRandom(Lookup<K> target) {
        int prev = target.size();
        for (TItem<K,Integer> item : $var.range(0, BASIC_PUT_RANDOM).shuffle(666)) {
            target.put(item.key(), item.valueInt());
        }
        assertEquals(prev + BASIC_PUT_RANDOM, target.size());
    }
	
	@TestContract
    public void basicPutRandom_HC(Lookup<K> target) {
        int prev = target.size();
        for (TItem<K,Integer> item : $var.urange(0, BASIC_PUT_RANDOM)) {
            target.put(item.key(), item.valueInt());
        }
        assertEquals(prev + (2 * BASIC_PUT_RANDOM), target.size());
    }
	    
    @TestContract
    public void basicRemove(Function<int[], Lookup<K>> f) {
        Lookup<K> target = f.apply(new int[]{1,2,3});
        
        target.defaultReturnValue(DEFAULT);
        
        assertNotRemove(target, $var.item(7));
        
        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_HC(Function<int[], Lookup<K>> f) {
        Lookup<K> target = f.apply(new int[]{1,2,3});
        
        target.defaultReturnValue(DEFAULT);
        
        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 basicRemoveAll(Function<int[], Lookup<K>> f) {
        TItems<K,Integer> items = $var.range(0, BASIC_REMOVE_ALL);
        Lookup<K> target = f.apply(items.indexes());

        assertEquals(BASIC_REMOVE_ALL, target.size());
        
        for(int i=0; i<BASIC_REMOVE_ALL; i++) {
            assertEquals(items.at(i).valueInt(), target.removeInt(items.at(i).key()));
            assertEquals(BASIC_REMOVE_ALL-i-1, target.size());
        }
        assertEquals(0, target.size());
    }
    
    @TestContract
    public void basicRemoveRandom(Function<int[], Lookup<K>> f) {
        TItems<K,Integer> items = $var.range(0, BASIC_REMOVE_RANDOM);
        Lookup<K> 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).valueInt(), target.removeInt(items.at(i).key()));
            assertEquals(BASIC_REMOVE_RANDOM-i-1, target.size());
        }
        assertEquals(0, target.size());
    }
	
	@TestContract
    public void basicGet(Function<int[], Lookup<K>> f) {
        Lookup<K> target0 = f.apply(new int[0]);
        target0.defaultReturnValue(DEFAULT);
        assertEquals(DEFAULT, target0.getInt($var.item(1).key()));

        Lookup<K> target1 = f.apply(new int[]{1,2,3});
        assertEquals(3, target1.size());
        target1.defaultReturnValue(DEFAULT);
        assertEquals(DEFAULT, target1.getInt($var.item(7).key()));
        
        assertEquals($var.item(1).valueInt(), target1.getInt($var.item(1).key()));
        assertEquals($var.item(2).valueInt(), target1.getInt($var.item(2).key()));
        assertEquals($var.item(3).valueInt(), target1.getInt($var.item(3).key()));
		
		Lookup<K> target2 = f.apply(new int[]{1,2,3});
        assertEquals($default, target2.getInt($var.item(7).key()));
    }
    
    @TestContract
    public void basicGet_HC(Supplier<Lookup<K>> f) {
		Lookup<K> target1 = f.get();
        target1.defaultReturnValue(DEFAULT);
        assertEquals(DEFAULT, target1.getInt($var.item(1).key()));

        $var.list(1,2,3).into(target1);
        assertEquals(3, target1.size());
        assertEquals(DEFAULT, target1.getInt($var.item(1,1).key()));
        
        assertEquals($var.item(1).valueInt(), target1.getInt($var.item(1).key()));
        assertEquals($var.item(2).valueInt(), target1.getInt($var.item(2).key()));
        assertEquals($var.item(3).valueInt(), target1.getInt($var.item(3).key()));
		
		Lookup<K> target2 = f.get();
        assertEquals($default, target2.getInt($var.item(1).key()));

        $var.list(1,2,3).into(target2);
        assertEquals($default, target2.getInt($var.item(1,1).key()));
    }
    
    @TestContract
    public void logicRemove(Function<int[], Lookup<K>> f) {
        Lookup<K> target = f.apply(new int[]{1,2,3});
        target.defaultReturnValue(DEFAULT);

		assertNotRemove(target, $var.item(7));
		
        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_HC(Function<int[], Lookup<K>> f) {
        Lookup<K> target = f.apply(new int[]{1,2,3});
        target.defaultReturnValue(DEFAULT);
        
        assertEquals(DEFAULT, target.removeInt($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 basicContains(Function<int[], Lookup<K>> f) {
        Lookup<K> 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 basicContains_HC(Function<int[], Lookup<K>> f) {
        Lookup<K> target = f.apply(new int[]{1,2});
        target.defaultReturnValue(DEFAULT);
        
        assertEquals(false, target.containsValue($var.item(1,1).value()));
    }
    
	@TestContract
    public void basicEntrySet_Iterator(Function<int[], Lookup<K>> f) {
        Lookup<K> target = f.apply(new int[]{1,7,2,3});
        Set<LookupEntry<K>> set = target.fastEntrySet();

		assertEquals(4, set.size());
        
        Iterator<LookupEntry<K>> iterator = set.iterator();
        while(iterator.hasNext()) {
            LookupEntry<K> entry = iterator.next();
            assertEquals(target.getInt(entry.getKey()), entry.getIntValue());
        }
        
        assertThrows(NoSuchElementException.class, ()-> iterator.next());
	}
	
	@TestContract
    public void basicEntrySet_Iterator_HC(Lookup<K> target) {
        Set<LookupEntry<K>> set = target.fastEntrySet();
		
        assertEquals(0, set.size());
		$var.list().item(1).item(1,1).item(2).item(3).into(target);
		assertEquals(4, set.size());
        
        Iterator<LookupEntry<K>> iterator = set.iterator();
        while(iterator.hasNext()) {
            LookupEntry<K> entry = iterator.next();
            assertEquals(target.getInt(entry.getKey()), entry.getIntValue());
        }
        
        assertThrows(NoSuchElementException.class, ()-> iterator.next());
	}
	
	@TestContract
    public void basicEntrySet_Iterator_SetValue(Lookup<K> target) {
        Set<LookupEntry<K>> set = target.fastEntrySet();
                
		assertEquals(0, set.size());
		$var.list().item(1).item(7).item(2).item(3).into(target);
		K k1 = $var.item(1).key();
		K k2 = $var.item(2).key();
        int v1 = $var.item(101).valueInt();
        int v2 = $var.item(102).valueInt();
		
		Iterator<LookupEntry<K>> iterator = set.iterator();
		while(iterator.hasNext()) {
			LookupEntry<K> 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(7));
		assertContains(target, $var.item(3));
	}
    
    @TestContract
    public void basicEntrySet_Iterator_SetValue_HC(Lookup<K> target) {
        Set<LookupEntry<K>> set = target.fastEntrySet();
                
		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();
        int v1 = $var.item(101).valueInt();
        int v2 = $var.item(102).valueInt();
		
		Iterator<LookupEntry<K>> iterator = set.iterator();
		while(iterator.hasNext()) {
			LookupEntry<K> 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 basicValues_Contains(Function<int[], Lookup<K>> f) {
        Lookup<K> target = f.apply(new int[]{1,7,2,3});
        IntCollection set = target.values();
		
		assertEquals(4, set.size());
        assertEquals(true, set.contains($var.item(1).value()));
        assertEquals(true, set.contains($var.item(7).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_HC(Lookup<K> target) {
        IntCollection 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_Remove(Function<int[], Lookup<K>> f) {
        Lookup<K> target = f.apply(new int[]{1,7,2,3});
        IntCollection 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(7).value()));
        assertEquals(true, set.contains($var.item(2).value()));
        assertEquals(true, set.contains($var.item(3).value()));
	}
	
	@TestContract
    public void basicValues_Remove_HC(Lookup<K> target) {
        IntCollection 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_Iterator(Function<int[], Lookup<K>> f) {
        Lookup<K> target = f.apply(new int[]{1,7,2,3});
        IntCollection set = target.values();

		assertEquals(4, set.size());

        IntIterator iterator = set.iterator();
        while(iterator.hasNext()) {
            int var = iterator.next();
            assertEquals(true, target.containsValue(var));
        }
        
        assertThrows(NoSuchElementException.class, ()-> iterator.next());
	}
   
    @TestContract
    public void basicValues_Iterator_HC(Lookup<K> target) {
        IntCollection set = target.values();
		
        assertEquals(0, set.size());
		$var.list().item(1).item(1,1).item(2).item(3).into(target);
		assertEquals(4, set.size());

        IntIterator iterator = set.iterator();
        while(iterator.hasNext()) {
            int var = iterator.next();
            assertEquals(true, target.containsValue(var));
        }
        
        assertThrows(NoSuchElementException.class, ()-> iterator.next());
	}
	
	@SuppressWarnings("unchecked")
    @TestContract
    public void basicValues_Iterator_Remove(Function<int[], Lookup<K>> f) {
        Lookup<K> target = f.apply(new int[]{1,7,2,3});
        IntCollection set = target.values();
		
		IntIterator iterator = set.iterator();
		while(iterator.hasNext()) {
			int var = iterator.next();
			
            if(var == $var.item(1).valueInt()) {
                iterator.remove();
            }
            if(var == $var.item(2).valueInt()) {
                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(7));
		assertContains(target, $var.item(3));
        
        set.clear();
        assertEquals(0, target.size());
	}
    
    @SuppressWarnings("unchecked")
    @TestContract
    public void basicValues_Iterator_Remove_HC(Lookup<K> target) {
        IntCollection set = target.values();
        
		assertEquals(0, set.size());
		$var.list().item(1).item(1,1).item(2).item(3).into(target);
		
		IntIterator iterator = set.iterator();
		while(iterator.hasNext()) {
			int var = iterator.next();
			
            if(var == $var.item(1).valueInt()) {
                iterator.remove();
            }
            if(var == $var.item(2).valueInt()) {
                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());
	}
        
    private void assertRemove(Lookup<K> target, TItem<K,Integer> item) {
        int size = target.size();
        assertEquals(item.valueInt(), target.removeInt(item.key()));
        assertEquals(size-1, target.size());
    }
    
    private void assertNotRemove(Lookup<K> target, TItem<K,Integer> item) {
        int size = target.size();
        assertEquals(target.defaultReturnValue(), target.removeInt(item.key()));
        assertEquals(size, target.size());
    }
	
	private void assertContains(Lookup<K> target, K key, int value) {
        assertEquals(true, target.containsKey(key));
        assertEquals(value, target.getInt(key));
    }
    
    private void assertContains(Lookup<K> target, TItem<K,Integer> item) {
		assertContains(target, item.key(), item.valueInt());
    }
    
    private void assertNotContains(Lookup<K> target, TItem<K,Integer> item) {
        assertEquals(false, target.containsKey(item.key()));
        assertEquals(target.defaultReturnValue(), target.getInt(item.key()));
    }
    
}
