package rpi.goldsd.container;

import java.util.Enumeration;

/**
 * The <tt>LinkedList</tt> class provides a generic linked list container
 * by managing a doubly linked list of <tt>ListNode</tt> objects.
 *
 * @see ListNode
 * @see Comparable
 * @version 1.3, 4/16/98
 * @author David Goldschmidt
 */
public class LinkedList
{
  /**
   * The default constructor simply sets the protected data elements of
   * this linked-list to values indicating an empty list.
   * <p>
   * This method performs in constant time <b>O(1)</b>.
   */
  public LinkedList()
  {
    this.head = this.tail = null;
    this.numElements = 0;
  }


  /**
   * Adds a new <tt>ListNode</tt> object to this linked-list after the
   * given <tt>ListNode</tt> object <tt>existingNode</tt>.
   * <p>
   * This method performs in constant time <b>O(1)</b>; however,
   * this method assumes that the <tt>existingNode</tt> does indeed
   * exist in this linked-list.
   * @param existingNode the element after which <tt>newNode</tt> will
   *                     be added to this linked-list.
   * @param newNode the element to be added to this linked-list.
   */
  public synchronized void addAfter( ListNode existingNode,
                                     ListNode newNode )
  {
    if ( tail == existingNode )
      addToTail( newNode );
    else
    {
      newNode.setNext( existingNode.getNext() );
      existingNode.setNext( newNode );
      newNode.setPrevious( existingNode );
      if ( newNode.getNext() != null )
        newNode.getNext().setPrevious( newNode );
      numElements++;
    }
  }


  /**
   * Adds a new <tt>ListNode</tt> object to this linked-list at the
   * beginning of the list.
   * <p>
   * This method performs in constant time <b>O(1)</b>.
   * @param newNode the element to be added to the beginning of this
   *                linked-list.
   */
  public synchronized void addToHead( ListNode newNode )
  {
    if ( head != null ) head.setPrevious( newNode );
    newNode.setNext( head );
    newNode.setPrevious( null );
    head = newNode;
    if ( numElements++ == 0 ) tail = head;
  }


  /**
   * Adds a new <tt>ListNode</tt> object to this linked-list at the end of
   * the list.
   * <p>
   * This method performs in constant time <b>O(1)</b>.
   * @param newNode the element to be added to the end of this linked-list.
   */
  public synchronized void addToTail( ListNode newNode )
  {
    if ( numElements++ == 0 )
      head = newNode;
    else
      tail.setNext( newNode );

    newNode.setPrevious( tail );
    tail = newNode;
    newNode.setNext( null );
  }


  /**
   * Tests whether the given <tt>Comparable</tt> object exists in this
   * linked-list by using the <tt>isEqualTo()</tt> method.
   * <p>
   * This method performs in linear time <b>O(n)</b>.
   * @param node the <tt>ListNode</tt> or <tt>Comparable</tt> object to
   *             be found in this linked-list.
   * @return <tt>true</tt> if <tt>node</tt> is in this linked-list;
   *         <tt>false</tt> otherwise.
   */
  public synchronized boolean contains( Comparable node )
  {
    return ( find( node ) != null );
  }


  /**
   * Tests whether the given <tt>ListNode</tt> object exists in this
   * linked-list by comparing the references of the objects
   * (using the <tt>==</tt> operator).
   * <p>
   * This method performs in linear time <b>O(n)</b>.
   * @param node the <tt>ListNode</tt> object to be found in this linked-list.
   * @return <tt>true</tt> if <tt>node</tt> is in this linked-list;
   *         <tt>false</tt> otherwise.
   */
  public synchronized boolean containsReferenceTo( ListNode node )
  {
    Enumeration e = elements();

    while ( e.hasMoreElements() )
      if ( ((ListNode)e.nextElement()) == node )
        return true;

    return false;
  }


  /**
   * Provides an enumeration or traversal of this linked-list
   * starting at the beginning of the list.  The <tt>nextElement()</tt>
   * method of the instantiated <tt>Enumeration</tt> object returns the
   * next <tt>ListNode</tt> object in this list.
   * <p>
   * This method performs in constant time <b>O(1)</b>; however, note that
   * an algorithm making use of the resulting <tt>Enumeration</tt> will
   * likely be linear <b>O(n)</b>.
   * @return an <tt>Enumeration</tt> object representing a traversal of
   *         this linked-list from head to tail.
   */
  public synchronized Enumeration elements()
  {
    return ( new Enumeration() {
      ListNode current = head;

      public boolean hasMoreElements()  { return ( current != null ); }

      public Object nextElement()
      {
        if ( ! hasMoreElements() )
          return null;

        ListNode result = current;
        current = (ListNode)current.getNext();
        return result;
      }
    } );
  }


