// Generic version of LinkedListInt.java
// Replace: int data -> E data, NodeInt -> Node<E>
// Key change: indexOf uses .equals() instead of ==
public class LinkedList<E> {
    private Node<E> head;
    private int numElems;

    // creates a linked list with 0 elements; head is null
    public LinkedList() {
        head = null;
        numElems = 0;
    }

    // returns the number of elements currently stored in the list
    public int size() {
        return numElems;
    }

    // if index is in bounds: index >= 0 and index < numElems, does nothing
    // if index is out of bounds, displays an error and quits
    private void checkInBounds(int index) {
        if (index < 0 || index >= numElems) {
            System.out.println("Error: List access out of bounds at index " + index);
            System.exit(1);
        }
    }

    // returns node at position index in the list
    // assumes that index satisfies 0 <= index < numElems
    private Node<E> getNode(int index) {
        Node<E> current = head;
        for (int skips = 0; skips < index; skips++) {
            current = current.getNext();
        }
        return current;
    }

    // returns the element stored in slot index
    public E get(int index) {
        checkInBounds(index);
        return getNode(index).getData();
    }

    // sets slot index to store newElement
    // returns the element previously stored at index
    public E set(int index, E newElement) {
        checkInBounds(index);
        Node<E> current = getNode(index);
        E oldValue = current.getData();
        current.setData(newElement);
        return oldValue;
    }

    // add newElement to the end of the list
    public void add(E newElement) {
        Node<E> newNode = new Node<>(newElement);
        if (numElems == 0) {
            head = newNode;
        } else {
            Node<E> lastInList = getNode(size() - 1);
            lastInList.setNext(newNode);
        }
        numElems++;
    }

    // goal: slot index stores newElement; push remaining elements down
    public void add(int index, E newElement) {
        if (index < 0 || index > numElems) {
            System.out.println("Error: List access out of bounds at index " + index);
            System.exit(1);
        }
        Node<E> newNode = new Node<>(newElement);
        if (index == 0) {
            newNode.setNext(head);
            head = newNode;
        } else {
            Node<E> previous = getNode(index - 1);
            Node<E> previousNext = previous.getNext();
            previous.setNext(newNode);
            newNode.setNext(previousNext);
        }
        numElems++;
    }

    // removes the element at index, moving all later elements up one slot
    // returns the removed element
    public E remove(int index) {
        checkInBounds(index);
        E toReturn;
        if (index == 0) {
            toReturn = head.getData();
            head = head.getNext();
        } else {
            Node<E> previous = getNode(index - 1);
            Node<E> toRemove = previous.getNext();
            previous.setNext(toRemove.getNext());
            toReturn = toRemove.getData();
        }
        numElems--;
        return toReturn;
    }

    // find the first occurrence of element and return its index
    // if there is no occurrence, return -1
    // Note: uses .equals() instead of == to compare objects
    public int indexOf(E element) {
        Node<E> current = head;
        for (int index = 0; index < numElems; index++) {
            if (element.equals(current.getData())) {
                return index;
            }
            current = current.getNext();
        }
        return -1;
    }

    // returns true if element is in the list; false otherwise
    public boolean contains(E element) {
        return indexOf(element) != -1;
    }

    public String toString() {
        if (numElems == 0) {
            return "[ ]";
        }
        String ret = "[";
        Node<E> current = head;
        for (int index = 0; index < numElems - 1; index++) {
            ret += current.getData() + ", ";
            current = current.getNext();
        }
        ret += current.getData() + "]";
        return ret;
    }

    public static void main(String[] args) {
        LinkedList<String> test = new LinkedList<>();
        test.add("hello");
        test.add("world");
        test.add("CS");
        System.out.println("Added hello, world, CS");
        System.out.println(test);
        System.out.println("New element 136 at slot 1");
        test.add(1, "136");
        System.out.println(test);
        System.out.println("indexOf 'world': " + test.indexOf("world"));
        System.out.println("indexOf 'xyz':   " + test.indexOf("xyz"));
        test.remove(2);
        System.out.println("Remove element at slot 2");
        System.out.println(test);

    }
}
