package freenet.support;

import static org.junit.Assert.*;

import java.util.Enumeration;
import java.util.NoSuchElementException;

import org.junit.Test;

import freenet.support.DoublyLinkedListImpl.Item;

public class DoublyLinkedListImplTest {
	private static class T extends Item<T> {
		int value;
		boolean isClone;

		T(int v) {
			value = v;
		}

		@Override
		public T clone() {
			T c = new T(value);
			c.isClone = true;
			return c;
		}

		@Override
		public String toString() {
			if (isClone)
				return "[" + value + "]";
			else
				return "(" + value + ")";
		}

		void assertV(int v) {
			assertEquals(v, value);
		}

		@Override
		public boolean equals(Object o) {
			if (o == null)
				return false;
			if (o.getClass() != this.getClass())
				return false;
			T t = (T) o;
			return t.value == value && t.isClone == isClone;
		}

		@Override
		public int hashCode() {
			return value;
		}
	}

	@Test
	public void testForwardPushPop() {
		DoublyLinkedList<T> list = new DoublyLinkedListImpl<T>();
		list.push(new T(0));
		list.push(new T(1));
		list.push(new T(2));
		list.push(new T(3));

		assertFalse("isEmpty()", list.isEmpty());
		list.pop().assertV(3);
		assertFalse("isEmpty()", list.isEmpty());
		list.pop().assertV(2);
		assertFalse("isEmpty()", list.isEmpty());

		// add again
		list.push(new T(4));
		list.push(new T(5));

		list.pop().assertV(5);
		assertFalse("isEmpty()", list.isEmpty());
		list.pop().assertV(4);
		assertFalse("isEmpty()", list.isEmpty());
		list.pop().assertV(1);
		assertFalse("isEmpty()", list.isEmpty());
		list.pop().assertV(0);

		assertTrue("isEmpty()", list.isEmpty());
		assertNull("pop()", list.pop());
	}

	@Test
	public void testForwardShiftUnshift() {
		DoublyLinkedList<T> list = new DoublyLinkedListImpl<T>();
		list.unshift(new T(0));
		list.unshift(new T(1));
		list.unshift(new T(2));
		list.unshift(new T(3));

		assertFalse("isEmpty()", list.isEmpty());
		list.shift().assertV(3);
		assertFalse("isEmpty()", list.isEmpty());
		list.shift().assertV(2);
		assertFalse("isEmpty()", list.isEmpty());

		// add again
		list.unshift(new T(4));
		list.unshift(new T(5));

		list.shift().assertV(5);
		assertFalse("isEmpty()", list.isEmpty());
		list.shift().assertV(4);
		assertFalse("isEmpty()", list.isEmpty());
		list.shift().assertV(1);
		assertFalse("isEmpty()", list.isEmpty());
		list.shift().assertV(0);

		assertTrue("isEmpty()", list.isEmpty());
		assertNull("shift()", list.shift());
	}

	@Test
	public void testClearSize() {
		DoublyLinkedList<T> list = new DoublyLinkedListImpl<T>();
		list.unshift(new T(0));
		list.unshift(new T(1));
		list.unshift(new T(2));
		list.unshift(new T(3));

		assertEquals("size()", 4, list.size());
		assertFalse("isEmpty()", list.isEmpty());
		list.shift().assertV(3);
		assertEquals("size()", 3, list.size());
		assertFalse("isEmpty()", list.isEmpty());
		list.shift().assertV(2);
		assertEquals("size()", 2, list.size());
		assertFalse("isEmpty()", list.isEmpty());

		list.clear();

		assertEquals("size()", 0, list.size());
		assertTrue("isEmpty()", list.isEmpty());

		// add again
		list.unshift(new T(4));
		list.unshift(new T(5));
		assertEquals("size()", 2, list.size());
		assertFalse("isEmpty()", list.isEmpty());

		list.shift().assertV(5);
		list.shift().assertV(4);

		assertEquals("size()", 0, list.size());
		assertTrue("isEmpty()", list.isEmpty());
	}

