/*
 * @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.Map.Entry;
import java.util.NoSuchElementException;
import java.util.SortedMap;
import java.util.SortedSet;
import javax.annotation.Resource;
import net.ranides.assira.collection.arrays.ArrayUtils;
import net.ranides.assira.collection.suite.sets.SetTester;
import net.ranides.assira.test.TMap;
import net.ranides.assira.test.TMap.TItem;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.junit.TestContract;

/**
 * 
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class SortedMapTester<K,V> {
    
    @Resource(name = "map!")
    private TMap<K,V> $var;
    
    @TestContract
    public void basicBounds(SortedMap<K, V> target) {
        assertThrows(NoSuchElementException.class, ()->
            target.firstKey()
        );
        assertThrows(NoSuchElementException.class, ()->
            target.lastKey()
        );
        $var.list(2,5,3,1,4).into(target);
        assertEquals($var.item(1).key(), target.firstKey());
        assertEquals($var.item(5).key(), target.lastKey());
    }
    
    @TestContract
    public void basicBounds_KeySet(SortedMap<K, V> target) {
        SortedSet<K> set = (SortedSet<K>)target.keySet();
        assertThrows(NoSuchElementException.class, ()->
            set.first()
        );
        assertThrows(NoSuchElementException.class, ()->
            set.last()
        );
        $var.list(2,5,3,1,4).into(target);
        assertEquals($var.item(1).key(), set.first());
        assertEquals($var.item(5).key(), set.last());
    }
    
    @TestContract
    public void basicBounds_EntrySet(SortedMap<K, V> target) {
        SortedSet<Entry<K, V>> set = (SortedSet<Entry<K, V>>)target.entrySet();
        assertThrows(NoSuchElementException.class, ()->
            set.first()
        );
        assertThrows(NoSuchElementException.class, ()->
            set.last()
        );
        $var.list(2,5,3,1,4).into(target);
        assertEquals($var.item(1).entry(), set.first());
        assertEquals($var.item(5).entry(), set.last());
    }
    
    @TestContract
    public void basicMap_Head(SortedMap<K, V> target) {
        checkHead(true, target);
        $var.list(4,7,5,3,6).into(target);
        checkHead(true, target, $var.list(3,4,5,6,7).keys());
    }
    
    @TestContract
    public void basicMap_Tail(SortedMap<K, V> target) {
        checkTail(true, target);
        $var.list(4,7,5,3,6).into(target);
        checkTail(true, target, $var.list(3,4,5,6,7).keys());
    }
    
    @TestContract
    public void basicMap_Sub(SortedMap<K, V> target) {
        checkTail(true, target);
        $var.list(4,7,5,3,6).into(target);
        checkSub(true, target, $var.list(3,4,5,6,7).keys());
    }
        
    @TestContract
    public void basicEntries_Head(SortedMap<K, V> target) {
        SetTester.assertHead($var.entries(), $entryset(target), $var.list().entries());
        $var.list(4, 7, 5, 3, 6).into(target);
        SetTester.assertHead($var.entries(), $entryset(target), $var.list(3, 4, 5, 6, 7).entries());
    }
    
    @TestContract
    public void basicEntries_Tail(SortedMap<K, V> target) {
        SetTester.assertTail($var.entries(), $entryset(target), $var.list().entries());
        $var.list(4, 7, 5, 3, 6).into(target);
        SetTester.assertTail($var.entries(), $entryset(target), $var.list(3, 4, 5, 6, 7).entries());
    }
    
    @TestContract
    public void basicEntries_Sub(SortedMap<K, V> target) {
        SetTester.assertSub($var.entries(), $entryset(target), $var.list().entries());
        $var.list(4, 7, 5, 3, 6).into(target);
        SetTester.assertSub($var.entries(), $entryset(target), $var.list(3, 4, 5, 6, 7).entries());
    }
    
    @SafeVarargs
    final void checkHead(boolean nested, SortedMap<K, V> target, K... content) {
        int n = content.length;
        assertEquals(0, target.headMap($var.item(0).key()).size());
        assertEquals(n, target.headMap($var.item(9).key()).size());
        checkRange(target, content);
        
        for(int i=0; i<n; i++) {
            K point = content[i];
            K[] exp = ArrayUtils.head(content, point, $var.comparator());
            SortedMap<K, V> map = target.headMap(point);
            assertEquals(exp.length, map.size());
            if(nested) {
                checkHead(false, map, exp);
                checkTail(false, map, exp);
                checkSub(false, map, exp);
            }
        }
    }
    
    @SafeVarargs
    final void checkTail(boolean nested, SortedMap<K, V> target, K... content) {
        int n = content.length;
        assertEquals(n, target.tailMap($var.item(0).key()).size());
        assertEquals(0, target.tailMap($var.item(9).key()).size());
        checkRange(target, content);
        
        for(int i=0; i<n; i++) {
            K point = content[i];
            K[] exp = ArrayUtils.tail(content, point, $var.comparator());
            SortedMap<K, V> map = target.tailMap(point);
            assertEquals(exp.length, map.size());
            if(nested) {
                checkHead(false, map, exp);
                checkTail(false, map, exp);
                checkSub(false, map, exp);
            }
        }
    }
    
    @SafeVarargs
    final void checkSub(boolean nested, SortedMap<K, V> target, K... content) {
        checkSub(nested, target, $var.item(3), $var.item(9), content);
        checkSub(nested, target, $var.item(5), $var.item(9), content);
            
        checkSub(nested, target, $var.item(3), $var.item(7), content);
        checkSub(nested, target, $var.item(3), $var.item(6), content);
        checkSub(nested, target, $var.item(4), $var.item(6), content);
        checkSub(nested, target, $var.item(5), $var.item(6), content);

        checkSub(nested, target, $var.item(6), $var.item(7), content);
        checkSub(nested, target, $var.item(6), $var.item(6), content);
        
        checkSub(nested, target, $var.item(0), $var.item(3), content);
        checkSub(nested, target, $var.item(0), $var.item(5), content);
        checkSub(nested, target, $var.item(0), $var.item(9), content);
    }
    
    private void checkSub(boolean nested, SortedMap<K, V> target, TItem<K,V> begin, TItem<K,V> end, K[] content) {
        K[] exp = ArrayUtils.slice(content, begin.key(), end.key(), $var.comparator());
        
        SortedMap<K, V> map = target.subMap(begin.key(), end.key());
        assertEquals(exp.length, map.size());
        checkRange(map, exp);
       
        if(nested) {
            checkHead(false, map, exp);
            checkTail(false, map, exp);
            checkSub(false, map, exp);
        }
    }
    
    private <V> void checkRange(SortedMap<K, V> target, K[] content) {
        if(content.length > 0) {
            assertEquals(content[0], target.firstKey());
            assertEquals(content[content.length-1], target.lastKey());
        } else {
            assertThrows(NoSuchElementException.class, ()->target.firstKey());
            assertThrows(NoSuchElementException.class, ()->target.lastKey());
        }
    }
    
    @TestContract
    public void basicSubMap_Put(SortedMap<K, V> target) {
        $var.list(4, 7, 3, 6).into(target);
        SortedMap<K, V> map = target.subMap($var.item(3).key(), $var.item(7).key());
        assertEquals(3, map.size());
        
        assertNull( map.put($var.item(5).key(), $var.item(5).value()) );
        
        assertTrue( map.containsKey($var.item(5).key()));
        assertTrue( target.containsKey($var.item(5).key()));
        assertEquals(4, map.size());
        assertEquals(5, target.size());
        
        assertEquals($var.item(6).value(), map.put($var.item(6).key(), $var.item(6).value()));
        assertEquals(4, map.size());
        assertEquals(5, target.size());
        assertEquals($var.item(6).value(), map.get($var.item(6).key()));
        assertEquals($var.item(6).value(), target.get($var.item(6).key()));
        
        assertThrows(IllegalArgumentException.class, ()->
            map.put($var.item(20).key(), $var.item(20).value())
        );
        assertThrows(IllegalArgumentException.class, ()->
            map.put($var.item(0).key(), $var.item(0).value())
        );
        
    }
    
    @TestContract
    public void basicSubMap_Clear(SortedMap<K, V> target) {
        $var.list(4, 7, 5, 3, 6).into(target);
        SortedMap<K, V> map = target.subMap($var.item(4).key(), $var.item(6).key());
        assertEquals(2, map.size());
        
        map.clear();
        assertEquals(0, map.size());
        assertTrue(map.isEmpty());
        assertEquals(3, target.size());
    }
	
	@TestContract
    public void basicSubMap_Remove(SortedMap<K, V> target) {
        $var.list(4, 7, 5, 3, 6).into(target);
        SortedMap<K, V> map = target.subMap($var.item(4).key(), $var.item(7).key());
        assertEquals(5, target.size());
        assertEquals(3, map.size());

        assertNull(map.remove($var.item(3).key()));
        assertNull(map.remove($var.item(7).key()));
        assertNull(map.remove(new Object()));
        
        assertEquals($var.item(6).value(), target.remove($var.item(6).key()));
        assertEquals(4, target.size());
        assertEquals(2, map.size());
        assertEquals($var.item(4).value(), map.remove($var.item(4).key()));
        assertEquals($var.item(5).value(), map.remove($var.item(5).key()));
        
        assertEquals(2, target.size());
        assertEquals(0, map.size());
        assertEquals(true, map.isEmpty());
    }
    
    @TestContract
    public void basicSubMap_Remove_HC(SortedMap<K, V> target) {
        $var.list(4, 7, 5, 3, 6).into(target);
        SortedMap<K, V> map = target.subMap($var.item(4).key(), $var.item(7).key());
        assertEquals(5, target.size());
        assertEquals(3, map.size());

        assertNull(map.remove($var.item(3).key()));
        assertNull(map.remove($var.item(3,1).key()));
        assertNull(map.remove($var.item(7).key()));
        assertNull(map.remove(new Object()));
        
        assertEquals($var.item(6).value(), target.remove($var.item(6).key()));
        assertEquals(4, target.size());
        assertEquals(2, map.size());
        assertEquals($var.item(4).value(), map.remove($var.item(4).key()));
        assertEquals($var.item(5).value(), map.remove($var.item(5).key()));
        
        assertEquals(2, target.size());
        assertEquals(0, map.size());
        assertEquals(true, map.isEmpty());
    }
    
    @TestContract
    public void basicToString(SortedMap<K, V> target) {
        $var.list(2,5,1,3,4).into(target);
        String expected = "{"
            + $var.item(1).key() + "=>" + $var.item(1).value() + ", "
            + $var.item(2).key() + "=>" + $var.item(2).value() + ", "
            + $var.item(3).key() + "=>" + $var.item(3).value() + ", "
            + $var.item(4).key() + "=>" + $var.item(4).value() + ", "
            + $var.item(5).key() + "=>" + $var.item(5).value()
            + "}";
        assertEquals(expected, target.toString());
    }
    
    private SortedSet<Entry<K,V>> $entryset(SortedMap<K, V> target) {
        return (SortedSet<Entry<K,V>>)target.entrySet();
    }
    
}
