package kr.util;

import java.util.ArrayList;
import java.util.Stack;

/**
 * This linked list allows the nodes to be quickly removed no matter where they
 * are in the list. CAVEAT: A node can only be in one linked list of this type
 * at a time. Nodes that are equal using equals() will not be treated the same
 * in this list.
 */
public class LinkedList<E extends LinkedList.Entry> {
	private E head, tail;

	private int size = 0;

	public LinkedList() {
	}

	public void addToStart(E entry) {
		size++;
		if (head == null) {
			tail = head = entry;
			entry.next = entry.previous = null;
		} else {
			entry.next = head;
			entry.previous = null;
			head.previous = entry;
			head = entry;
		}
	}

	/**
	 * @return true if removed, or false if not in list
	 */
	public boolean remove(E entry) {
		if (entry == head) {
			if (head.next == null) // if there is only one element in list
				head = tail = null;
			else
				head = (E) head.next;
			
			if(head != null)
				head.previous = null;
		} else {
			// there should never be a node that has an entry for previous but
			// not next.
			// If both are null this means that the entry was already removed
			// from the list
			if (entry.previous == null)
				return false;

			if (entry == tail) {
				tail = (E) entry.previous;
				tail.next = null;
			} else {
				entry.previous.next = entry.next;
				entry.next.previous = entry.previous;
			}
		}

		entry.next = entry.previous = null;

		size--;
		return true;
	}

	public int size() {
		return size;
	}

	/**
	 * Removes a node from the end
	 * 
	 * @return the node if there were any nodes at all in the list or null if
	 *         not
	 */
	public E removeFromEnd() {
		E n = tail;

		if (remove(n))
			return n;

		return null;
	}

	public E getLast() {
		return tail;
	}

	public static class Entry {
		Entry next;

		Entry previous;

		public Entry() {
		}

		public Entry getNext() {
			return next;
		}

		void setNext(Entry next) {
			this.next = next;
		}

		public Entry getPrevious() {
			return previous;
		}

		void setPrevious(Entry previous) {
			this.previous = previous;
		}

		public boolean isTail() {
			return next == null;
		}

		public boolean isHead() {
			return previous == null;
		}
	}

	private static class Foo extends LinkedList.Entry {
		private int i;

		public Foo(int i) {
			this.i = i;
		}

		public String toString() {
			return "Foo(" + i + ")";
		}
	}

	public static void main(String[] argv) {
		LinkedList<Foo> l = new LinkedList<Foo>();

		ArrayList<Foo> foo = new ArrayList<Foo>();

		Stack<Foo> toDelete = new Stack<Foo>();

		for (int i = 0; i < 1000; i++) {
			if (i % 10 < 8) {
				Foo f = new Foo(i);
				l.addToStart(f);
				foo.add(0, f);

				if (i % 10 == 4 || i % 10 == 6) {
					toDelete.push(f);
				}
			} else {
				Foo f = toDelete.pop();
				l.remove(f);
				foo.remove(f);
			}
		}

		for (int i = 0; i < foo.size(); i++) {
			if (l.head != foo.get(i))
				throw new RuntimeException("crap");
			System.out.println(i + ": " + l.head);
			l.remove(l.head);
		}
	}
	
	public static interface ConstantComparator<T>
	{
		public int compareToConstant(T t);
	}

	/**
	 * Returns the element matching the comparator or closest to it.
	 * The elements must be sorted so that using the comparator,
	 * every element is less then the next.
	 * 
	 * @param beforeIfNotEqual
	 *            if true, then if there are no matching elements to the
	 *            comparator, returns the element immediately before it.
	 * @param startingElement
	 */
	public E getElementMatchingComparator(E startingElement,
			ConstantComparator<E> comparator, boolean beforeIfNotEqual) {
		E e;
		if(startingElement == null)
			e = head; 
		else
			e = startingElement;
		
		if(e == null) return null;
		
		E lastE = null;
		
		boolean isForward = comparator.compareToConstant(e) < 0;

		while (true) {
			if (e == null)
				break;
			int value = comparator.compareToConstant(e);
			
			if (value < 0) {
				if (isForward) {
					lastE = e;
					e = (E) e.getNext();
				} else
					break;
			} else if(value > 0) {
				if (!isForward) {
					lastE = e;
					e = (E) e.getPrevious();
				} else
					break;
			}
			else //if we exactly equal the comparator
			{
				return e;
			}
		}

		// we want the element immediately before the micros,
		// so if we are marching forwards, we take the last one that is before
		if (isForward && beforeIfNotEqual || !isForward && !beforeIfNotEqual)
			e = lastE;

		if (e == null)
			return null;

		return e;
	}

	public void clear() {
		while (head != null) {
			E e = head;
			head = (E) head.next;
			e.previous = e.next = null;
		}

		tail = null;
		size = 0;
	}

	public void insertAt(E insertPoint, E newItem) {
		size++;
		E prevItem = (E) insertPoint.getPrevious();
		if(prevItem != null)
		{
			prevItem.next = newItem;
		}
		else
			head = newItem;
		newItem.previous = prevItem;
		newItem.next = insertPoint;
		insertPoint.previous = newItem;
		
	}

	public void addToEnd(E entry) {
		size++;
		if (tail == null) {
			tail = head = entry;
			entry.next = entry.previous = null;
		} else {
			entry.previous = tail;
			entry.next = null;
			tail.next = entry;
			tail = entry;
		}
	}

	public void swapWithPrevious(E next) {
		swapCouple((E) next.previous, next);
	}

	public void swapWithNext(E prev) {
		swapCouple(prev, (E)prev.next); 
	}
	
	private void swapCouple(E prev, E next)
	{
		if(prev.previous != null)
			prev.previous.next = next;
		else
			head = next;

		if(next.next != null)
			next.next.previous = prev;
		else
			tail = prev;
		
		next.previous = prev.previous;
		prev.next = next.next;
		next.next = prev;
		prev.previous = next;
	}

	public E getFirst() {
		return head;
	}

}
