import Node;
import rebelsky.util.NewComparable;
import rebelsky.util.EmptyListException;
import rebelsky.util.IncomparableException;

/**
 * An implementation of lists using nodes and making the use of
 * nodes explicit.  Should not be taken as a good list implementation.
 * Written for a lab in CS152.  The code is based, in part, on
 * code from Chapter 7 of "Designing Pascal Solutions: Case Studies
 * with Data Structures" by Michael J. Clancy and Marcia C. Linn.
 * Computer Science Press (W.H. Freeman), 1996.
 *
 * @author Samuel A. Rebelsky
 * @version 1.0 of March 1998
 */
public class NodeList
{
  // +------------+--------------------------------------------------------
  // | Attributes |
  // +------------+

  /**
   * The front of the list (the first element in the list).  
   * Set to null when the list is empty.
   */
  Node front;

  /**
   * The end of the list.  Set to null when the list is empty.
   */
  Node back;

  /**
   * The number of elements in the list.
   */
  int length;


  // +--------------+------------------------------------------------------
  // | Constructors |
  // +--------------+

  /**
   * Create a new empty list.
   */
  public NodeList()
  {
    front = null;
    back = null;
    length = 0;
  } // NodeList()

  // +------------------+--------------------------------------------------
  // | Standard Methods |
  // +------------------+

  /**
   * Provide a printable version of the current list.
   */
  public String toString()
  {
    // Special case: empty list
    if (empty()) {
      return "";
    }
    // Normal case: nonempty list
    else {
      String str = "";	// The stringified version of the list
      Node tmp = front;	// To step through the elements of the list
      // Step through the list, adding elements separated by commas.
      // Stop when you reach the last nonempty element of the list.
      while (tmp.getNext() != null) {
        str = str + tmp.getValue().toString() + ",";
        tmp = tmp.getNext();
      } // while
      // Add the last element of the list
      str = str + tmp.getValue().toString();
      // That's it
      return str;
    } // Normal case
  } // toString()


  // +-----------+---------------------------------------------------------
  // | Accessors |
  // +-----------+

  /**
   * Determine if the list is empty.
   */
  public boolean empty()
  {
    return (front == null);
  } // empty()

  /**
   * Get the first element in the list.
   * pre: The list is nonempty.
   * post: The first element is returned.
   * post: The original list is not modified.
   */
  public Object first()
    throws EmptyListException
  {
    // Make sure the list is nonempty.
    if (empty()) {
      throw new EmptyListException();
    }
    return front.getValue();
  } // first()

  /**
   * Get the last element in the list.
   * pre: The list is nonempty.
   * post: The last element is returned.
   * post: The original list is not modified.
   */
  public Object last()
    throws EmptyListException
  {
    // Make sure the list is nonempty.
    if (empty()) {
      throw new EmptyListException();
    }
    return back.getValue();
  } // last()

  /**
   * Get the length of the current list.
   */
  public int length()
  {
    return length;
  } // length()

  /**
   * Build and return a sorted version of the current list.
   * post: The list is not modified.
   * post: A sorted version of the list is returned.
   *
   * @exception IncomparableException
   *   When two elements of the list cannot be compared
   */
  public NodeList quickSort()
    throws IncomparableException
  {
    // The final sorted list
    NodeList sorted = new NodeList();
    // The pivot.  Set to null so that Java doesn't complain that it's
    // unitialized.
    NewComparable pivot = null;
    // The smaller elements of the current list
    NodeList smaller = new NodeList();
    // And a sorted version of that list
    NodeList sorted_smaller;
    // The bigger elements of the list
    NodeList bigger = new NodeList();
    // And a sorted version of that list
    NodeList sorted_bigger;
    // The elements equal to the pivot
    NodeList pivots = new NodeList();

    // Special case: empty list
    if (empty()) {
      return sorted;	// It's empty, it must be sorted.
    }
    // Normal case: split, recurse, and join
    else {
      // Note that picking a pivot can throw an exception if the list
      // is empty.  However, we've just verified that the list is nonempty
      // so we can successfully ignore the exception (I hope).
      try {
        pivot = pickPivot();
      }
      catch (EmptyListException e) {
        // It'll never happen
        pivot = null;
      }
      partition(pivot, smaller, pivots, bigger);
      sorted_smaller = smaller.quickSort();
      sorted_bigger = bigger.quickSort();
      sorted.appendList(sorted_smaller);
      sorted.appendList(pivots);
      sorted.appendList(sorted_bigger);
      return sorted;
    } // normal case
  } // quickSort()