  /**
   * Traverses this linked-list to find the given <tt>Comparable</tt>
   * object by using the <tt>isEqualTo()</tt> method.
   * <p>
   * This method performs in linear time <b>O(n)</b>.
   * @param node the <tt>ListNode</tt> or <tt>Comparable</tt> object to be
   *             found in this linked-list.
   * @return a reference to the <tt>ListNode</tt> that was found; or
   *         <tt>null</tt> if not found.
   */
  public ListNode find( Comparable node )
  {
    Enumeration e = elements();

    while ( e.hasMoreElements() )
    {
      ListNode N = (ListNode)e.nextElement();
      if ( N.isEqualTo( node ) )
        return N;
    }

    return null;
  }


  /**
   * Returns the number of elements in this linked-list.
   * <p>
   * This method performs in constant time <b>O(1)</b>.
   * @return the number of elements in this linked-list.
   */
  public synchronized int getSize()  { return numElements; }


  /**
   * Returns <tt>true</tt> if this linked-list contains no elements.
   * <p>
   * This method performs in constant time <b>O(1)</b>.
   * @return <tt>true</tt> if this linked-list contains no elements;
   *         <tt>false</tt> otherwise.
   */
  public synchronized boolean isEmpty()  { return ( head == null ); }


  /**
   * Removes and returns a reference to the <i>first</i> occurrence
   * of the given <tt>ListNode</tt> or <tt>Comparable</tt> object.
   * To find the given object in this linked-list, the <tt>isEqualTo()</tt>
   * method is applied to the given argument and each node of this linked-list.
   * Note that the reference that is returned refers to the <tt>ListNode</tt>
   * object that is removed (or <tt>null</tt> if not found).
   * <p>
   * This method performs in linear time <b>O(n)</b>.
   * @param node the <tt>ListNode</tt> or <tt>Comparable</tt> object to be
   *             removed from this linked-list.
   * @return the removed <tt>ListNode</tt> object; or <tt>null</tt> if no
   *         object is removed from this linked-list.
   */
  public synchronized ListNode remove( Comparable node )
  {
    if ( numElements == 0 ) return null;

    ListNode result = null;

    if ( head.isEqualTo( node ) )
    {
      result = head;
      head = (ListNode)head.getNext();
      if ( head != null ) head.setPrevious( null );
    }
    else
    {
      boolean found = false;
      Enumeration e = elements();

      while ( ! found && e.hasMoreElements() )
      {
        result = (ListNode)e.nextElement();
        if ( result.isEqualTo( node ) )
          found = true;
      }

      if ( ! found ) return null;

      if ( result.getPrevious() != null )
        result.getPrevious().setNext( result.getNext() );

      if ( result.getNext() != null )
        result.getNext().setPrevious( result.getPrevious() );

      if ( result == tail ) tail = (ListNode)result.getPrevious();
    }

    if ( --numElements == 0 ) head = tail = null;
    result.setNext( null );
    result.setPrevious( null );
    return result;
  }


  /**
   * Removes and returns a reference to the <tt>ListNode</tt> object that
   * is currently the head of this list.
   * <p>
   * This method performs in constant time <b>O(1)</b>.
   * @return the removed <tt>ListNode</tt> object; or <tt>null</tt> if the
   *         list contains no elements.
   */
  public synchronized ListNode removeFromHead()
  {
    if ( numElements == 0 ) return null;

    ListNode result = head;
    head = (ListNode)head.getNext();
    if ( head != null ) head.setPrevious( null );
    if ( --numElements == 0 ) head = tail = null;
    result.setNext( null );
    result.setPrevious( null );
    return result;
  }


  /**
   * Removes and returns a reference to the <tt>ListNode</tt> object that
   * is currently the tail of this list.
   * <p>
   * This method performs in constant time <b>O(1)</b>.
   * @return the removed <tt>ListNode</tt> object; or <tt>null</tt> if the
   *         list contains no elements.
   */
  public synchronized ListNode removeFromTail()
  {
    if ( numElements == 0 ) return null;

    ListNode result = tail;
    tail = (ListNode)tail.getPrevious();
    if ( tail != null ) tail.setNext( null );
    if ( --numElements == 0 ) head = tail = null;
    result.setNext( null );
    result.setPrevious( null );
    return result;
  }


  /**
   * The <tt>toString()</tt> is the default method used to display this
   * linked-list in the form of a <tt>String</tt> object.
   * @return a <tt>String</tt> representing this linked-list.
   */
  public synchronized String toString()
  {
    if ( numElements == 0 ) return "<empty>";

    String s = "";
    Enumeration e = elements();

    while ( e.hasMoreElements() )
    {
      ListNode node = (ListNode)e.nextElement();
      s = s + "[" + node + "]";
      if ( node != tail ) s = s + "<-->";
    }

    return s;
  }


  /** A reference to the first element of this linked-list. */
  protected ListNode head;


  /** The number of elements in this linked-list. */
  protected int numElements;


  /** A reference to the last element of this linked-list. */
  protected ListNode tail;
}

