/*
 * @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.lists.IntArrayList;
import net.ranides.assira.collection.lists.IntRange;
import net.ranides.assira.collection.mockup.TCollections;
import net.ranides.assira.collection.suite.IteratorTester;
import net.ranides.assira.collection.suite.IteratorTester.IteratorParams;
import org.junit.Test;
import static net.ranides.assira.junit.NewAssert.*;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class IteratorUtilsTest {
	
	@Test
	public void testFirst() {
		assertEquals((Integer)1, IteratorUtils.first(Arrays.asList(1,2,3,4).iterator()).get());
		assertEquals((Integer)1, IteratorUtils.first(Arrays.asList(1,2).iterator()).get());
		assertEquals((Integer)1, IteratorUtils.first(Arrays.asList(1).iterator()).get());
		
		assertThrows(NoSuchElementException.class, () -> {
			IteratorUtils.first(Arrays.asList().iterator()).get();
		});
	}
	
	@Test
	public void testLast() {
		assertEquals((Integer)4, IteratorUtils.last(Arrays.asList(1,2,3,4).iterator()).get());
		assertEquals((Integer)4, IteratorUtils.last(Arrays.asList(3,4).iterator()).get());
		assertEquals((Integer)4, IteratorUtils.last(Arrays.asList(4).iterator()).get());
		
		assertThrows(NoSuchElementException.class, () -> {
			IteratorUtils.last(Arrays.asList().iterator()).get();
		});
	}
	
	
	@Test
	public void testFirst_Filter() {
		assertEquals((Integer)7, IteratorUtils.first(Arrays.asList(1,2,7,0,8,0,3).iterator(), a -> a>5).get());
		
		assertThrows(NoSuchElementException.class, () -> {
			IteratorUtils.first(Arrays.asList(1,2,3).iterator(), a -> a>5).get();
		});
		assertThrows(NoSuchElementException.class, () -> {
			IteratorUtils.first(Arrays.<Integer>asList().iterator(), a -> a>5).get();
		});
	}
	
	@Test
	public void testLast_Filter() {
		assertEquals((Integer)8, IteratorUtils.last(Arrays.asList(1,2,7,0,8,0,3).iterator(), a -> a>5).get());
		assertEquals((Integer)8, IteratorUtils.last(Arrays.asList(1,2,7,0,8).iterator(), a -> a>5).get());
		assertEquals((Integer)8, IteratorUtils.last(Arrays.asList(1,8).iterator(), a -> a>5).get());
		assertEquals((Integer)8, IteratorUtils.last(Arrays.asList(8,1).iterator(), a -> a>5).get());
		
		assertThrows(NoSuchElementException.class, () -> {
			IteratorUtils.last(Arrays.asList(1,2,3).iterator(), a -> a>5).get();
		});
		assertThrows(NoSuchElementException.class, () -> {
			IteratorUtils.last(Arrays.<Integer>asList().iterator(), a -> a>5).get();
		});
	}
	
	@Test
	public void testSize() {
		assertEquals(4, IteratorUtils.size(Arrays.asList(1,2,7,8).iterator()));
		assertEquals(2, IteratorUtils.size(Arrays.asList(1,2).iterator()));
		assertEquals(1, IteratorUtils.size(Arrays.asList(1).iterator()));
		assertEquals(0, IteratorUtils.size(Arrays.asList().iterator()));
		assertEquals(0, IteratorUtils.size(null));
	}
	
	@Test
	public void testMap_Iterator() {
        ContractTesters
            .runner()
            .param("iterator.array!", TCollections.LOWER.asRangeGenerator() )
            .function(new IteratorParams(), this::upper2lower)
            .run();
	}
    

    private ListIterator<String> upper2lower(IteratorParams p) {
        ListIterator<String> iterator = TCollections.UPPER.range(p.size)
            .intoList()
            .listIterator(p.index);
        return IteratorUtils.map(iterator, s -> s.toLowerCase());
    }

    @Test
	public void testMap_Iterator_Remove() {
        List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
        Iterator<String> b = IteratorUtils.map(list.iterator(), s -> s.toUpperCase());
        b.next();
        b.remove();
        b.next();
        b.next();
        b.remove();
        assertEquals(Arrays.asList("b","d"), list);
	}
	
	@Test
	public void testMap_ListIterator() {
        List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
		
        ListIterator<String> a = IteratorUtils.map(list.listIterator(), s -> s.toUpperCase());
		IteratorTester.listIterator(a, 0, new String[]{"A", "B", "C", "D"});
        
        ListIterator<String> b = IteratorUtils.map(list.listIterator(), s -> s.toUpperCase());
        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("b","d"), list);
	}
    
    @Test
	public void testFilter_Iterator() {
        ContractTesters
            .runner()
            .param("iterator.array!", TCollections.produce(n -> 2*n).asRangeGenerator() )
            .function(new IteratorParams(), this::range2even)
            .run();
        ContractTesters
            .runner()
            .param("iterator.array!", TCollections.produce(n -> 2*n+1).asRangeGenerator() )
            .function(new IteratorParams(), this::range2odd)
            .run();
        ContractTesters
            .runner()
            .param("iterator.array!", TCollections.produce(n -> n+5).asRangeGenerator() )
            .function(new IteratorParams(), this::range2greater)
            .run();
    }
    
    @Test
	public void testLimit_Iterator() {
        ContractTesters
            .runner()
            .param("iterator.array!", TCollections.produce(n -> n).asRangeGenerator() )
            .function(new IteratorParams(), this::limit)
            .run();
        ContractTesters
            .runner()
            .param("iterator.array!", TCollections.produce(n -> n).asRangeGenerator() )
            .function(new IteratorParams(), this::limitList)
            .run();
    }
    
    private Iterator<Integer> limit(IteratorParams p) {
        IntRange range = new IntRange(0, 2*p.size);
        Iterator<Integer> i = IteratorUtils.limit((IntIterator)range.iterator(), v -> v < p.size);
        IteratorUtils.next(i, p.index);
        return i;
    }
    
    private Iterator<Integer> limitList(IteratorParams p) {
        IntRange range = new IntRange(0, 2*p.size);
        ListIterator<Integer> i = IteratorUtils.limit(range.iterator(), v -> v < p.size);
        IteratorUtils.next(i, p.index);
        return i;
    }
    
    private Iterator<Integer> range2even(IteratorParams p) {
        IntRange range = new IntRange(0, 2*p.size);
        ListIterator<Integer> i = IteratorUtils.filter(range.iterator(), v -> v%2==0);
        IteratorUtils.next(i, p.index);
        return i;
    }
    
    private Iterator<Integer> range2odd(IteratorParams p) {
        IntRange range = new IntRange(0, 2*p.size);
        ListIterator<Integer> i = IteratorUtils.filter(range.iterator(), v -> v%2==1);
        IteratorUtils.next(i, p.index);
        return i;
    }
    
    private Iterator<Integer> range2greater(IteratorParams p) {
        IntRange range = new IntRange(0, p.size+5);
        ListIterator<Integer> i = IteratorUtils.filter(range.iterator(), v -> v >= 5);
        IteratorUtils.next(i, p.index);
        return i;
    }
    
    @Test
	public void testReverse() {
        List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5,6,7,8,9));
        ListIterator<Integer> i = list.listIterator(9);
        IteratorTester.basicIterator(IteratorUtils.reverse(i), 0, new Integer[]{9,8,7,6,5,4,3,2,1});
        
        Iterator<Integer> b = IteratorUtils.reverse(list.listIterator(9));
        IteratorUtils.next(b, 1);
        b.remove();
        IteratorUtils.next(b, 2);
        b.remove();
        IteratorUtils.next(b, 3);
        b.remove();
        assertEquals(new IntArrayList(new int[]{1,2,3,5,6,8}),list);
    }
    
    @Test
    public void testStream() {
        List<Integer> exp = Arrays.asList(1,2,3,4,5,6,7,8,9);
        List<Integer> out = new ArrayList<>();
        IteratorUtils.stream( exp.listIterator() ).forEach(v -> out.add(v));
        assertEquals(exp, out);
    }
    
    @Test
    public void testFlat() {
        List<Integer> exp = Arrays.asList(1,2,3,4);
        Iterator<Iterator<Integer>> i = IteratorUtils.map(exp.iterator(), this::expand);
        Iterator<Integer> o = IteratorUtils.flat(i);
        IteratorTester.basicIterator(o, 0, new Integer[]{11,12,13,21,22,23,31,32,33,41,42,43});
    }
    
    Iterator<Integer> expand(Integer v) {
        int d = 10*v;
        return Arrays.asList(d+1, d+2, d+3).iterator();
    }
    
    @Test
    public void testCollect() {
        List<Integer> exp1 = Arrays.asList(1,2,3,4,5,6,7,8,9);
        List<Integer> out1 = IteratorUtils.collect(exp1.listIterator(), new ArrayList<>());
        assertEquals(exp1, out1);
        
        List<Integer> exp2 = Arrays.asList(1);
        List<Integer> out2 = IteratorUtils.collect(exp2.listIterator(), new ArrayList<>());
        assertEquals(exp2, out2);
        
        List<Integer> exp3 = Arrays.asList();
        List<Integer> out3 = IteratorUtils.collect(exp3.listIterator(), new ArrayList<>());
        assertEquals(exp3, out3);
    }
    
    @Test
    public void testCompare() {
        List<Integer> a = Arrays.asList(1,2,3,4,5,6,7,8,9);
        List<Integer> b = Arrays.asList(1,2,3,4,5,6,7,8);
        List<Integer> c = Arrays.asList(1,2,3,8,8,8,7,8);
        List<Integer> d = Arrays.asList(1,2,3,8,8,8,7,8,9,10);
        List<Integer> e = Arrays.asList(4);
        List<Integer> f = Arrays.asList(0);
        List<Integer> g = Arrays.asList();
        
        assertTrue( IteratorUtils.compare(a.iterator(), a.iterator()) == 0);
        assertTrue( IteratorUtils.compare(a.iterator(), b.iterator()) > 0);
        assertTrue( IteratorUtils.compare(a.iterator(), c.iterator()) < 0);
        assertTrue( IteratorUtils.compare(a.iterator(), d.iterator()) < 0);
        assertTrue( IteratorUtils.compare(a.iterator(), e.iterator()) < 0);
        assertTrue( IteratorUtils.compare(a.iterator(), f.iterator()) > 0);
        assertTrue( IteratorUtils.compare(a.iterator(), g.iterator()) > 0);
    }
    
    @Test
    public void testEquals() {
        List<Integer> a = Arrays.asList(1,2,3,4,5,6,7,8,9);
        List<Integer> b = Arrays.asList(1,2,3,4,5,6,7,8);
        List<Integer> c = Arrays.asList(1,2,3,8,8,8,7,8);
        List<Integer> d = Arrays.asList(1,2,3,8,8,8,7,8,9,10);
        List<Integer> e = Arrays.asList(4);
        List<Integer> f = Arrays.asList(0);
        List<Integer> g = Arrays.asList();
        
        assertTrue( IteratorUtils.equals(a.iterator(), a.iterator()) );
        assertFalse( IteratorUtils.equals(a.iterator(), b.iterator()) );
        assertFalse( IteratorUtils.equals(a.iterator(), c.iterator()) );
        assertFalse( IteratorUtils.equals(a.iterator(), d.iterator()) );
        assertFalse( IteratorUtils.equals(a.iterator(), e.iterator()) );
        assertFalse( IteratorUtils.equals(a.iterator(), f.iterator()) );
        assertFalse( IteratorUtils.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
        
        List<Integer> list = Arrays.asList(1,2,3,4,5);
        Iterator<Integer> i = list.iterator();
        assertEquals(5, IteratorUtils.next(i, 8));
        assertFalse(i.hasNext());
    }
	
}
