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

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.function.Function;
import javax.annotation.Resource;
import net.ranides.assira.collection.suite.IteratorTester;
import net.ranides.assira.junit.TestContractRunner;
import net.ranides.assira.test.TCollection;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.junit.TestContract;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
@SuppressWarnings("PMD.MethodNameRules")
public class ListTester<T> {
    
    @Resource(name = "collection!")
    private TCollection<T> $var;
    
    @Resource
    private TestContractRunner runner;

    @TestContract
    public void basicAdd(List<T> target) {
        assertEquals(0, target.size());
        
        for(TCollection.TItem<T> item : $var.range(32)) {
            target.add(item.value());
        }
        
        assertEquals(32, target.size());
        assertEquals($var.item(4).value(), target.get(4));
		assertEquals($var.range(32).into(new ArrayList<>()), target);
    }
	
	@TestContract
    public void basicAddAt(List<T> target) {
		assertEquals(0, target.size());
		$var.range(8).into(target);
		target.add(0, $var.item(71).value());
		target.add(0, $var.item(72).value());
		target.add(4, $var.item(73).value());
		target.add(6, $var.item(74).value());
		target.add(11, $var.item(75).value());
		target.add(13, $var.item(76).value());

		assertEquals( $var.list(72, 71, 0, 1, 73, 2, 74, 3, 4, 5, 6, 75, 7, 76).into(new ArrayList<>()), target);
        
                
        assertThrows(IndexOutOfBoundsException.class, ()->{
            target.add(-1, $var.item(66).value());
        });
        assertThrows(IndexOutOfBoundsException.class, ()->{
            target.add(100, $var.item(66).value());
        });
        
	}
    
    @TestContract
    public void basicAddAll(List<T> target) {
        assertEquals(0, target.size());
        
		assertTrue(target.addAll($var.range(0,8).into(new ArrayList<>())));
		assertTrue(target.addAll($var.range(10,18).into(new ArrayList<>())));
		assertFalse(target.addAll(new ArrayList<>()));
        
        assertEquals(16, target.size());
        assertEquals($var.list(0,1,2,3,4,5,6,7,10,11,12,13,14,15,16,17).into(new ArrayList<>()), target);
    }
	
	@TestContract
    public void basicAddAllAt(List<T> target) {
        assertEquals(0, target.size());
        
		assertTrue(target.addAll($var.range(0,8).into(new ArrayList<>())));
		assertTrue(target.addAll(4, $var.range(10,18).into(new ArrayList<>())));
		assertFalse(target.addAll(4, new ArrayList<>()));
		
        assertEquals(16, target.size());
        assertEquals($var.list(0,1,2,3,10,11,12,13,14,15,16,17,4,5,6,7).into(new ArrayList<>()), target);
		
		assertTrue(target.addAll(target.size(), $var.list(31,32,33).into(new ArrayList<>())));
		assertThrows(IndexOutOfBoundsException.class, ()->{
			target.addAll(target.size()+1, $var.list(41,42,43).into(new ArrayList<>()));
		});

		assertEquals($var.list(0,1,2,3,10,11,12,13,14,15,16,17,4,5,6,7,31,32,33).into(new ArrayList<>()), target);
        
    }
    
    @TestContract
    public void basicGet(Function<int[], List<T>> f) {
        List<T> target = f.apply(new int[]{0,1,2,3,4,5,6,7});
        
        assertEquals(8, target.size());
        
		assertEquals($var.item(3).value(), target.get(3));
		assertEquals($var.item(5).value(), target.get(5));
		assertEquals($var.item(7).value(), target.get(7));
        assertThrows(IndexOutOfBoundsException.class, ()->{
            target.get(-1);
        });
        assertThrows(IndexOutOfBoundsException.class, ()->{
            target.get(20);
        });
        assertThrows(IndexOutOfBoundsException.class, ()->{
            target.get(8);
        });
    }
	
	@TestContract
    public void basicSet(Function<int[], List<T>> f) {
		List<T> target = f.apply(new int[]{0,1,2,3,4,5,6,7});
		target.set(0, $var.item(21).value());
		target.set(2, $var.item(22).value());
		target.set(7, $var.item(23).value());

		assertThrows(IndexOutOfBoundsException.class, ()->{
			target.set(8, $var.item(26).value());
		});
		assertThrows(IndexOutOfBoundsException.class, ()->{
			target.set(-1, $var.item(27).value());
		});
		assertEquals( $var.list(21,1,22,3,4,5,6,23).into(new ArrayList<>()), target);
	}
	