	//	public void testClone() {
	//		DoublyLinkedList<T> list = new DoublyLinkedListImpl<T>();
	//		for (int i = 0; i < 3; i++) {
	//			list.unshift(new T(i));
	//		}
	//
	//		DoublyLinkedList<T> listClone = list.clone();
	//
	//		for (int i = 2; i >= 0; i--) {
	//			T t = (T) list.shift();
	//			t.assertV(i);
	//			t.assertIsNotClone();
	//
	//			T tc = (T) listClone.shift();
	//			tc.assertV(i);
	//			tc.assertIsClone();
	//		}
	//	}

	@Test
	public void testShiftN() {
		DoublyLinkedList<T> list = new DoublyLinkedListImpl<T>();

		for (int i = 0; i < 5; i++) {
			list.push(new T(i));
		}

		DoublyLinkedList<T> list2 = list.shift(2);
		assertEquals("list2.size()", 2, list2.size());
		list2.shift().assertV(0);
		list2.shift().assertV(1);
		assertTrue("list2.isEmpty()", list2.isEmpty());

		assertEquals("list.size()", 3, list.size());
		list.shift().assertV(2);

		list2 = list.shift(20);
		assertTrue("list.isEmpty()", list.isEmpty());
		list2.shift().assertV(3);
		list2.shift().assertV(4);
		assertTrue("list2.isEmpty()", list2.isEmpty());

		list2 = list.shift(20);
		assertTrue("list2.isEmpty()", list2.isEmpty());
	}

	@Test
	public void testPopN() {
		DoublyLinkedList<T> list = new DoublyLinkedListImpl<T>();

		for (int i = 0; i < 5; i++) {
			list.unshift(new T(i));
		}

		DoublyLinkedList<T> list2 = list.pop(2);
		assertEquals("list2.size()", 2, list2.size());
		list2.pop().assertV(0);
		list2.pop().assertV(1);
		assertTrue("list2.isEmpty()", list2.isEmpty());

		assertEquals("list.size()", 3, list.size());
		list.pop().assertV(2);

		list2 = list.pop(20);
		assertTrue("list.isEmpty()", list.isEmpty());
		list2.pop().assertV(3);
		list2.pop().assertV(4);
		assertTrue("list2.isEmpty()", list2.isEmpty());

		list2 = list.pop(20);
		assertTrue("list2.isEmpty()", list2.isEmpty());
	}

	@Test
	public void testHeadTail() {
		DoublyLinkedList<T> list = new DoublyLinkedListImpl<T>();

		assertNull("head() == null", list.head());
		assertNull("tail() == null", list.tail());

		T[] array = new T[5];
		for (int i = 0; i < 5; i++) {
			array[i] = new T(i);
			list.push(array[i]);
		}

		assertTrue("head() == 0", array[0] == list.head());
		assertTrue("tail() == 4", array[4] == list.tail());

		list.shift();
		assertTrue("head() == 1", array[1] == list.head());
		assertTrue("tail() == 4", array[4] == list.tail());

		list.pop();
		assertTrue("head() == 1", array[1] == list.head());
		assertTrue("tail() == 3", array[3] == list.tail());

		list.clear();

		assertNull("head() == null", list.head());
		assertNull("tail() == null", list.tail());
	}

	@Test
	public void testIternator() {
		DoublyLinkedList<T> list = new DoublyLinkedListImpl<T>();
		T[] array = new T[5];

		for (int i = 0; i < 5; i++) {
			array[i] = new T(i);
			list.push(array[i]);
		}

		// manual, forward
		T h = list.head();
		for (int i = 0; i < 5; i++) {
			assertEquals("manual iternate, forward", array[i], h);
			//assertEquals("DoublyLinkedList.next() == Item.next()", h.getNext(), list.next(h));
			assertEquals("hasNext()", i != 4, list.hasNext(h));
			assertEquals("hasPrev()", i != 0, list.hasPrev(h));

			h.assertV(i);

			h = list.next(h);
		}
		assertEquals("h==null", null, h);

		// manual, reverse
		T t = list.tail();
		for (int i = 4; i >= 0; i--) {
			assertEquals("manual iternate, reverse", array[i], t);
			//assertEquals("DoublyLinkedList.prev() == Item.getPrev()", tail.getPrev(), list.prev(tail));
			assertEquals("hasNext()", i != 4, list.hasNext(t));
			assertEquals("hasPrev()", i != 0, list.hasPrev(t));

			t.assertV(i);

			t = list.prev(t);
		}
		assertNull("t==null", t);

		Enumeration<T> e = list.elements();
		for (int i = 0; i < 5; i++) {
			assertTrue("hasMoreElements()", e.hasMoreElements());

			T n = e.nextElement();
			n.assertV(i);

			assertEquals("hasMoreElements()", i != 4, e.hasMoreElements());
		}
		try {
			e.nextElement();
			fail("NoSuchElementException");
		} catch (NoSuchElementException nsee) {
		}
	}

