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

import java.util.NoSuchElementException;
import java.util.SortedSet;
import javax.annotation.Resource;
import net.ranides.assira.collection.arrays.ArrayUtils;
import net.ranides.assira.test.TCollection;
import net.ranides.assira.test.TCollection.TItem;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.junit.TestContract;

/**
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class SortedSetTester<K> {
    
    @Resource(name = "collection!")
    private TCollection<K> $var;
    
    @TestContract
    public void basicBounds(SortedSet<K> target) {
        assertThrows(NoSuchElementException.class, ()->
            target.first()
        );
        assertThrows(NoSuchElementException.class, ()->
            target.last()
        );
        $var.list(2,5,3,1,4).into(target);
        assertEquals($var.item(1).value(), target.first());
        assertEquals($var.item(5).value(), target.last());
    }
    
    @TestContract
    public void basicHead(SortedSet<K> target) {
        checkHead(true, target);
        $var.list(4,7,5,3,6).into(target);
        checkHead(true, target, $var.list(3,4,5,6,7).values());
    }
    
    @TestContract
    public void basicTail(SortedSet<K> target) {
        checkTail(true, target);
        $var.list(4,7,5,3,6).into(target);
        checkTail(true, target, $var.list(3,4,5,6,7).values());
    }
    
    @TestContract
    public void basicSub(SortedSet<K> target) {
        checkTail(true, target);
        $var.list(4,7,5,3,6).into(target);
        checkSub(true, target, $var.list(3,4,5,6,7).values());
    }
    
    @SafeVarargs
    final void checkHead(boolean nested, SortedSet<K> target, K... content) {
        int n = content.length;
        assertEquals(0, target.headSet($var.item(0).value()).size());
        assertEquals(n, target.headSet($var.item(9).value()).size());
        checkRange(target, content);
        
        for(int i=0; i<n; i++) {
            K point = content[i];
            K[] exp = ArrayUtils.head(content, point, $var.comparator());
            SortedSet<K> map = target.headSet(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, SortedSet<K> target, K... content) {
        int n = content.length;
        assertEquals(n, target.tailSet($var.item(0).value()).size());
        assertEquals(0, target.tailSet($var.item(9).value()).size());
        checkRange(target, content);
        
        for(int i=0; i<n; i++) {
            K point = content[i];
            K[] exp = ArrayUtils.tail(content, point, $var.comparator());
            SortedSet<K> map = target.tailSet(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, SortedSet<K> 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, SortedSet<K> target, TItem<K> begin, TItem<K> end, K[] content) {
        K[] exp = ArrayUtils.slice(content, begin.value(), end.value(), $var.comparator());
        
        SortedSet<K> map = target.subSet(begin.value(), end.value());
        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(SortedSet<K> target, K[] content) {
        if(content.length > 0) {
            assertEquals(content[0], target.first());
            assertEquals(content[content.length-1], target.last());
        } else {
            assertThrows(NoSuchElementException.class, ()->target.first());
            assertThrows(NoSuchElementException.class, ()->target.last());
        }
    }
    
    @TestContract
    public void basicSub_Put(SortedSet<K> target) {
        $var.list(4, 7, 3, 6).into(target);
        SortedSet<K> map = target.subSet($var.item(3).value(), $var.item(7).value());
        assertEquals(3, map.size());
        
        assertTrue( map.add($var.item(5).value()) );
        
        assertTrue( map.contains($var.item(5).value()));
        assertTrue( target.contains($var.item(5).value()));
        assertEquals(4, map.size());
        assertEquals(5, target.size());
        
        assertFalse(map.add($var.item(6).value()));
        assertEquals(4, map.size());
        assertEquals(5, target.size());
        assertTrue(map.contains($var.item(6).value()));
        assertTrue(target.contains($var.item(6).value()));
        
        assertThrows(IllegalArgumentException.class, ()->
            map.add($var.item(20).value())
        );
        assertThrows(IllegalArgumentException.class, ()->
            map.add($var.item(0).value())
        );
        
    }
    
    @TestContract
    public void basicSubMap_Clear(SortedSet<K> target) {
        $var.list(4, 7, 5, 3, 6).into(target);
        SortedSet<K> map = target.subSet($var.item(4).value(), $var.item(6).value());
        assertEquals(2, map.size());
        
        map.clear();
        assertEquals(0, map.size());
        assertTrue(map.isEmpty());
        assertEquals(3, target.size());
    }
    
    @TestContract
    public void basicSubMap_Remove(SortedSet<K> target) {
        $var.list(4, 7, 5, 3, 6).into(target);
        SortedSet<K> map = target.subSet($var.item(4).value(), $var.item(7).value());
        assertEquals(5, target.size());
        assertEquals(3, map.size());

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