	@TestContract
    public void basicIndexOf(Function<int[], List<T>> f) {
		List<T> target = f.apply(new int[]{1,3,5,7,9,3,3,11});
		
		assertEquals(0, target.indexOf($var.item(1).value()));
		assertEquals(1, target.indexOf($var.item(3).value()));
		assertEquals(2, target.indexOf($var.item(5).value()));
		assertEquals(3, target.indexOf($var.item(7).value()));
		assertEquals(4, target.indexOf($var.item(9).value()));
		assertEquals(7, target.indexOf($var.item(11).value()));
		assertEquals(-1, target.indexOf($var.item(0).value()));
	}
	
	@TestContract
    public void basicLastIndexOf(Function<int[], List<T>> f) {
		List<T> target = f.apply(new int[]{1,3,5,7,9,3,3,11});
		
		assertEquals(6, target.lastIndexOf($var.item(3).value()));
        assertEquals(0, target.lastIndexOf($var.item(1).value()));
		assertEquals(2, target.lastIndexOf($var.item(5).value()));
		assertEquals(3, target.lastIndexOf($var.item(7).value()));
		assertEquals(4, target.lastIndexOf($var.item(9).value()));
		assertEquals(7, target.lastIndexOf($var.item(11).value()));
		assertEquals(-1, target.lastIndexOf($var.item(0).value()));
	}
	
	@TestContract
    public void basicRemove(Function<int[], List<T>> f) {
		List<T> target = f.apply(new int[]{1,3,5,7,9,11});
		
		assertTrue(target.remove( $var.item(5).value()));
		assertEquals( $var.list(1,3,7,9,11).into(new ArrayList<>()), target);
		
		assertTrue(target.remove( $var.item(1).value()));
		assertEquals( $var.list(3,7,9,11).into(new ArrayList<>()), target);
		
		assertTrue(target.remove( $var.item(11).value()));
		assertEquals( $var.list(3,7,9).into(new ArrayList<>()), target);
		
		assertFalse(target.remove( $var.item(0).value()));
		assertEquals( $var.list(3,7,9).into(new ArrayList<>()), target);
	}
	
	@TestContract
	public void basicRemoveRetainAll(Function<int[], List<T>> f) {
		List<T> target2 = f.apply(new int[]{1,3,5,7,9,11});
		target2.removeAll(f.apply(new int[]{1,5,7,11}));
		assertEquals( $var.list(3,9).into(new ArrayList<>()), target2);
		
		List<T> target3 = f.apply(new int[]{1,3,5,7,9,11});
		target3.retainAll(f.apply(new int[]{2,5,9,12}));
		assertEquals( $var.list(5,9).into(new ArrayList<>()), target3);
	}
	
	@TestContract
    public void basicRemoveIndex(Function<int[], List<T>> f) {
		List<T> target = f.apply(new int[]{1,3,5,7,9,11});
		
		assertEquals($var.item(5).value(), target.remove(2));
		assertEquals( $var.list(1,3,7,9,11).into(new ArrayList<>()), target);
		
		assertEquals($var.item(1).value(), target.remove(0));
		assertEquals( $var.list(3,7,9,11).into(new ArrayList<>()), target);
		
		assertEquals($var.item(11).value(), target.remove(3));
		assertEquals( $var.list(3,7,9).into(new ArrayList<>()), target);
		
		assertThrows(IndexOutOfBoundsException.class, ()->{
			target.remove(20);
		});
		assertThrows(IndexOutOfBoundsException.class, ()->{
			target.remove(-1);
		});
		assertEquals( $var.list(3,7,9).into(new ArrayList<>()), target);
		
		target.clear();
		assertThrows(IndexOutOfBoundsException.class, ()->{
			target.remove(0);
		});
	}
    
    @TestContract
    public void basicClear(Function<int[], List<T>> f) {
		List<T> target = f.apply(new int[]{1,3,5,7,9,11});
        assertEquals(6, target.size());
        assertFalse(target.isEmpty());
        
        target.clear();
        assertEquals(0, target.size());
        assertTrue(target.isEmpty());
    }
	
	@TestContract
    public void basicClearAdd(Function<int[], List<T>> f) {
		List<T> target = f.apply(new int[]{1,3,5,7,9,11});
        assertEquals(6, target.size());
        assertFalse(target.isEmpty());
        
        target.clear();
        assertEquals(0, target.size());
        assertTrue(target.isEmpty());
        
        $var.list(21,22).into(target);
        assertEquals(2, target.size());
        assertFalse(target.isEmpty());
    }
        
    
    @TestContract
    public void basicSerializable(Function<int[], List<T>> f) {
        List<T> target = f.apply(new int[]{1,2,3,4});
        assertSerializable(target);
    }
    
