/*
 * SimpleLinkedList.java
 *
 * Version:
 *    $Id: SimpleLinkedList.java,v 1.2 2001/11/28 14:39:16 ptt Exp $
 *
 * Revisions:
 *    $Log: SimpleLinkedList.java,v $
 *    Revision 1.2  2001/11/28 14:39:16  ptt
 *    Finished and tested implementation.
 *
 *    Revision 1.1  2001/11/27 18:36:28  ptt
 *    INITIAL COMMIT
 *
 */

import java.util.Iterator;

/**
 * A linked implementation of a simple list.
 *
 * @author Paul Tymann
 */

public class SimpleLinkedList implements SimpleList {

    // The nodes that make up the list

    private static class ListNode {
	private Object data;    // The data in this node
	private ListNode next;  // The next node in the list

	/**
	 * Create a node that contains the specified data and that has
	 * no successor.
	 *
	 * @param d the data to be placed in this node.
	 */

	public ListNode( Object d ) {
	    this( d, null );
	}

	/**
	 * Create a node that contains the specified data and successor.
	 *
	 * @param d the data to hold in this node.
	 * @param n the next node in the list.
	 */
	
	public ListNode( Object d, ListNode n ) {
	    data = d;
	    next = n;
	}

	/**
	 * Return the link to the next node.
	 *
	 * @return the link to the next node.
	 */
	
	public ListNode getNext() {
	    return next;
	}

	/**
	 * Return the data stored in this node.
	 *
	 * @return the data stored in this node.
	 */
	
	public Object getData() {
	    return data;
	}

	/**
	 * Set the link to the next node.
	 *
	 * @param n the next node.
	 */
	
	public void setNext( ListNode n ) {
	    next = n;
	}

	/**
	 * Set the data stored in this node.
	 *
	 * @param d the new data in this node.
	 */

	public void setData( Object d ) {
	    this.data = d;
	}
	
    } // ListNode

    // Iterator capable of traversing the list

    private class LinkedListIterator implements Iterator {
	private ListNode cur = head;  // Current position in the list

	/**
	 * Return true if there are more elements in the iteration.
	 *
	 * @return true if there are more elements in the iteration.
	 */

	public boolean hasNext() {
	    return cur != null;
	}

	/**
	 * Return the next element in the iteration.
	 *
	 * @return the next element in the iteration.
	 */

	public Object next() {
	    Object retVal = cur.getData();
	    cur = cur.getNext();

	    return retVal;
	}

	/**
	 * Unsupported operation but required to implement the Iterator
	 * interface.
	 */

	public void remove() {
	    throw new UnsupportedOperationException();
	}

    } // LinkedListIterator

    // Instance Variables

    private ListNode head;  // the node at the head of the list
    private ListNode tail;  // the node at the end of the list
    private int length;     // the number of elements in the list

    /**
     * Create a new linked list.
     */

    public SimpleLinkedList() {
	head = tail = null;
	length = 0;
    }

    /**
     * Add a new element to the end of the list.
     *
     * @param data the element to add to the list.
     */

    public void add( Object data ) {
	ListNode newNode = new ListNode( data );

	if ( isEmpty() ) {
	    head = newNode;
	}
	else {
	    tail.setNext( newNode );
	}

	tail = newNode;
	length = length + 1;
    }

    /**
     * Inserts the specified element at the specified position in the list.
     * The elements in the list will be shifted to the right to make room
     * for the new element.
     *
     * @param pos position at which element is to be inserted.
     * @param data the element to add to the list.
     *
     * @exception IndexOutOfBoundsException thrown if pos is invalid.
     */

    public void add( int pos, Object data ) throws IndexOutOfBoundsException {
	ListNode before = null;
	ListNode after = head;
	ListNode newNode = null;

	// Make sure the index is valid

	if ( pos < 0 || pos > size() ) {
	    throw new IndexOutOfBoundsException();
	}

	// Step to the place where the new element is to be placed

	while ( pos > 0 ) {
	    before = after;
	    after = after.getNext();
	    pos = pos - 1;
	}

	// Insert the new element

	newNode = new ListNode( data, after );

	// Is this a new head?

	if ( before == null ) {
	    head = newNode;
	} else {
	    before.setNext( newNode );
	}

	// Is it a new tail?

	if ( after == null ) {
	    tail = newNode;
	}

	length = length + 1;
    }

    /**
     * Determine if the specified element is in the list.
     *
     * @param target the element to look for.
     *
     * @returns true if the element is in the list and false otherwise.
     */

