// This file is derived from the Cesium code base under Apache 2 license
// See LICENSE.md and https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md

import {DoublyLinkedListNode} from './doubly-linked-list-node';

/**
 * Doubly linked list
 * @private
 */
export class DoublyLinkedList {
  head: DoublyLinkedListNode | null = null;
  tail: DoublyLinkedListNode | null = null;
  _length = 0;

  get length() {
    return this._length;
  }

  /**
   * Adds the item to the end of the list
   * @param {*} [item]
   * @return {DoublyLinkedListNode}
   */
  add(item) {
    const node = new DoublyLinkedListNode(item, this.tail, null);

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

    ++this._length;

    return node;
  }

  /**
   * Removes the given node from the list
   * @param {DoublyLinkedListNode} node
   */
  remove(node) {
    if (!node) {
      return;
    }

    if (node.previous && node.next) {
      node.previous.next = node.next;
      node.next.previous = node.previous;
    } else if (node.previous) {
      // Remove last node
      node.previous.next = null;
      this.tail = node.previous;
    } else if (node.next) {
      // Remove first node
      node.next.previous = null;
      this.head = node.next;
    } else {
      // Remove last node in the linked list
      this.head = null;
      this.tail = null;
    }

    node.next = null;
    node.previous = null;

    --this._length;
  }

  /**
   * Moves nextNode after node
   * @param {DoublyLinkedListNode} node
   * @param {DoublyLinkedListNode} nextNode
   */
  splice(node, nextNode) {
    if (node === nextNode) {
      return;
    }

    // Remove nextNode, then insert after node
    this.remove(nextNode);
    this._insert(node, nextNode);
  }

  _insert(node, nextNode) {
    const oldNodeNext = node.next;
    node.next = nextNode;

    // nextNode is the new tail
    if (this.tail === node) {
      this.tail = nextNode;
    } else {
      oldNodeNext.previous = nextNode;
    }

    nextNode.next = oldNodeNext;
    nextNode.previous = node;

    ++this._length;
  }
}
