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

import net.ranides.assira.ContractTesters;
import net.ranides.assira.collection.lists.IntRange;
import net.ranides.assira.collection.mockup.TCollections;
import net.ranides.assira.collection.suite.IteratorTester;
import org.junit.Test;

import java.util.*;

import static net.ranides.assira.junit.NewAssert.assertThrows;
import static org.junit.Assert.*;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class EnumerationUtilsTest {

    @Test
    public void testFirst() {
        assertEquals((Integer)1, EnumerationUtils.first(vector(1,2,3,4).elements()).get());
        assertEquals((Integer)1, EnumerationUtils.first(vector(1,2).elements()).get());
        assertEquals((Integer)1, EnumerationUtils.first(vector(1).elements()).get());

        assertThrows(NoSuchElementException.class, () -> {
            EnumerationUtils.first(vector().elements()).get();
        });
    }

    @Test
    public void testLast() {
        assertEquals((Integer)4, EnumerationUtils.last(vector(1,2,3,4).elements()).get());
        assertEquals((Integer)4, EnumerationUtils.last(vector(3,4).elements()).get());
        assertEquals((Integer)4, EnumerationUtils.last(vector(4).elements()).get());

        assertThrows(NoSuchElementException.class, () -> {
            EnumerationUtils.last(vector().elements()).get();
        });
    }


    @Test
    public void testFirst_Filter() {
        assertEquals((Integer)7, EnumerationUtils.first(vector(1,2,7,0,8,0,3).elements(), a -> a>5).get());

        assertThrows(NoSuchElementException.class, () -> {
            EnumerationUtils.first(vector(1,2,3).elements(), a -> a>5).get();
        });
        assertThrows(NoSuchElementException.class, () -> {
            EnumerationUtils.first(EnumerationUtilsTest.<Integer>vector().elements(), a -> a>5).get();
        });
    }

    @Test
    public void testLast_Filter() {
        assertEquals((Integer)8, EnumerationUtils.last(vector(1,2,7,0,8,0,3).elements(), a -> a>5).get());
        assertEquals((Integer)8, EnumerationUtils.last(vector(1,2,7,0,8).elements(), a -> a>5).get());
        assertEquals((Integer)8, EnumerationUtils.last(vector(1,8).elements(), a -> a>5).get());
        assertEquals((Integer)8, EnumerationUtils.last(vector(8,1).elements(), a -> a>5).get());

        assertThrows(NoSuchElementException.class, () -> {
            EnumerationUtils.last(vector(1,2,3).elements(), a -> a>5).get();
        });
        assertThrows(NoSuchElementException.class, () -> {
            EnumerationUtils.last(EnumerationUtilsTest.<Integer>vector().elements(), a -> a>5).get();
        });
    }

    @Test
    public void testSize() {
        assertEquals(4, EnumerationUtils.size(vector(1,2,7,8).elements()));
        assertEquals(2, EnumerationUtils.size(vector(1,2).elements()));
        assertEquals(1, EnumerationUtils.size(vector(1).elements()));
        assertEquals(0, EnumerationUtils.size(vector().elements()));
        assertEquals(0, EnumerationUtils.size(null));
    }

    @Test
    public void testMap() {
        Vector<String> list = vector("a", "b", "c", "d");

        ArrayList<String> list1 = IteratorUtils.collect(EnumerationUtils.map(list.elements(), e -> e.toUpperCase()), new ArrayList<>());
        ArrayList<String> list2 = IteratorUtils.collect(EnumerationUtils.map(list.elements(), (i,e) -> e.toUpperCase()+i), new ArrayList<>());

        assertEquals(vector("A","B","C","D"), list1);
        assertEquals(vector("A0","B1","C2","D3"), list2);
    }

    @Test
    public void testMap_Iterator_Remove() {
        Vector<String> list = vector("a", "b", "c", "d");
        Iterator<String> b = EnumerationUtils.map(list.elements(), s -> s.toUpperCase());
        b.next();
        assertThrows(UnsupportedOperationException.class, ()->{
            b.remove();
        });
        b.next();
        b.next();
        assertThrows(UnsupportedOperationException.class, ()->{
            b.remove();
        });
        assertEquals(vector("a", "b","c","d"), list);
    }

    @Test
    public void testFilter_Iterator() {
        ContractTesters
            .runner()
            .param("iterator.array!", TCollections.produce(n -> 2*n).asRangeGenerator() )
            .function(new IteratorTester.IteratorParams(), this::range2even)
            .run();
    }

    @Test
    public void testLimit_Iterator() {
        ContractTesters
            .runner()
            .param("iterator.array!", TCollections.produce(n -> n).asRangeGenerator() )
            .function(new IteratorTester.IteratorParams(), this::limit)
            .run();
    }

    private Iterator<Integer> limit(IteratorTester.IteratorParams p) {
        IntRange range = new IntRange(0, 2*p.size);
        IntIterator iterator = range.iterator();
        Iterator<Integer> i = EnumerationUtils.limit(EnumerationUtils.enumeration(iterator), v -> v < p.size);
        EnumerationUtils.next(EnumerationUtils.enumeration(i), p.index);
        return i;
    }

    private Iterator<Integer> range2even(IteratorTester.IteratorParams p) {
        IntRange range = new IntRange(0, 2*p.size);
        Iterator<Integer> i = EnumerationUtils.filter(EnumerationUtils.enumeration(range.iterator()), v -> v%2==0);
        EnumerationUtils.next(EnumerationUtils.enumeration(i), p.index);
        return i;
    }


    @Test
    public void testStream() {
        Vector<Integer> exp = vector(1,2,3,4,5,6,7,8,9);
        List<Integer> out = new ArrayList<>();
        EnumerationUtils.stream( exp.elements() ).forEach(v -> out.add(v));
        assertEquals(exp, out);
    }

    @Test
    public void testFlat() {
        Vector<Integer> exp = vector(1,2,3,4);
        Iterator<Enumeration<Integer>> i = EnumerationUtils.map(exp.elements(), this::expand);
        Iterator<Integer> o = EnumerationUtils.flat(EnumerationUtils.enumeration(i));
        IteratorTester.basicIterator(o, 0, new Integer[]{11,12,13,21,22,23,31,32,33,41,42,43});
    }

    Enumeration<Integer> expand(Integer v) {
        int d = 10*v;
        return vector(d+1, d+2, d+3).elements();
    }

    @Test
    public void testCollect() {
        Vector<Integer> exp1 = vector(1,2,3,4,5,6,7,8,9);
        List<Integer> out1 = EnumerationUtils.collect(exp1.elements(), new ArrayList<>());
        assertEquals(exp1, out1);

        Vector<Integer> exp2 = vector(1);
        List<Integer> out2 = EnumerationUtils.collect(exp2.elements(), new ArrayList<>());
        assertEquals(exp2, out2);

        Vector<Integer> exp3 = vector();
        List<Integer> out3 = EnumerationUtils.collect(exp3.elements(), new ArrayList<>());
        assertEquals(exp3, out3);
    }

    @Test
    public void testCompare() {
        Vector<Integer> a = vector(1,2,3,4,5,6,7,8,9);
        Vector<Integer> b = vector(1,2,3,4,5,6,7,8);
        Vector<Integer> c = vector(1,2,3,8,8,8,7,8);
        Vector<Integer> d = vector(1,2,3,8,8,8,7,8,9,10);
        Vector<Integer> e = vector(4);
        Vector<Integer> f = vector(0);
        Vector<Integer> g = vector();

        assertTrue( EnumerationUtils.compare(a.elements(), a.elements()) == 0);
        assertTrue( EnumerationUtils.compare(a.elements(), b.elements()) > 0);
        assertTrue( EnumerationUtils.compare(a.elements(), c.elements()) < 0);
        assertTrue( EnumerationUtils.compare(a.elements(), d.elements()) < 0);
        assertTrue( EnumerationUtils.compare(a.elements(), e.elements()) < 0);
        assertTrue( EnumerationUtils.compare(a.elements(), f.elements()) > 0);
        assertTrue( EnumerationUtils.compare(a.elements(), g.elements()) > 0);
    }

    @Test
    public void testEquals() {
        Vector<Integer> a = vector(1,2,3,4,5,6,7,8,9);
        Vector<Integer> b = vector(1,2,3,4,5,6,7,8);
        Vector<Integer> c = vector(1,2,3,8,8,8,7,8);
        Vector<Integer> d = vector(1,2,3,8,8,8,7,8,9,10);
        Vector<Integer> e = vector(4);
        Vector<Integer> f = vector(0);
        Vector<Integer> g = vector();

        assertTrue( EnumerationUtils.equals(a.elements(), a.elements()) );
        assertFalse( EnumerationUtils.equals(a.elements(), b.elements()) );
        assertFalse( EnumerationUtils.equals(a.elements(), c.elements()) );
        assertFalse( EnumerationUtils.equals(a.elements(), d.elements()) );
        assertFalse( EnumerationUtils.equals(a.elements(), e.elements()) );
        assertFalse( EnumerationUtils.equals(a.elements(), f.elements()) );
        assertFalse( EnumerationUtils.equals(a.elements(), g.elements()) );
    }

    @Test
    public void testNext() {
        // dość mocno już testujemy #next w testach związanych z filtrami
        // tutaj sprawdźmy zachowanie out-of-bound

        Vector<Integer> list = vector(1,2,3,4,5);
        Enumeration<Integer> i = list.elements();
        assertEquals(5, EnumerationUtils.next(i, 8));
        assertFalse(i.hasMoreElements());
    }

    private static <T> Vector<T> vector(T... values) {
        return new Vector<>(Arrays.asList(values));
    }

}