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

import java.util.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.function.Function;
import java.util.function.IntFunction;
import javax.annotation.Resource;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.junit.TestContract;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
@SuppressWarnings("PMD.MethodNameRules")
public final class IteratorTester<T> {

    
    @Resource(name = "iterator.array!")
    private IntFunction<T[]> $data;
    
    
    public static final class IteratorParams {
        
        public final int index;
        public final int size;
        
        public IteratorParams() {
            this(0,0);
        }

        public IteratorParams(int index, int size) {
            this.index = index;
            this.size = size;
        }
        
    }
        
    @TestContract
    public void basicIterator(Function<IteratorParams, Iterator<T>> f) {
        basicIterator(f, new IteratorParams(0, 16));
        basicIterator(f, new IteratorParams(5, 16));
        basicIterator(f, new IteratorParams(0, 1));
        basicIterator(f, new IteratorParams(0, 0));
    }
    
    @TestContract
    public void basicIterator_Next(Function<IteratorParams, Iterator<T>> f) {
        basicIterator_Next(f, new IteratorParams(0, 16));
        basicIterator_Next(f, new IteratorParams(5, 16));
        basicIterator_Next(f, new IteratorParams(0, 1));
        basicIterator_Next(f, new IteratorParams(0, 0));
    }
    
    @TestContract
    public void listIterator(Function<IteratorParams, ListIterator<T>> f) {
        listIterator(f, new IteratorParams(0, 16));
        listIterator(f, new IteratorParams(5, 16));
        listIterator(f, new IteratorParams(0, 1));
        listIterator(f, new IteratorParams(0, 0));
    }
    
    @TestContract
    public void listIterator_Next(Function<IteratorParams, ListIterator<T>> f) {
        listIterator_Next(f, new IteratorParams(0, 16));
        listIterator_Next(f, new IteratorParams(5, 16));
        listIterator_Next(f, new IteratorParams(0, 1));
        listIterator_Next(f, new IteratorParams(0, 0));
    }
    
    private void basicIterator(Function<IteratorParams, Iterator<T>> f, IteratorParams params) {
        T[] content = $data.apply(params.size);
        Iterator<T> iterator = f.apply(params);
        
        for (int i=params.index; i<content.length; i++) {
            assertTrue(iterator.hasNext());
            assertEquals(content[i], iterator.next());
        }
        assertFalse(iterator.hasNext());
        assertThrows(NoSuchElementException.class, ()->
            iterator.next()
        );
    }
    
    private void basicIterator_Next(Function<IteratorParams, Iterator<T>> f, IteratorParams params) {
        T[] content = $data.apply(params.size);
        Iterator<T> iterator = f.apply(params);
        
        for (int i=params.index; i<content.length; i++) {
            assertEquals(content[i], iterator.next());
        }
        assertThrows(NoSuchElementException.class, ()->
            iterator.next()
        );
    }
    
    private void listIterator(Function<IteratorParams, ListIterator<T>> f, IteratorParams params) {
        T[] content = $data.apply(params.size);
        ListIterator<T> iterator = f.apply(params);
        
        assertEquals(params.index>0, iterator.hasPrevious());
        assertEquals(content.length>0, iterator.hasNext());
        
        for (int i=params.index; i<content.length; i++) {
            checkIndex(iterator, i);
            assertTrue(iterator.hasNext());
            assertEquals(content[i], iterator.next());
        }
        assertFalse(iterator.hasNext());
        assertEquals(content.length>0, iterator.hasPrevious());
        assertThrows(NoSuchElementException.class, ()->
            iterator.next()
        );
		
        for (int i=content.length-1; i>=0; i--) {
            assertTrue(iterator.hasPrevious());
            assertEquals(content[i], iterator.previous());
            checkIndex(iterator, i);
        }
        assertEquals(content.length>0, iterator.hasNext());
        assertFalse(iterator.hasPrevious());
        assertThrows(NoSuchElementException.class, ()->
            iterator.previous()
        );
    }
    
