/** 
  * A list implemented using a doubly-linked circular list
  * @author         Edo Biagioni
  * @lecture        ICS 211 Feb 1 (or later)
  * @date           January 27, 2011
  */

public class DLinkedList<E> {

  // here, include the DLinkedNode definition


       /** 
         * A node in a doubly-linked list
         * @author         Edo Biagioni
         * @lecture        ICS 211 Feb 1
         * @date           January 27, 2010
         * @bugs           private class: include this code within a larger class
         */
       
       private static class DLinkedNode<E> {
         private E item;
         private DLinkedNode<E> prev;
         private DLinkedNode<E> next;
       
       
       /** 
         * constructor to build a node with no successor
         * @param the value to be stored by this node
         */
         private DLinkedNode(E value) {
           item = value;
           next = null;
           prev = null;
         }
       
       
       /** 
         * constructor to build a node with a specified (perhaps null) successor
         * @param the value to be stored by this node
         * @param the prev field for this node
         * @param the next field for this node
         */
         private DLinkedNode(E value, DLinkedNode<E> prev, DLinkedNode<E> next) {
           item = value;
           this.next = next;
           this.prev = prev;
         }
       }


  // this is the start of the linked list.  If the list is empty, it is null
  protected DLinkedNode<E> head;
  // since this is a circular double-linked list, the tail node
  // can always be found at head.prev, so we don't explicitly store it
  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((size == 0) == (head == null));
    verify(size >= 0);
    if (size == 0) {
      return;     // no more checks
    }
    // 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;
    DLinkedNode<E> node = head;
    do {
      node = node.next;
      measuredSize++;
    } while (node != head);
    verify(measuredSize == size);
  }


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


  /** 
    * 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();
    DLinkedNode<E> newNode = new DLinkedNode<E>(value);
    if (head == null) {
      head = newNode;
      newNode.next = head;
      newNode.prev = head;
    } else {
      DLinkedNode<E> tail = head.prev;
      tail.next = newNode;
      head.prev = newNode;
      newNode.next = head;
      newNode.prev = tail;
    }
    size++;
    checkInvariants();
    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 DLinkedNode<E> nodeAtPosition(int index) {
    verify (index >= 0);
    DLinkedNode<E> result = head;
    while (index > 0) {
      result = result.next;
      index--;
    }
    verify (result != null);
    return result;
  }


  /** 
    * removes a value from the given position in the list
    * @param the position at which to add: 0 to add at the start
    * @throws IndexOutOfBoundsException if the index is less than 0
    *         or greater than or equal to the number of elements in
    *         the linked list
    */
  public E remove(int index) {
    checkInvariants();
    if ((index < 0) || (index >= size)) {
      String badIndex =
        new String ("index " + index + " must be between 0 and " + (size - 1));
      throw new IndexOutOfBoundsException(badIndex);
    }
    // if head is null, by the invariants, either index < 0 or index >= size
    // therefore head cannot be null
    // just to be sure, let the program verify that
    verify (head != null);
    E value = head.item;
    if (size == 1) {
      head = null;   // very simple!!
    } else {
      DLinkedNode<E> node = nodeAtPosition(index);
      value = node.item;             // return the value
      if (index == 0) {              // removing the head node
        head = node.next;            // new head node == old second node
      }
      node.prev.next = node.next;    // get this node out of the list
      node.next.prev = node.prev;
    }
    size--;
    checkInvariants();
    return value;
  }


  /** 
    * concatenates the elements of the linked list, separated by " ==> "
    * @return the string representation of the list
    */
  public String toString() {
    checkInvariants();
    DLinkedNode<E> node = head;
    StringBuffer result = new StringBuffer();
    if (head != null) {
      while (true) {
        result.append (node.item.toString());
        node = node.next;
        if (node == head) {
          break;    // entire list has been traversed
        }
        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) {
    DLinkedList<String> dll = new DLinkedList<String>();
    System.out.println (dll);
    dll.add ("hello");
    System.out.println (dll);
    dll.add ("world");
    System.out.println (dll);
    dll.add ("foo");
    System.out.println (dll);
    dll.add ("bar");
    System.out.println (dll);
    dll.add ("baz");
    System.out.println (dll);
    dll.remove (2);   // remove foo
    System.out.println (dll);
    dll.remove (3);   // remove baz
    System.out.println (dll);
    dll.remove (1);   // remove world
    System.out.println (dll);
    dll.remove (0);   // remove hello
    System.out.println (dll);
    dll.remove (0);   // remove hello
    System.out.println (dll);
  }

}