/** 
  * A list implemented using a singly-linked list
  * @author         Edo Biagioni
  * @lecture        ICS 211 Jan 27 (or later)
  * @date           January 26, 2011
  */

public class LinkedList<E> {

  // here, include the LinkedNode definition

          /** 
            * A node in a singly-linked list
            * @author         Edo Biagioni
            * @lecture        ICS 211 Jan 27 or later
            * @date           January 26, 2010
            */
          
          private static class LinkedNode<T> {
            private T item;
            private LinkedNode<T> next;
          
          
          /** 
            * constructor to build a node with no successor
            * @param the value to be stored by this node
            */
            private LinkedNode(T value) {
              item = value;
              next = null;
            }
          
          
          /** 
            * constructor to build a node with specified (maybe null) successor
            * @param the value to be stored by this node
            * @param the next field for this node
            */
            private LinkedNode(T value, LinkedNode<T> reference) {
              item = value;
              next = reference;
            }
          }
  // end of the LinkedNode definition


  // this is the start of the linked list.  If the list is empty, it is null
  protected LinkedNode<E> head;
  // this is the end of the linked list.  If the list is empty, it is null
  protected LinkedNode<E> tail;
  protected int size;


  // there are some relationships between the class variables.  This
  // method checks that these relationships always hold.  Any
  // property that always holds is called an invariant.

  // the property may not hold in the middle of a method,
  // so only call this at the beginning or end of a public method.


  /** 
    * checks assertion
    * @throws java.lang.AssertionError if the assertion is not true
    */
  private void verify(boolean mustBeTrue) {
    if (! mustBeTrue) {
      throw new java.lang.AssertionError("assertion error");
    }
  }


  /** 
    * checks class invariants
    * @throws java.lang.AssertionError if the invariant is violated
    */
  private void checkInvariants() {
    // uncomment the next line to skip the checks:
    // return;
    // either head and tail are both null, or neither is null.
    // size is zero if and only if they are null, and otherwise is positive
    verify((head == null) == (tail == null));
    verify((size == 0) == (head == null));
    verify(size >= 0);
    // if the list only has one element, head should be the same as tail
    // (and also if the list has no elements), otherwise they should differ
    verify((head == tail) == (size <= 1));
    // a non-null tail variable should always have a null "next" field
    verify((tail == null) || (tail.next == null));
    // check to make sure size is the same as the length of the list.
    // this code takes O(n), so comment it out if performance is important
    int measuredSize = 0;
    LinkedNode<E> node = head;
    // if visitedLast is null, the list is empty, and tail should also be null
    LinkedNode<E> visitedLast = null;
    while (node != null) {
      visitedLast = node;
      node = node.next;
      measuredSize++;
    }
    verify(measuredSize == size);
    // also make sure "last" really is the last node in the linked list
    verify(visitedLast == tail);
  }


  /** 
    * initializes an empty linked list
    */
  public LinkedList() {
    head = null;
    tail = null;
    size = 0;
    // one of the constructor's jobs is to make sure that the invariants hold.
    checkInvariants();
  }



  // these private (helper) methods simplify implementation of
  // the public "add" methods
  // the helper methods never modify "size", the public methods
  // take care of that, so the invariants probably do not hold at the end of
  // a helper methods

  /** 
    * adds at the head of the list
    * @param the value to be added
    */
  private void addAtFront(E value) {
    head = new LinkedNode<E>(value, head);
    if (tail == null) {
      tail = head;
    }
  }


  /** 
    * adds at the tail of the list.  Assumes (and checks) that tail is not null
    * @param the value to be added
    * @throws RuntimeException
    */
  private void addAtEnd(E value) {
    if (tail == null) {
      throw new RuntimeException ("invalid call to addAtEnd, tail is null");
    }
    LinkedNode<E> newNode = new LinkedNode<E>(value);
    tail.next = newNode;
    tail = newNode;
  }


  /** 
    * adds a value to the list after the given node 
    * @param the node after which the new value is added
    * @param the value to be added
    */
  private void addAfter(LinkedNode<E> reference, E value) {
    LinkedNode<E> newNode = new LinkedNode<E>(value, reference.next);
    reference.next = newNode;
    if (reference == tail) {  // if added at end, update tail value
      tail = newNode;
    }
  }


  /** 
    * adds a value to the end of the list
    * @param the value to be added
    * @return true (the add always succeeds)
    */
  public boolean add(E value) {
    checkInvariants();  // useful for debugging
    if (head != null) {
      addAtEnd (value);
    } else {
      addAtFront (value);
    }
    size++;
    checkInvariants();  // invariants valid at start, are they still valid?
                        // i.e., did this method break the invariants?
    return true;
  }


  /** 
    * returns the node at the requested position, may take time O(n)
    * @param the position of the requested node, 0 for the head node
    * @return the requested node
    * @throws NullPointerException if the index is larger than the linked list
    */
  private LinkedNode<E> nodeAtPosition(int index) {
    verify (index >= 0);
    LinkedNode<E> result = head;
    while (index > 0) {
      result = result.next;
      index--;
    }
    verify (result != null);
    return result;
  }


  /** 
    * adds a value to the list, in the given position
    * @param the position at which to add: 0 to add at the start
    * @param the value to be added
    * @throws IndexOutOfBoundsException if the index is less than 0
    *         or greater than the number of elements in the linked list
    */
  public void add(int index, E value) {
    checkInvariants();
    if ((index < 0) || (index > size)) {
      String badIndex =
        new String ("index " + index + " must be between 0 and " + size);
      throw new IndexOutOfBoundsException(badIndex);
    }
    if (index == 0) {
      addAtFront (value);
    } else {
      addAfter (nodeAtPosition (index - 1), value);
    }
    size++;
    checkInvariants();
  }


  /** 
    * concatenates the elements of the linked list, separated by " ==> "
    * @return the string representation of the list
    */
  public String toString() {
    checkInvariants();
    LinkedNode<E> node = head;
    StringBuffer result = new StringBuffer();
    while (node != null) {
      result.append (node.item.toString());
      node = node.next;
      if (node != null) {
        result.append (" ==> ");
      }
    }
    checkInvariants();   // make sure we didn't break anything
    return result.toString();
  }


  /** 
    * unit test method -- basic testing of the functionality
    * @param required, ignored
    */
  public static void main (String [] arguments) {
    LinkedList<String> ll = new LinkedList<String>();
    System.out.println (ll);
    ll.add ("foo");
    System.out.println (ll);
    ll.add (1, "bar");
    System.out.println (ll);
    ll.add ("baz");
    System.out.println (ll);
    ll.add (0, "hello");
    System.out.println (ll);
    ll.add (1, "world");
    System.out.println (ll);
  }

}