	@Test
	public void testRandomRemovePush() {
		DoublyLinkedList<T> list = new DoublyLinkedListImpl<T>();
		T[] array = new T[5];

		for (int i = 0; i < 5; i++) {
			array[i] = new T(i);
			list.push(array[i]);
		}

		assertTrue(list.remove(array[3]) == array[3]);
		list.push(array[3]);

		// Remove non-exist item -> give null
		assertNull(list.remove(new T(-1)));

		// Remove non-identical (but equal) item -> give null
		assertNull(list.remove(new T(2)));

		list.shift().assertV(0);
		list.shift().assertV(1);
		list.shift().assertV(2);
		list.shift().assertV(4);
		list.shift().assertV(3);

		assertNull(list.remove(new T(-1)));
	}

	@Test
	public void testRandomShiftPush() {
		DoublyLinkedList<T> list = new DoublyLinkedListImpl<T>();
		list.push(new T(0));
		list.push(new T(1));
		list.unshift(new T(2));
		list.push(new T(3));
		list.unshift(new T(4));
		list.unshift(new T(5));

		list.shift().assertV(5);
		list.pop().assertV(3);
		list.pop().assertV(1);
		list.pop().assertV(0);
		list.shift().assertV(4);
		list.shift().assertV(2);
	}

	@Test
	public void testRandomInsert() {
		DoublyLinkedList<T> list = new DoublyLinkedListImpl<T>();
		T[] array = new T[5];

		for (int i = 0; i < 5; i++) {
			array[i] = new T(i);
			list.push(array[i]);
		}

		list.insertPrev(array[0], new T(100));
		list.insertPrev(array[2], new T(102));
		list.insertNext(array[4], new T(104));
		list.insertNext(array[4], new T(105));

		DoublyLinkedList<T> list2 = new DoublyLinkedListImpl<T>();
		T l2 = new T(9999);
		list2.push(l2);
		try {
			// already exist
			list2.insertNext(l2, l2);
			fail("PromiscuousItemException");
		} catch (PromiscuousItemException pie) {
		}
		try {
			// already exist
			list2.insertNext(l2, l2);
			fail("PromiscuousItemException");
		} catch (PromiscuousItemException pie) {
		}
		try {
			// bad position
			list2.insertPrev(array[3], new T(8888));
			fail("PromiscuousItemException");
		} catch (PromiscuousItemException pie) {
		}
		try {
			// bad position
			list2.insertNext(array[3], new T(8888));
			fail("PromiscuousItemException");
		} catch (PromiscuousItemException pie) {
		}

		try {
			// item in other list
			list2.insertPrev(l2, array[3]);
			fail("PromiscuousItemException");
		} catch (PromiscuousItemException pie) {
		}
		try {
			// item in other list
			list2.insertNext(l2, array[3]);
			fail("PromiscuousItemException");
		} catch (PromiscuousItemException pie) {
		}

		T l3 = new T(9999);
		list2.push(l3);
		try {
			// VirginItemException
			l3.setPrev(null); // corrupt it
			list2.insertPrev(l3, new T(8888));
			fail("VirginItemException");
		} catch (VirginItemException vie) {
		}
		try {
			// VirginItemException
			l2.setNext(null); // corrupt it
			list2.insertNext(l2, new T(8888));
			fail("VirginItemException");
		} catch (VirginItemException vie) {
		}

		list.shift().assertV(100);
		list.shift().assertV(0);
		list.shift().assertV(1);
		list.shift().assertV(102);
		list.shift().assertV(2);
		list.shift().assertV(3);
		list.shift().assertV(4);
		list.shift().assertV(105);
		list.shift().assertV(104);

	}
}
