// do not change the node class, you never know how many things it caan break! :)
class Node {
  constructor(data, next) {
    this.data = data;
    this.next = next || null;
  }
}

class LinkedList {
  constructor() {
    this.head = null;
    this.tail = null;
    this.size = 0;
  }

  addAtBeginning(element) {
    this.head = new Node(element, this.head);
    if (!this.tail) {
      this.tail = this.head;
    }
    this.size += 1;
    return this.head;
  }

  addAtEnd(element) {
    const node = new Node(element, null);
    this.size += 1;

    if (!this.head) {
      this.head = node;
      this.tail = node;
      return node;
    }
    this.tail.next = node;
    this.tail = node;
    return node;
  }

  removeFromBeginning() {
    if (!this.head) {
      return null;
    }
    if (this.head.next === null) {
      this.tail = this.head;
    }
    const node = this.head;
    this.head = this.head.next;
    this.size -= 1;
    node.next = null;
    return node;
  }

  removeFromEnd() {
    if (!this.head) {
      return null;
    }
    let address = this.head;

    while (address.next.next) {
      address = address.next;
    }

    this.tail = address;

    const node = this.tail.next;
    this.tail.next = null;
    this.size -= 1;
    return node;
  }

  getFirst() {
    if (!this.head) {
      return null;
    }
    return this.head;
  }

  getLast() {
    if (!this.head) {
      return null;
    }
    return this.tail;
  }

  getAt(index) {
    if (!this.length()) {
      return null;
    }
    let address = this.head;
    let count = index;

    while (count && address.next) {
      address = address.next;
      count -= 1;
    }

    return address;
  }

  addAt(index, element) {
    if (!this.head) {
      return this.addAtBeginning(element);
    }

    if (index >= this.length()) {
      return this.addAtEnd(element);
    }

    let address = this.head;
    let previous = this.head;
    let count = index;

    while (count) {
      previous = address;
      address = address.next;
      count -= 1;
    }

    const node = new Node(element, previous.next);
    previous.next = node;
    this.size += 1;
    return node;
  }

  removeAt(index) {
    if (!this.head) {
      return null;
    }
    if (index === 0) {
      return this.removeFromBeginning();
    }

    if (index >= this.size - 1) {
      return this.removeFromEnd();
    }

    let address = this.head;
    let previous = address;
    let count = index;

    while (count >= 1) {
      previous = address;
      address = address.next;
      count -= 1;
    }
    const node = address;
    previous.next = address.next;
    this.size -= 1;

    node.next = null;
    return node;
  }

  filter(value) {
    if (!this.head) {
      return null;
    }

    if (this.head.data === value) {
      this.head = this.head.next;
      this.size -= 1;
    }

    let { head } = this;
    let previous = null;

    while (head !== null) {
      if (head.data === value) {
        previous.next = head.next;
        this.size -= 1;

        if (head.next === null) {
          this.tail = previous;
        }
      }
      previous = head;
      head = head.next;
    }
    return this.head;
  }

  length() {
    return this.size;
  }

  delete() {
    this.head = null;
    this.tail = this.head;
    this.size = 0;
  }

  traverseList() {
    const arr = [];
    let node = this.head;
    while (node !== null) {
      arr.push(node.data);
      node = node.next;
    }
    return arr;
  }
}

// const ll = new LinkedList();
// ll.addAtBeginning(20);
// ll.addAtBeginning(15);
// ll.addAtBeginning(10);
// ll.addAtBeginning(5);

// console.log(ll.traverseList());

// console.log(ll.removeAt(0));
// console.log(ll.traverseList());

// console.log(ll.removeAt(1));
// console.log(ll.traverseList());

// const list = new LinkedList();
// [1, 2, 3, 5, 3, 6, 7, 10, 3].forEach(el => {
//   list.addAtEnd(el);
// });

// console.log(list.traverseList());
// list.filter(3);
// console.log(list.traverseList());
// console.log(list.tail);

module.exports = { LinkedList, Node };