/**
   * Add an element to the list at the specified index
   *
   * @param The index where the element should be added
   * @param element The element to add
   */
  public void add(int index, E element) {
    // TODO: Implement this method
    /*if (index < 0 || index > size - 1) { throw new IndexOutOfBoundsException(); }*/

    if (element == null) {
      throw new NullPointerException();
    }

    LLNode<E> newNode = new LLNode<E>(element);

    LLNode<E> currentNode = this.head.next;

    while (index > 0 && currentNode.next != null) {
      currentNode = currentNode.next;
      index--;
    }

    // Re-assign pointers so currentNode -> newNode ->  newNode.next
    newNode.next = currentNode;
    currentNode.prev.next = newNode;
    newNode.prev = currentNode.prev;
    currentNode.prev = newNode;

    // Increment list size
    this.size++;
  }
  /**
   * Add an element to the list at the specified index
   *
   * @param The index where the element should be added
   * @param element The element to add
   */
  public void add(int index, E element) {
    // TODO: Implement this method
    if (element == null) throw new NullPointerException();

    LLNode<E> elemToAdd = new LLNode<E>(element);
    if ((index > this.size) || (index < 0)) {
      throw new IndexOutOfBoundsException();
    } else {

      int count = 0;
      LLNode<E> cur = this.head;

      while (count <= index) {
        cur = cur.next;
        count++;
      }

      LLNode<E> prevNode = cur.prev;
      prevNode.next = elemToAdd;
      cur.prev = elemToAdd;
      elemToAdd.next = cur;
      elemToAdd.prev = prevNode;
      this.size++;
    }
  }
 /**
  * Add an element to the list at the specified index
  *
  * @param index where the element should be added
  * @param element The element to add
  */
 public void addback(int index, E element) {
   if (index < 0 || index > size) {
     throw new IndexOutOfBoundsException("Index out of bounds: " + index);
   } else if (element == null) {
     throw new NullPointerException("Null elements not supported by list.");
   } else if (index == 0) {
     final LLNode<E> node = new LLNode<>(element);
     node.next = head;
     node.prev = node;
     head = node;
     size++;
   } else if (index == size) {
     final LLNode<E> node = new LLNode<>(element);
     tail.next = node;
     node.prev = tail;
     tail = node;
     size++;
   } else {
     final LLNode<E> node = new LLNode<>(element);
     LLNode<E> nextNode = new LLNode<>(null);
     nextNode = head;
     while (index > 0) {
       nextNode = nextNode.next;
       index--;
     }
     nextNode.prev.next = node;
     node.prev = nextNode.prev;
     node.next = nextNode;
     nextNode.prev = node;
     size++;
   }
 }
 /** Create a new empty LinkedList */
 public MyLinkedList() {
   // TODO: Implement this method
   this.size = 0;
   this.head = new LLNode<E>(null);
   this.tail = new LLNode<E>(null);
   head.next = tail;
   tail.prev = head;
 }
  public void add(int index, E element) {
    if (index < 0 || index > size) {
      throw new IndexOutOfBoundsException("Index out of bounds: " + index);
    } else if (element == null) {
      throw new NullPointerException("Null elements not supported by list.");
    } else if (index == 0) {
      final LLNode<E> node = new LLNode<>(element);
      node.setNext(head);
      head.setPrev(node);
      head = node;
      size++;
    } else if (index == size) {
      final LLNode<E> node = new LLNode<>(element);
      tail.setNext(node);
      node.setPrev(tail);
      tail = node;
      size++;
    } else {
      final LLNode<E> node = new LLNode<>(element);
      LLNode<E> nextNode = new LLNode<>(null);
      nextNode = head;
      while (index > 0) {
        nextNode = nextNode.next;
        index--;
      }
      nextNode.prev.next = node;
      node.prev = nextNode.prev;
      node.next = nextNode;
      nextNode.prev = node;
      size++;
    }

    //    This method adds an element at the specified index.
    // If elements exist at that index, you will move elements at that
    // index (and beyond) up, effectively inserting this element at that location in the list.
    // Although drawings are helpful for implementing any method, you will benefit heavily from
    // drawing out what should happen in this method before trying to code it.
    // You will want to throw an IndexOutOfBoundsException if the index provided is not reasonable
    // for
    // inserting an element.
    // Optional: After authoring this version of the add method, you could consider removing
    // redundant
    // code by having the add method you originally wrote just call this method.
    // To test this method, use the method in MyLinkedListTester called testAddAtIndex.
  }
 /**
  * Add an element to the list at the specified index
  *
  * @param The index where the element should be added
  * @param element The element to add
  */
 public void add(int index, E element) {
   // TODO: Implement this method
   if (element == null) throw new NullPointerException();
   if (index < 0 || index > size) throw new IndexOutOfBoundsException();
   LLNode<E> indexNode = getNode(index);
   LLNode<E> newNode = new LLNode<E>(element);
   newNode.prev = indexNode.prev;
   newNode.next = indexNode;
   newNode.next.prev = newNode;
   newNode.prev.next = newNode;
   size++;
 }
 /**
  * Appends an element to the end of the list
  *
  * @param element The element to add
  */
 public boolean add(E element) {
   if (element == null) {
     throw new NullPointerException("Null elements not supported by list.");
   } else {
     final LLNode<E> node = new LLNode<>(element);
     if (tail.data == null) {
       head = tail = node;
     } else {
       tail.next = node;
       node.prev = tail;
       tail = node;
     }
     size++;
     return true;
   }
 }
  /**
   * Appends an element to the end of the list
   *
   * @param element The element to add
   */
  public boolean add(E element) {
    // TODO: Implement this method
    // Instantiate new node
    LLNode<E> newNode = new LLNode<E>(element);

    LLNode<E> currentNode = this.tail.prev;

    // Re-assign pointers for currentNode and tail to newNode
    newNode.next = this.tail;
    newNode.prev = currentNode;
    this.tail.prev = newNode;
    currentNode.next = newNode;

    // Increment size
    this.size++;

    return true;
  }
  /**
   * Remove a node at the specified index and return its data element.
   *
   * @param index The index of the element to remove
   * @return The data element removed
   * @throws IndexOutOfBoundsException If index is outside the bounds of the list
   */
  public E remove(int index) {
    // TODO: Implement this method
    if ((index >= this.size) || (index < 0)) {
      throw new IndexOutOfBoundsException();
    } else {
      int count = 0;
      LLNode<E> cur = this.head;

      while (count <= index) {
        cur = cur.next;
        count++;
      }

      LLNode<E> prevNode = cur.prev;
      LLNode<E> nextNode = cur.next;
      prevNode.next = nextNode;
      nextNode.prev = prevNode;
      this.size--;
      return cur.data;
    }
  }