    public boolean contains( Object target ) {
	return indexOf( target ) != -1;
    }

    /**
     * Returns the index of the first occurence of the specified element
     * in the list or -1 if the element is not in the list.
     *
     * @param target the element to look for.
     *
     * @returns the index of the specified element or -1 if the element
     *          is not in the list.
     */

    public int indexOf( Object target ) {
	int retVal = 0;
	Object cur = null;

	for ( Iterator i = iterator(); 
	      i.hasNext() && !i.next().equals( target ); 
	      retVal = retVal + 1 );

	if ( retVal == size() ) {
	    retVal = -1;
	}

	return retVal;
    }  

    /**
     * Returns the element at the specified position in the list.
     *
     * @param pos the position of the element to return.
     *
     * @return the specified element in the list.
     *
     * @exception IndexOutOfBoundsException if pos is invalid.
     */

    public Object get( int pos ) throws IndexOutOfBoundsException {
	ListNode cur = head;

	if ( pos < 0 || pos >= size() ) {
	    throw new IndexOutOfBoundsException();
	}

	// Find the element

	while ( pos > 0 ) {
	    cur = cur.getNext();
	    pos = pos - 1;
	}

	// Return it

	return cur.getData();
    }

    /**
     * Remove the element at the specified position from the list.
     *
     * @param pos position of the element to remove
     *
     * @exception IndexOutOfBoundsException if pos is invalid.
     */

    public void remove( int pos ) throws IndexOutOfBoundsException {
	ListNode target = head;
	ListNode prev = null;

	// Make sure the index is valid

	if ( pos < 0 || pos >= size() ) {
	    throw new IndexOutOfBoundsException();
	}

	// Find the node that contains the element we wish to
	// delete and the node immediately before it.

	while ( pos > 0 ) {
	    prev = target;
	    target = target.getNext();
	    pos = pos - 1;
	}

	// Target is now pointing to the node we wish to
	// delete
	
	if ( prev == null ) {
	    // We are deleting the head

	    head = target.getNext();
	}
	else {
	    // We are somewhere in the middle of the list

	    prev.setNext( target.getNext() );
	}

	// Did we just delete the tail?

	if ( target == tail ) {
	    tail = prev;
	}

	length--;
    }

    /**
     * Remove the first occurence of the specified element from the
     * list.
     *
     * @param target the element to remove.
     */

    public void remove( Object target ) {
	int pos = indexOf( target );

	if ( pos != -1 ) {
	    remove( pos );
	}
    }

    /**
     * Remove all of the elements from the list.
     */

    public void clear() {
	head = tail = null;
	length = 0;
    }


    /**
     * Returns an iterator over the elements in the list.
     *
     * @returns an iterator over the elements in the list.
     */

    public Iterator iterator() {
	return new LinkedListIterator();
    }

    /**
     * Return the number of elements currently in the list.
     *
     * @return the number of elements in the list.
     */

    public int size() {
	return length;
    }

    /**
     * Return true if the list is empty.
     *
     * @return true if the list is empty.
     */

    public boolean isEmpty() {
	return size() == 0;
    }

    /**
     * Determine if another object is equal to this object.  Two lists
     * are equal if the contain the same number of elements in the
     * same order.
     *
     * @param the object to be compared.
     *
     * @return true if the lists are equal and false otherwise.
     */

    public boolean equals( Object o ) {
	boolean retVal = false;
	SimpleList other = null;

	if ( o instanceof SimpleList ) {
	    other = (SimpleList)o;

	    retVal = size() == other.size();

	    for ( Iterator l1 = iterator(), l2 = other.iterator();
		  l1.hasNext() && retVal; ) {
		
		retVal = retVal && l1.next().equals( l2.next() );
	    }
	}

	return retVal;
    }

    /**
     * Return a string representation of this list.
     *
     * @return A string representation of this list.
     */

    public String toString() {
	StringBuffer retVal = new StringBuffer( "[" );
	int len = 0;

	// Assemble the string

	for ( Iterator i = iterator(); i.hasNext(); ) {
	    retVal.append( i.next() );
	    retVal.append( "," );
	}

	// Get rid of the trailing ','

	if ( ( len = retVal.length() ) != 1 ) {
	    retVal.deleteCharAt( retVal.length() - 1 );
	}

	// Close the string and return

	retVal.append( "]" );

	return retVal.toString();
    }

} // SimpleLinkedList

