public class LinkedListInt {
    private NodeInt head;
    private int numElems;

    // creates a linked list with 0 elements; head is null
    public LinkedListInt() {
        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); // exit the program
        }
    }

    //returns node index in the list
    //assumes that index satisfies 0 <= index < numElems
    private NodeInt getNode(int index) {
        NodeInt current = head;
        for (int x = 0; x < index; x++) {
            // current is now the xth node in the list
            current = current.getNext();
        }
        return current;
    }

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

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

    // add newElement to the end of the list
    public void add(int newElement) {
        add(numElems, newElement);
    }

    // goal: arr[index] stores newElement
    // push remaining elements down
    public void add(int index, int newElement) {
        // check is different because numElems is allowed
        if (index < 0 || index > numElems) {
            System.out.println("Error: List access out of bounds at index " + index);
            System.exit(1); // exit the program
        }
        NodeInt newNode = new NodeInt(newElement);
        if (index == 0) {
            newNode.setNext(head);
            head = newNode;
        } else {
            NodeInt previous = getNode(index - 1); // find the node immediately before the index-th node
            NodeInt previousNext = previous.getNext(); // find the node immediately after previous
            previous.setNext(newNode); // set it to point to our new node
            newNode.setNext(previousNext); // continue the list
        }
        numElems++;
    }

    // removes the element at index; moving all later elements
    // up one slot.
    // Returns the removed element
    public int remove(int index) {
        checkInBounds(index);
        int toReturn;
        if (index == 0) {
            toReturn = head.getData();
            head = head.getNext();
        } else {
            NodeInt previous = getNode(index - 1);
            NodeInt 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 occurrenct, return -1
    public int indexOf(int element) {
        NodeInt current = head;
        for (int index = 0; index < numElems; index++) {
            if (current.getData() == element) {
                return index;
            }
            current = current.getNext();
        }
        return -1;
    }

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

    public String toString() {
        if (numElems == 0) {
            return "[ ]";
        }
        String ret = "[";
        NodeInt 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) {
        LinkedListInt test = new LinkedListInt();

        test.add(10);
		test.add(20);
		test.add(30);
		test.add(40);
		System.out.println("Added elements 10, 20, 30, 40");
		System.out.println(test);			  
		System.out.println("New element 100 at slot 1; push remainder down");
		test.add(1, 100);
		System.out.println(test);			  
		System.out.println("Testing indexOf: 30 is at slot " + test.indexOf(30));
		System.out.println("Testing indexOf: 7 is at slot " + test.indexOf(7));
		test.remove(2); 
		System.out.println("Remove the element at slot 2"); System.out.println(test);			  
		System.out.println("Now, 30 is at slot " + test.indexOf(30));


    }
}