    @TestContract
    public void basicEquals(Function<int[], List<T>> f) {
        List<T> list1 = f.apply(new int[]{1,2,3,4});
        List<T> list2 = f.apply(new int[]{1,2,3,4});
        List<T> list3 = f.apply(new int[]{1,5,6,8});
        List<T> list4 = f.apply(new int[]{1,2,3});
        List<T> list5 = f.apply(new int[]{1,2,4,3});
        
        assertEquality(list1, list2, list3);
        assertEquality(list1, list2, list4);
        assertEquality(list1, list2, list5);
    }
    
    @TestContract
    public void basicSublist(Function<int[], List<T>> f) {
        List<T> target = f.apply(new int[]{1,3,5,7,9});
        
        assertEquals($list(3,5,7,9), target.subList(1, 5));
        assertEquals($list(3,5,7), target.subList(1, 4));
        assertEquals($list(1,3,5,7,9), target.subList(0, 5));
        
        assertThrows(IndexOutOfBoundsException.class, ()->{
            target.subList(-1, 3);
        });
        assertThrows(IndexOutOfBoundsException.class, ()->{
            target.subList(1, 30);
        });
        assertThrows(IndexOutOfBoundsException.class, ()->{
            target.subList(0, 6);
        });
        assertThrows(IllegalArgumentException.class, ()->{
            target.subList(4, 1);
        });
    }
    
    @TestContract
    public void basicSublist_indexOf(Function<int[], List<T>> f) {
        List<T> target = f.apply(new int[]{1,3,5,7,9});
        
        assertEquals(-1, target.subList(1, 3).indexOf($var.item(9).value()));
        assertEquals(-1, target.subList(1, 3).indexOf($var.item(1).value()));
        assertEquals(1, target.subList(1, 3).indexOf($var.item(5).value()));
    }
    
    @TestContract
    public void basicSublist_lastIndexOf(Function<int[], List<T>> f) {
        List<T> target = f.apply(new int[]{1,3,5,7,9});
        
        assertEquals(-1, target.subList(1, 3).lastIndexOf($var.item(9).value()));
        assertEquals(-1, target.subList(1, 3).lastIndexOf($var.item(1).value()));
        assertEquals(1, target.subList(1, 3).lastIndexOf($var.item(5).value()));
    }
    
    @TestContract
    public void basicSublist_add(Function<int[], List<T>> f) {
        List<T> target = f.apply(new int[]{1,3,5,7,9});
        List<T> sub = target.subList(1, 3);
        
        assertEquals($list(3,5), sub);
        assertEquals($list(1,3,5,7,9), target);
        
        sub.add(1, $var.item(21).value());
        assertEquals($list(3,21,5), sub);
        assertEquals($list(1,3,21,5,7,9), target);
        
        sub.add($var.item(31).value());
        assertEquals($list(3,21,5,31), sub);
        assertEquals($list(1,3,21,5,31,7,9), target);
    }
    
    @TestContract
    public void basicSublist_addAll(Function<int[], List<T>> f) {
        List<T> target = f.apply(new int[]{1,3,5,7,9});
        List<T> sub = target.subList(1, 3);
        
        assertEquals($list(3,5), sub);
        assertEquals($list(1,3,5,7,9), target);
        
        sub.addAll(1, $list(21,22));
        assertEquals($list(3,21,22,5), sub);
        assertEquals($list(1,3,21,22,5,7,9), target);
        
        sub.addAll($list(31,32));
        assertEquals($list(3,21,22,5,31,32), sub);
        assertEquals($list(1,3,21,22,5,31,32,7,9), target);
    }
    
    @TestContract
    public void basicSublist_clear(Function<int[], List<T>> f) {
        List<T> target = f.apply(new int[]{1,3,5,7,9});
        List<T> sub = target.subList(1, 3);
        
        sub.clear();
        assertEquals(Collections.emptyList(), sub);
        assertEquals($list(1,7,9), target);
    }
	
