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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import net.ranides.assira.ContractTesters;
import net.ranides.assira.collection.arrays.IntArrays;
import net.ranides.assira.collection.lists.IntArrayList;
import net.ranides.assira.collection.lists.IntList;
import net.ranides.assira.collection.lists.IntRange;
import net.ranides.assira.collection.mockup.TCollections;
import net.ranides.assira.collection.suite.IteratorTester;
import static net.ranides.assira.junit.NewAssert.*;
import org.junit.Test;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class IntIteratorUtilsTest {
    
    @Test
	public void testFirst() {
		assertEquals(1, IntIteratorUtils.first(IntArrays.asList(1,2,3,4).iterator()));
		assertEquals(1, IntIteratorUtils.first(IntArrays.asList(1,2).iterator()));
		assertEquals(1, IntIteratorUtils.first(IntArrays.asList(1).iterator()));
		
		assertThrows(NoSuchElementException.class, () -> {
			IntIteratorUtils.first(IntArrays.asList().iterator());
		});
	}
	
	@Test
	public void testLast() {
		assertEquals(4, IntIteratorUtils.last(IntArrays.asList(1,2,3,4).iterator()));
		assertEquals(4, IntIteratorUtils.last(IntArrays.asList(3,4).iterator()));
		assertEquals(4, IntIteratorUtils.last(IntArrays.asList(4).iterator()));
		
		assertThrows(NoSuchElementException.class, () -> {
            IntIteratorUtils.last(IntArrays.asList().iterator());
		});
	}
    
    @Test
	public void testFirst_Filter() {
		assertEquals(7, IntIteratorUtils.first(IntArrays.asList(1,2,7,0,8,0,3).iterator(), a -> a>5));
		
		assertThrows(NoSuchElementException.class, () -> {
			IntIteratorUtils.first(IntArrays.asList(1,2,3).iterator(), a -> a>5);
		});
		assertThrows(NoSuchElementException.class, () -> {
			IntIteratorUtils.first(IntArrays.asList().iterator(), a -> a>5);
		});
	}
	
	@Test
	public void testLast_Filter() {
		assertEquals(8, IntIteratorUtils.last(IntArrays.asList(1,2,7,0,8,0,3).iterator(), a -> a>5));
		assertEquals(8, IntIteratorUtils.last(IntArrays.asList(1,2,7,0,8).iterator(), a -> a>5));
		assertEquals(8, IntIteratorUtils.last(IntArrays.asList(1,8).iterator(), a -> a>5));
		assertEquals(8, IntIteratorUtils.last(IntArrays.asList(8,1).iterator(), a -> a>5));
		
		assertThrows(NoSuchElementException.class, () -> {
			IntIteratorUtils.last(IntArrays.asList(1,2,3).iterator(), a -> a>5);
		});
		assertThrows(NoSuchElementException.class, () -> {
			IntIteratorUtils.last(IntArrays.asList().iterator(), a -> a>5);
		});
	}
	
    
    @Test
	public void testSize() {
		assertEquals(4, IntIteratorUtils.size(IntArrays.asList(1,2,7,8).iterator()));
		assertEquals(2, IntIteratorUtils.size(IntArrays.asList(1,2).iterator()));
		assertEquals(1, IntIteratorUtils.size(IntArrays.asList(1).iterator()));
		assertEquals(0, IntIteratorUtils.size(IntArrays.asList().iterator()));
		assertEquals(0, IntIteratorUtils.size(null));
	}
	
	
	@Test
	public void testMap_Iterator() {
        ContractTesters
            .runner()
            .param("iterator.array!", TCollections.produce(n -> 10*n).asRangeGenerator() )
            .function(new IteratorTester.IteratorParams(), this::int2big)
            .run();
	}
    
    private IntListIterator int2big(IteratorTester.IteratorParams p) {
        IntListIterator iterator = new IntRange(0, p.size).listIterator(p.index);
        return IntIteratorUtils.map(iterator, (int n) -> 10*n);
    }
    
    @Test
	public void testMap_Iterator_Remove() {
        IntList list = new IntArrayList(new int[]{1,2,3,4,5});
        IntIterator b = IntIteratorUtils.map((IntIterator)list.iterator(), (int v)->10*v);
        b.next();
        b.remove();
        b.next();
        b.next();
        b.remove();
        assertEquals(Arrays.asList(2,4,5), list);
    }
    
    @Test
    public void testMap_IteratorFunction() {
        IntList list = new IntArrayList(new int[]{1,2,3,4,5});
		
        Iterator<String> a = IntIteratorUtils.map((IntIterator)list.iterator(), (int v)->"#"+v);
		IteratorTester.basicIterator(a, 0, new String[]{"#1","#2","#3","#4","#5"});
        
        Iterator<String> b = IntIteratorUtils.map((IntIterator)list.iterator(), (int v)->"#"+v);
        b.next();
        b.remove();
        b.next();
        b.next();
        b.remove();
        assertEquals(Arrays.asList(2,4,5), list);
    }
	
	@Test
	public void testMap_ListIterator() {
		IntList list = new IntArrayList(new int[]{1,2,3,4,5});
        
		IntListIterator a = IntIteratorUtils.map(list.iterator(), (int v)->10*v);
		IteratorTester.basicIterator(a, 0, new Integer[]{10,20,30,40,50});
        
        IntListIterator b = IntIteratorUtils.map(list.iterator(), (int v)->10*v);
        b.next();
        b.remove();
        b.next();
        assertThrows(UnsupportedOperationException.class, ()->{ b.set(9); });
        assertThrows(UnsupportedOperationException.class, ()->{ b.add(9); });
        b.next();
        b.remove();
        assertEquals(Arrays.asList(2,4,5), list);
	}
    
    @Test
    public void testMap_ListIteratorFunction() {
        IntList list = new IntArrayList(new int[]{1,2,3,4,5});
		
        ListIterator<String> a = IntIteratorUtils.map(list.iterator(), (int v)->"#"+v);
		IteratorTester.basicIterator(a, 0, new String[]{"#1","#2","#3","#4","#5"});
        
        ListIterator<String> b = IntIteratorUtils.map(list.iterator(), (int v)->"#"+v);
        b.next();
        b.remove();
        b.next();
        assertThrows(UnsupportedOperationException.class, ()->{ b.set("Q"); });
        assertThrows(UnsupportedOperationException.class, ()->{ b.add("Q"); });
        b.next();
        b.remove();
        assertEquals(Arrays.asList(2,4,5), list);
    }
    
//    @Test
//	public void testFilter_Iterator() {
//        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
//        
//		IteratorTester.basicIterator(IntIteratorUtils.filter(IntIterator.wrap(list.iterator()), v -> v%2==0), 0, new Integer[]{2,4,6,8});
//		IteratorTester.basicIterator(IntIteratorUtils.filter(IntIterator.wrap(list.iterator()), v -> v%2==1), 0, new Integer[]{1,3,5,7,9});
//		IteratorTester.basicIterator(IntIteratorUtils.filter(IntIterator.wrap(list.iterator()), v -> v>5), 0, new Integer[]{6,7,8,9});
//	}
//	
//	@Test
//	public void testFilter_ListIterator() {
//		IntList list = IntArrays.asList(1,2,3,4,5,6,7,8,9);
//
//		IteratorTester.listIterator(IntIteratorUtils.filter(list.listIterator(), v -> v%2==0), 0, new Integer[]{2,4,6,8});
//		IteratorTester.listIterator(IntIteratorUtils.filter(list.listIterator(), v -> v%2==1), 0, new Integer[]{1,3,5,7,9});
//		IteratorTester.listIterator(IntIteratorUtils.filter(list.listIterator(), v -> v>5), 0, new Integer[]{6,7,8,9});
//		
//		
//		IteratorTester.listIterator(IntIteratorUtils.filter(list.listIterator(2), v -> v%2==0), 0, new Integer[]{4,6,8});
//		IteratorTester.listIterator(IntIteratorUtils.filter(list.listIterator(2), v -> v%2==1), 0, new Integer[]{3,5,7,9});
//		IteratorTester.listIterator(IntIteratorUtils.filter(list.listIterator(6), v -> v>5), 0, new Integer[]{7,8,9});
//	}
    
    @Test
	public void testFilter_Iterator() {
        ContractTesters
            .runner()
            .param("iterator.array!", TCollections.produce(n -> 2*n).asRangeGenerator() )
            .function(new IteratorTester.IteratorParams(), this::range2even)
            .run();
        ContractTesters
            .runner()
            .param("iterator.array!", TCollections.produce(n -> 2*n+1).asRangeGenerator() )
            .function(new IteratorTester.IteratorParams(), this::range2odd)
            .run();
        ContractTesters
            .runner()
            .param("iterator.array!", TCollections.produce(n -> n+5).asRangeGenerator() )
            .function(new IteratorTester.IteratorParams(), this::range2greater)
            .run();
    }
    
    @Test
	public void testLimit_Iterator() {
        ContractTesters
            .runner()
            .param("iterator.array!", TCollections.produce(n -> n).asRangeGenerator() )
            .function(new IteratorTester.IteratorParams(), this::limit)
            .run();
        ContractTesters
            .runner()
            .param("iterator.array!", TCollections.produce(n -> n).asRangeGenerator() )
            .function(new IteratorTester.IteratorParams(), this::limitList)
            .run();
    }
    
    private Iterator<Integer> limit(IteratorTester.IteratorParams p) {
        IntRange range = new IntRange(0, 2*p.size);
        IntIterator i = IntIteratorUtils.limit((IntIterator)range.iterator(), v -> v < p.size);
        IntIteratorUtils.next(i, p.index);
        return i;
    }
    
    private Iterator<Integer> limitList(IteratorTester.IteratorParams p) {
        IntRange range = new IntRange(0, 2*p.size);
        IntListIterator i = IntIteratorUtils.limit(range.iterator(), v -> v < p.size);
        IntIteratorUtils.next(i, p.index);
        return i;
    }
    
    private Iterator<Integer> range2even(IteratorTester.IteratorParams p) {
        IntRange range = new IntRange(0, 2*p.size);
        IntListIterator i = IntIteratorUtils.filter(range.iterator(), v -> v%2==0);
        IntIteratorUtils.next(i, p.index);
        return i;
    }
    
    private Iterator<Integer> range2odd(IteratorTester.IteratorParams p) {
        IntRange range = new IntRange(0, 2*p.size);
        IntListIterator i = IntIteratorUtils.filter(range.iterator(), v -> v%2==1);
        IntIteratorUtils.next(i, p.index);
        return i;
    }
    
    private Iterator<Integer> range2greater(IteratorTester.IteratorParams p) {
        IntRange range = new IntRange(0, p.size+5);
        IntListIterator i = IntIteratorUtils.filter(range.iterator(), v -> v >= 5);
        IntIteratorUtils.next(i, p.index);
        return i;
    }
    
    @Test
	public void testReverse() {
        IntList list = new IntArrayList(new int[]{1,2,3,4,5,6,7,8,9});
        IntListIterator a = list.listIterator(9);
        IteratorTester.basicIterator(IntIteratorUtils.reverse(a), 0, new Integer[]{9,8,7,6,5,4,3,2,1});
        
        IntIterator b = IntIteratorUtils.reverse(list.listIterator(9));
        IntIteratorUtils.next(b, 1);
        b.remove();
        IntIteratorUtils.next(b, 2);
        b.remove();
        IntIteratorUtils.next(b, 3);
        b.remove();
        assertEquals(new IntArrayList(new int[]{1,2,3,5,6,8}),list);
    }
	
    @Test
    public void testStream() {
        IntList exp = IntArrays.asList(1,2,3,4,5,6,7,8,9);
        IntList out = new IntArrayList();
        IntIteratorUtils.stream( exp.listIterator() ).forEach(v -> out.add(v));
        assertEquals(exp, out);
    }
    
    @Test
    public void testFlat() {
        IntList exp = IntArrays.asList(1,2,3,4);
        Iterator<IntIterator> i = IntIteratorUtils.map(exp.iterator(), this::expand);
        Iterator<Integer> o = IntIteratorUtils.flat(i);
        IteratorTester.basicIterator(o, 0, new Integer[]{11,12,13,21,22,23,31,32,33,41,42,43});
    }
    
    IntIterator expand(Integer v) {
        int d = 10*v;
        return IntArrays.asList(d+1, d+2, d+3).iterator();
    }
    
    @Test
    public void testCollect() {
        IntList exp1 = IntArrays.asList(1,2,3,4,5,6,7,8,9);
        IntList out1 = IntIteratorUtils.collect(exp1.listIterator(), new IntArrayList());
        assertEquals(exp1, out1);
        
        IntList exp2 = IntArrays.asList(1);
        IntList out2 = IntIteratorUtils.collect(exp2.listIterator(), new IntArrayList());
        assertEquals(exp2, out2);
        
        IntList exp3 = IntArrays.asList();
        IntList out3 = IntIteratorUtils.collect(exp3.listIterator(), new IntArrayList());
        assertEquals(exp3, out3);
    }
    
    @Test
    public void testCompare() {
        IntList a = IntArrays.asList(1,2,3,4,5,6,7,8,9);
        IntList b = IntArrays.asList(1,2,3,4,5,6,7,8);
        IntList c = IntArrays.asList(1,2,3,8,8,8,7,8);
        IntList d = IntArrays.asList(1,2,3,8,8,8,7,8,9,10);
        IntList e = IntArrays.asList(4);
        IntList f = IntArrays.asList(0);
        IntList g = IntArrays.asList();
        
        assertTrue( IntIteratorUtils.compare(a.iterator(), a.iterator()) == 0);
        assertTrue( IntIteratorUtils.compare(a.iterator(), b.iterator()) > 0);
        assertTrue( IntIteratorUtils.compare(a.iterator(), c.iterator()) < 0);
        assertTrue( IntIteratorUtils.compare(a.iterator(), d.iterator()) < 0);
        assertTrue( IntIteratorUtils.compare(a.iterator(), e.iterator()) < 0);
        assertTrue( IntIteratorUtils.compare(a.iterator(), f.iterator()) > 0);
        assertTrue( IntIteratorUtils.compare(a.iterator(), g.iterator()) > 0);
    }
    
    @Test
    public void testEquals() {
        IntList a = IntArrays.asList(1,2,3,4,5,6,7,8,9);
        IntList b = IntArrays.asList(1,2,3,4,5,6,7,8);
        IntList c = IntArrays.asList(1,2,3,8,8,8,7,8);
        IntList d = IntArrays.asList(1,2,3,8,8,8,7,8,9,10);
        IntList e = IntArrays.asList(4);
        IntList f = IntArrays.asList(0);
        IntList g = IntArrays.asList();
        
        assertTrue( IntIteratorUtils.equals(a.iterator(), a.iterator()) );
        assertFalse( IntIteratorUtils.equals(a.iterator(), b.iterator()) );
        assertFalse( IntIteratorUtils.equals(a.iterator(), c.iterator()) );
        assertFalse( IntIteratorUtils.equals(a.iterator(), d.iterator()) );
        assertFalse( IntIteratorUtils.equals(a.iterator(), e.iterator()) );
        assertFalse( IntIteratorUtils.equals(a.iterator(), f.iterator()) );
        assertFalse( IntIteratorUtils.equals(a.iterator(), g.iterator()) );
    }
    
    @Test
    public void testNext() {
        // dość mocno już testujemy #next w testach związanych z filtrami
        // tutaj sprawdźmy zachowanie out-of-bound
        
        IntList list = IntArrays.asList(1,2,3,4,5);
        IntIterator i = list.iterator();
        assertEquals(5, IntIteratorUtils.next(i, 8));
        assertFalse(i.hasNext());
    }
	
}