  // +-----------+---------------------------------------------------------
  // | Modifiers |
  // +-----------+

  /**
   * Add a node to the end of the list.  If the list is empty,
   * make a list consisting of a single node.
   */
  public void addToEnd(Node n)
  {
    // Handle the empty list.  Set the front and the back to null.
    if (front == null) {
      front = n;
      back = n;
      length = 1;
    } // Empty list
    // Normal list.  Simply update the back of the list.
    else {
      // Update the next reference of the old last element
      back.setNext(n);
      // Update our view of the last element
      back = back.getNext();
      length = length + 1;
    } // Normal list
  } // addToEnd(Node)

  /**
   * Add another list to the current list.  If the current list
   * is empty, simply use that other list.
   * post: this = (old version of this) + other
   */
  public void appendList(NodeList other)
  {
    // If the current list is empty, simply copy the appropriate
    // fields of the other list.
    if (empty()) {
      this.front = other.front;
      this.back = other.back;
      this.length = other.length;
    } // current list is empty

    // Otherwise (the current list is not empty), so we need to
    // update the back half of the current list.
    else {
      // The next thing after the end of the old list is the first
      // element of the newly appended list.
      this.back.setNext(other.front);
      // The end of the joined list is the end of the appended list.
      this.back = other.back;
      // The length of the joined list is the sum of the lengths of the
      // two lists.
      this.length = this.length + other.length;
    } // current list is not empty
  } // appendList(NodeList)

  /**
   * Clear the current list.
   * post: length = 0
   * post: there are not elements in the current list
   */
  public void clear()
  {
    this.front = null;
    this.back = null;
    this.length = 0;
  } // clear()
  
  /**
   * Delete the first element of the vector.
   */
  public Object deleteFirst()
    throws EmptyListException
  {
    // Special case: Empty list
    if (empty()) {
      throw new EmptyListException();
    }
    // Normal case: Nonempty list
    else {
      // Remember the current front
      Object oldfront = front.getValue();
      // Advance
      front = front.getNext();
      // Update length
      length = length - 1;
      // Return the old front
      return oldfront;
    } // normal case
  } // deleteFirst()


  // +---------------+-----------------------------------------------------
  // | Local Methods |
  // +---------------+

  /**
   * Partition the current list into three sublists.
   */
  protected void partition(NewComparable pivot, 
                           NodeList smaller, 
                           NodeList equals, 
                           NodeList bigger)
    throws IncomparableException
  {
    // A "counter" to step through the list.
    Node current = front;
    // Step through the list, putting each element in the appropriate
    // sublist.
    while (current != null) {
      // Is it equal to the pivot?
      if (pivot.equals(current.getValue())) {
        equals.addToEnd(current);
      }
      // Does it follow the pivot?
      else if (pivot.lessThan(current.getValue())) {
        bigger.addToEnd(current);
      }
      // Otherwise, it must precede the pivot
      else {
        smaller.addToEnd(current);
      }
      // Move on to the next element
      current = current.getNext();
    } // while
  } // partitition

  /**
   * Pick a pivot from the list.
   */
  protected NewComparable pickPivot()
     throws EmptyListException, IncomparableException
  {
    // Special case: empty list.  Crash and burn.
    if (front == null) {
      throw new EmptyListException();
    } // empty list
    // Normal case: nonempty list.  Use the last element.
    else {
      try {
        return (NewComparable) back.getValue();
      }
      catch (ClassCastException cce) {
        throw new IncomparableException();
      }
    } // normal case
  } // pickPivot

} // NodeList