	@TestContract
    public void basicSublist_clearAdd(Function<int[], List<T>> f) {
        List<T> target = f.apply(new int[]{1,3,5,7,9});
        List<T> sub = target.subList(1, 3);
        
        sub.clear();
        assertEquals(Collections.emptyList(), sub);
        assertEquals($list(1,7,9), target);
        
        sub.addAll($list(31,32));
        assertEquals($list(31,32), sub);
        assertEquals($list(1,31,32,7,9), target);
    }
    
    @TestContract
    public void basicSublist_set(Function<int[], List<T>> f) {
        List<T> target = f.apply(new int[]{1,3,5,7,9});
        List<T> sub = target.subList(1, 3);
        
        assertEquals($var.item(3).value(), sub.set(0, $var.item(21).value()));
        assertEquals($var.item(5).value(), sub.set(1, $var.item(22).value()));
        
        assertEquals($var.item(21).value(), sub.get(0));
        assertEquals($var.item(22).value(), sub.get(1));
        
        assertEquals($list(21,22), sub);
        assertEquals($list(1,21,22,7,9), target);
    }
    
    @SuppressWarnings("unchecked")
    @TestContract
    public void basicIterator(Function<int[], List<T>> f) {
        List<T> target1 = f.apply(new int[0]);
        IteratorTester.listIterator(target1.listIterator(), 0, $var.list().values());
        
        List<T> target2 = f.apply(new int[]{3,4,5,6,7});
        IteratorTester.listIterator(target2.listIterator(), 0, $var.list(3, 4, 5, 6, 7).values());
    }
    
    @TestContract
    public void basicIteratorFrom(Function<int[], List<T>> f) {
        List<T> target1 = f.apply(new int[0]);
        IteratorTester.listIterator(target1.listIterator(0), 0, $var.list().values());
        
        
        List<T> target2 = f.apply(new int[]{3,4,5,6,7});
        IteratorTester.listIterator(target2.listIterator(2), 2, $var.list(3, 4, 5, 6, 7).values());
    }
    
    @TestContract
    public void basicIterator_Remove(Function<int[], List<T>> f) {
		List<T> target = f.apply(new int[]{1,2,3,5});
		T v1 = $var.item(1).value();
		T v2 = $var.item(3).value();
        
		Iterator<T> iterator = target.iterator();
		while(iterator.hasNext()) {
			T var = iterator.next();
			if(v1.equals(var)) {
                iterator.remove();
            }
            if(v2.equals(var)) {
                iterator.remove();
                assertThrows(IllegalStateException.class, ()-> iterator.remove());
            }
		}
        
        assertEquals(2, target.size());
        assertEquals(-1, target.indexOf(v1));
        assertEquals(-1, target.indexOf(v2));
        assertEquals($list(2,5), target);
        
        target.clear();
        assertEquals(0, target.size());
	}
    
    @TestContract
    public void basicIterator_Set(Function<int[], List<T>> f) {
		List<T> target = f.apply(new int[]{1,2,3,5});
		T v1 = $var.item(1).value();
		T v2 = $var.item(3).value();
        
		ListIterator<T> iterator = target.listIterator();
        
        assertThrows(IllegalStateException.class, ()->{
            iterator.set($var.item(41).value());
        });
        
		while(iterator.hasNext()) {
			T var = iterator.next();
			if(v1.equals(var)) {
                iterator.set($var.item(21).value());
            }
            if(v2.equals(var)) {
                iterator.set($var.item(31).value());
                iterator.set($var.item(32).value());
            }
		}
        assertEquals($list(21,2,32,5), target);
	}
    
    @TestContract
    public void basicIterator_Add(Function<int[], List<T>> f) {
		List<T> target = f.apply(new int[]{1,2,3,5});
		T v1 = $var.item(1).value();
		T v2 = $var.item(3).value();
        
		ListIterator<T> iterator = target.listIterator();
        
        iterator.add($var.item(41).value());
        
		while(iterator.hasNext()) {
			T var = iterator.next();
			if(v1.equals(var)) {
                iterator.add($var.item(21).value());
            }
            if(v2.equals(var)) {
                iterator.add($var.item(31).value());
                iterator.add($var.item(32).value());
            }
		}
        assertEquals($list(41, 1, 21, 2, 3, 31, 32, 5), target);
	}
    
    @TestContract
    public void nestedSublistTest(Function<int[], List<T>> f) {
        new TestContractRunner(runner)
            .ignore("ListTester.nestedSublistTest")
            .function(new int[0], array -> f.apply(array).subList(0, array.length))
			.run();
    }
    
    private List<T> $list(int... values) {
        return $var.list(values).into(new ArrayList<>());
    }
            
}
