/**
   * 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++;
  }
 /** 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;
 }
  /** Create a new empty doublyLinkedList */
  public MyLinkedList() {
    this.size = 0;
    this.head = new LLNode<E>(null);
    this.tail = new LLNode<E>(null);

    // Set the initial references
    head.setNext(tail);
    tail.setPrev(head);
  }
 /**
  * Set an index position in the list to a new element
  *
  * @param index The index of the element to change
  * @param element The new element
  * @return The element that was replaced
  * @throws IndexOutOfBoundsException if the index is out of bounds.
  */
 public E set(int index, E element) {
   // TODO: Implement this method
   if (element == null) throw new NullPointerException();
   if (index < 0 || index > size - 1) throw new IndexOutOfBoundsException();
   LLNode<E> u = getNode(index);
   E y = u.data;
   u.data = element;
   return y;
 }
 @Override
 public String toString() {
   String result = "MyLinkedList{";
   LLNode<E> probe = head;
   for (int i = 0; i < this.size; i++) {
     result += probe + " ";
     probe = probe.getNext();
   }
   result += '}';
   return result;
 }
 /**
  * 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++;
 }
 /**
  * Set an index position in the list to a new element
  *
  * @param index The index of the element to change
  * @param element The new element
  * @return The element that was replaced
  * @throws IndexOutOfBoundsException if the index is out of bounds.
  */
 public E set(int index, E element) {
   if (index < 0 || index > (this.size - 1)) {
     throw new IndexOutOfBoundsException("Index out of bounds: " + index);
   } else if (element == null) {
     throw new NullPointerException("Null elements not supported by list.");
   } else {
     LLNode<E> node = head;
     while (index > 0) {
       node = node.next;
       index--;
     }
     node.data = element;
     return element;
   }
 }
 /**
  * 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;
   }
 }
  /**
   * 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++;
    }
  }
  /**
   * 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;
    }
  }
  /**
   * Set an index position in the list to a new element
   *
   * @param index The index of the element to change
   * @param element The new element
   * @return The element that was replaced
   * @throws IndexOutOfBoundsException if the index is out of bounds.
   */
  public E set(int index, E element) {
    // TODO: Implement this method
    if (index > size - 1 || index < 0) {
      throw new IndexOutOfBoundsException();
    }

    if (this.head.next == null) {
      throw new NullPointerException();
    }

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

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

    E oldElement = currentNode.data;
    currentNode.data = element;

    return oldElement;
  }
 /**
  * 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++;
   }
 }
 // Return the previous node based on the referenceNode
 private LLNode<E> getPrev(LLNode<E> referenceNode) {
   if (referenceNode == head) {
     throw new BoundaryViolationException("Reference cannot be equal to head");
   }
   return referenceNode.getPrev();
 }
 // Return the next node based on the referenceNode
 private LLNode<E> getNext(LLNode<E> referenceNode) {
   if (referenceNode == tail) {
     throw new BoundaryViolationException("Reference cannot be equal to tail");
   }
   return referenceNode.getNext();
 }
  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.
  }