const Node = require("./node");
/**
 * This class is a Linked List implementation
 */
class LinkedList {
   /**
   * Creates a Linked List data-structure
   * @example
   * const { LinkedList } = require('data-structures-algorithms-js');
   * const linkedList = new LinkedList();
   */
    constructor() {
        this._count = 0;
        this._head = null;
    }

   /**
   * Adds a element to the end of the linked list
   * @param {*} element Element passed to insert
   * @example
   * const { LinkedList } = require('data-structures-algorithms-js');
   * const linkedList = new LinkedList();
   *
   * linkedList.push(5); //inserts 5 to the end of the linked list
   */
    push(element) {
        const node = new Node(element);
        if (this._head === null) this._head = node;
        else {
            let currentNode = this.head();
            while (currentNode.next) {
                currentNode = currentNode.next;
            }
            currentNode.next = node;
        }
        this._count++;
    }

   /**
   * Removes a element if it exists
   * @param {*} element Element passed to remove
   * @returns {*|undefined} Returns the removed element or undefined
   * @example
   * const { LinkedList } = require('data-structures-algorithms-js');
   * const linkedList = new LinkedList();
   *
   * linkedList.push(3);
   * linkedList.push(9);
   *
   * linkedList.remove(3); //removes and returns number 3
   */
    remove(element) {
        const index = this.indexOf(element);
        return this.removeAt(index);
    }

   /**
   * Removes a element from a specific index if it exists
   * @param {*} element Element passed to remove
   * @returns {*|undefined} Returns the removed element or undefined
   * @example
   * const { LinkedList } = require('data-structures-algorithms-js');
   * const linkedList = new LinkedList();
   *
   * linkedList.push(3);
   * linkedList.push(9);
   *
   * linkedList.removeAt(1); //removes and returns number 9
   */
    removeAt(index) {
        if (index >= 0 && index < this._count) {
            let currentNode = this.head();
            if (index === 0) {
                this._head = currentNode.next;
            } else {
                let previousNode = this.getElementAt(index - 1, 1);
                currentNode = previousNode.next;
                previousNode.next = currentNode.next;
            }
            this._count--;
            return currentNode.element;
        }
        return undefined;
    }

   /**
   * Returns a element from a specific index if it exists
   * @param {*} index Index passed to return element
   * @returns {*|undefined} Returns the element or undefined
   * @example
   * const { LinkedList } = require('data-structures-algorithms-js');
   * const linkedList = new LinkedList();
   *
   * linkedList.push(3);
   * linkedList.push(9);
   *
   * linkedList.getElementAt(1); //returns number 9
   */
    getElementAt(index, flag = 0) {
        if (index >= 0 && index < this._count) {
            let currentNode = this._head;
            for (let i = 0; i < index; i++)
                currentNode = currentNode.next;
            if (flag === 0)
                return currentNode.element;
            return currentNode;
        }
        return undefined;
    }

   /**
   * Inserts a element to the specific position of the linked list
   * @param {*} element Element passed to insert
   * @param {Number} index Index to insert
   * @example
   * const { LinkedList } = require('data-structures-algorithms-js');
   * const linkedList = new LinkedList();
   *
   * linkedList.push(5);
   * linkedList.push(7);
   * 
   * linkedList.insert(15, 1); // inserts 15 at index 1
   */
    insert(element, index) {
        if (index >= 0 && index <= this._count) {
            const node = new Node(element);
            if (index === 0) {
                const currentNode = this._head;
                node.next = currentNode;
                this._head = node;
            } else {
                let currentNode = this.getElementAt(index - 1, 1);
                node.next = currentNode.next;
                currentNode.next = node;
            }
            this._count++;
            return true;
        }
        return false;
    }

   /**
   * Returns the index of a specific element if it exists
   * @param {*} element Element passed to find the index
   * @returns {Number} Returns the index or -1
   * @example
   * const { LinkedList } = require('data-structures-algorithms-js');
   * const linkedList = new LinkedList();
   *
   * linkedList.push(3);
   * linkedList.push(9);
   *
   * linkedList.indexOf(5); //returns -1
   * linkedList.indexOf(3); //returns 0
   */
    indexOf(element) {
        let currentNode = this._head;
        for (let i = 0; i < this._count; i++) {
            if (currentNode.element === element)
                return i;
            currentNode = currentNode.next;
        }
        return -1;
    }

    /**
   * Returns the size of the Linked List
   * @returns {Number} The number of elements in the Linked List
   * @example
   * const { LinkedList } = require('data-structures-algorithms-js');
   * const linkedList = new LinkedList();
   *
   * linkedList.size(); // returns 0;
   *
   * linkedList.push(8);
   *
   * linkedList.size(); //returns 1;
   */
    size() {
        return this._count;
    }
   /**
   * Returns the nodo on the head of the linked list
   * @returns {Object} returns the head object with the element and next node 
   * @example
   * const { LinkedList } = require('data-structures-algorithms-js');
   * const linkedList = new LinkedList();
   *
   *
   * linkedList.push(23);
   *
   * linkedList.head(); //returns {element:23, next:null};
   */
    head() {
        return this._head;
    }
   /**
   * Returns if the linked list is empty
   * @returns {Boolean}
   * @example
   * const { LinkedList } = require('data-structures-algorithms-js');
   * const linkedList = new LinkedList();
   *
   * linkedList.isEmpty(); // returns true;
   *
   * linkedList.push(23);
   *
   * linkedList.isEmpty(); //returns false;
   */
    isEmpty() {
        return this.size() === 0;
    }
    /**
   * Resets the Linked List
   * @example
   * const { LinkedList } = require('data-structures-algorithms-js');
   * const linkedList = new LinkedList();
   *
   * linkedList.push(15);
   *
   * linkedList.clear(); // now linked list is empty
   */
    clear(){
        this._count = 0;
        this._head = null;
    }
}

module.exports = LinkedList;