    private void listIterator_Next(Function<IteratorParams, ListIterator<T>> f, IteratorParams params) {
        T[] content = $data.apply(params.size);
        ListIterator<T> iterator = f.apply(params);
        
        for (int i=params.index; i<content.length; i++) {
            assertEquals(content[i], iterator.next());
        }
        assertThrows(NoSuchElementException.class, ()->
            iterator.next()
        );
		
        for (int i=content.length-1; i>=0; i--) {
            assertEquals(content[i], iterator.previous());
        }
        assertThrows(NoSuchElementException.class, ()->
            iterator.previous()
        );
    }
    
    public static <T> void basicIterator(Iterator<T> iterator, int index, T[] content) {
        for (int i=index; i<content.length; i++) {
            assertTrue(iterator.hasNext());
            T next = iterator.next();
            assertEquals(content[i], next);
        }
        assertFalse(iterator.hasNext());
        assertThrows(NoSuchElementException.class, ()->
            iterator.next()
        );
    }
    
    public static <T> void orderIterator(ListIterator<T> iterator, int index, T[] content) {
        assertEquals(index>0, iterator.hasPrevious());
        assertEquals(content.length>0, iterator.hasNext());
        
        for (int i=index; i<content.length; i++) {
            checkIndex(iterator, i-index);
            assertTrue(iterator.hasNext());
            assertEquals(content[i], iterator.next());
        }
        
        assertFalse(iterator.hasNext());
        assertEquals(content.length>0, iterator.hasPrevious());
        assertThrows(NoSuchElementException.class, ()->
            iterator.next()
        );
        
        for (int i=content.length-1; i>=0; i--) {
            assertTrue(iterator.hasPrevious());
            assertEquals(content[i], iterator.previous());
            checkIndex(iterator, i-index);
        }
        
        assertEquals(content.length>0, iterator.hasNext());
        assertFalse(iterator.hasPrevious());
        assertThrows(NoSuchElementException.class, ()->
            iterator.previous()
        );
    }
    
    public static <T> void listIterator(ListIterator<T> iterator, int index, T[] content) {
        assertEquals(index>0, iterator.hasPrevious());
        assertEquals(content.length>0, iterator.hasNext());
        
//		System.err.printf("DATA = [%d] %s %n", index, Arrays.toString(content));
//		System.err.printf(">>>>>>%n");
        for (int i=index; i<content.length; i++) {
            checkIndex(iterator, i);
            assertTrue(iterator.hasNext());
            assertEquals(content[i], iterator.next());
        }
//        System.err.printf("    * %d / %d%n", iterator.previousIndex(), iterator.nextIndex());
        assertFalse(iterator.hasNext());
//		System.err.printf("    * %d / %d%n", iterator.previousIndex(), iterator.nextIndex());
        assertEquals(content.length>0, iterator.hasPrevious());
//		System.err.printf("    * %d / %d%n", iterator.previousIndex(), iterator.nextIndex());
        assertThrows(NoSuchElementException.class, ()->
            iterator.next()
        );
//		System.err.printf("    * %d / %d%n", iterator.previousIndex(), iterator.nextIndex());
//        System.err.printf("------------%n");
		
        for (int i=content.length-1; i>=0; i--) {
            assertTrue(iterator.hasPrevious());
            assertEquals(content[i], iterator.previous());
            checkIndex(iterator, i);
        }
//        System.err.printf("<<<<<<%n");
        assertEquals(content.length>0, iterator.hasNext());
        assertFalse(iterator.hasPrevious());
        assertThrows(NoSuchElementException.class, ()->
            iterator.previous()
        );
    }
    
    public static <T> void subIterator(ListIterator<T> iterator, int index, T[] content) {
        for (int i=index; i<content.length; i++) {
            checkIndex(iterator, i-index);
            assertTrue(iterator.hasNext());
            assertEquals(content[i], iterator.next());
        }
        for (int i=content.length-1; i>=0; i--) {
            assertTrue(iterator.hasPrevious());
            assertEquals(content[i], iterator.previous());
            checkIndex(iterator, i-index);
        }
    }
    
    private static void checkIndex(ListIterator<?> iterator, int current) {
        assertEquals(current, iterator.nextIndex());
        assertEquals(current-1, iterator.previousIndex());
    }

}
