/* a generic queue implemented using a linked list of nodes
 * @author	Biagioni, Edoardo
 * @assignment	lecture 14
 * @date	March 3, 2008
 * @inspiration	William Albritton's LinkedQueue.java,
 * href="http://www2.hawaii.edu/~walbritt/ics211/queueLinked/LinkedQueue.java
 * and LinkedList.java
 */

public class LinkedQueue<T> {
    /* only need to store a single pointer to the node at the back
     * of the queue.
     * The pointer is null if the queue is empty.
     * Also record the size of the queue.
     * Values are initialized correctly for an empty queue, so
     * no constructor is needed.
     */
    protected Node<T> back = null;
    /* invariant: size is the number of nodes in the queue pointed to by back*/
    protected int size = 0;

    /* accessor method */
    public int size() {
	return size;
    }

    /* @param	value to add to the end of the list
     * @return	true, this method always succeeds
     */
    public boolean offer(T value) {
	if (back == null) { 	// special case
	    back = new Node<T>(value, null);
	    back.setNext(back);	// queue is a circular list
	} else {
	    // new node points to the first node in the queue
	    Node<T> newNode = new Node<T>(value, back.getNext());
	    // the old back of the queue points to the new node
	    back.setNext(newNode);
	    // the new node is now at the back of the queue
	    back = newNode;
	}
	size++;
	return true;
    }

    /* @return	and remove the value at the head of the queue
     */
    public T poll() {
	if (back == null) { 	// special case
	    return null;
	} else {
	    // return the first node in the queue, which in our circular
	    // linked list, is the node after the last node in the queue
	    T result = back.getNext().getValue();
	    if (size == 1) {   // special case: removing last node in queue
		back = null;
	    } else {		// remove the first node from the queue
		back.setNext(back.getNext().getNext());
	    }
	    size--;
	    return result;
	}
    }

    /* convert the queue to a printable string
     * @return	a string representing the queue
     */
    public String toString() {
	if (back == null) {
	    return "" + null;
	} else {
	    return toString(back.getNext() /* first node */ , size);
	}
    }

    /* convert "nodesLeft" nodes to a string beginning with "node"
     * @param	node the start of the list to convert
     * @param	nodesLeft the count of nodes to convert, 0 to end
     * @return	a string representing the node values
     */
    private String toString(Node<T> node, int nodesLeft) {
	if (nodesLeft == 0) {
	    return "";
	} else {
	    return node.getValue() + " : " +
		   toString(node.getNext(), nodesLeft - 1);
	}
    }

    /* unit test -- test all the methods in this class
     * @param	ignored
     */
    public static void main(String[] args) {
	/* create two empty queues, make sure they print out correctly */
	LinkedQueue<String> q1 = new LinkedQueue<String>();
	LinkedQueue<String> q2 = new LinkedQueue<String>();
	System.out.println("1: q1 = '" + q1 + "', q2 = '" + q2 + "'");
	System.out.println("q1.size() = " + q1.size() +
			   ", q2.size() = " + q2.size());

	/* remove an item from an empty queue */
	if ((q1.poll() != null) || (q2.poll() != null)) {
	    System.out.println("error: removed item from empty queue(s)");
	}
	System.out.println("2: q1 = '" + q1 + "', q2 = '" + q2 + "'");

	/* insert some items, keep checking */
	if (! (q1.offer("hello"))) {
	    System.out.println("error inserting hello into q1");
	}
	if (! (q1.offer("world"))) {
	    System.out.println("error inserting world into q1");
	}
	if (! (q2.offer("foo"))) {
	    System.out.println("error inserting foo into q2");
	}
	if (! (q2.offer("bar"))) {
	    System.out.println("error inserting bar into q2");
	}
	if (! (q2.offer("baz"))) {
	    System.out.println("error inserting baz into q2");
	}
	System.out.println("3: q1 = '" + q1 + "', q2 = '" + q2 + "'");
	System.out.println("q1.size() = " + q1.size() +
			   ", q2.size() = " + q2.size());

	/* remove some items from the queues */
	if (! (q1.poll().equals("hello"))) {
	    System.out.println("error removing hello from q1");
	}
	if (! (q2.poll().equals("foo"))) {
	    System.out.println("error removing foo from q2");
	}
	if (! (q2.poll().equals("bar"))) {
	    System.out.println("error removing bar from q2");
	}
	if (! (q2.poll().equals("baz"))) {
	    System.out.println("error removing baz from q2");
	}
	System.out.println("4: q1 = '" + q1 + "', q2 = '" + q2 + "'");
	System.out.println("q1.size() = " + q1.size() +
			   ", q2.size() = " + q2.size());
    }
}