// Generic version of ArrayListInt.java
// Key difference: must use Object[] instead of E[] (Java limitation)
// Retrieving elements requires a cast: (E) arr[index]
public class ArrayList<E> {
    private Object[] arr;
    private int numElems;

    // creates an array with 1 slot, sets number of elements to 0
    public ArrayList() {
        arr = new Object[1];
        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: Array access out of bounds at index " + index);
            System.exit(1);
        }
    }

    // returns the element stored in slot index
    // cast (E) is safe: add() guarantees only E objects are stored
	@SuppressWarnings("unchecked") //avoids warnings for this method; students do not need to know (and should not use) this
    public E get(int index) {
        checkInBounds(index);
        return (E) arr[index];
    }

    // sets slot index to store newElement
    // returns the element previously stored at index
	@SuppressWarnings("unchecked") //avoids warnings for this method; students do not need to know (and should not use) this
    public E set(int index, E newElement) {
        checkInBounds(index);
        E ret = (E) arr[index];
        arr[index] = newElement;
        return ret;
    }

    // make sure that arr has at least minCapacity slots
    private void ensureCapacity(int minCapacity) {
        if (arr.length >= minCapacity) {
            return;
        }
        int newSize = arr.length;
        while (newSize < minCapacity) {
            newSize *= 2;
        }
        Object[] newArr = new Object[newSize];
        for (int index = 0; index < numElems; index++) {
            newArr[index] = arr[index];
        }
        arr = newArr;
    }

    // add newElement to the end of the list
    public void add(E newElement) {
        ensureCapacity(numElems + 1);
        arr[numElems] = newElement;
        numElems++;
    }

    // goal: slot index stores newElement; push remaining elements down
    public void add(int index, E newElement) {
        if (index == numElems) {
            add(newElement);
            return;
        }
        checkInBounds(index);
        ensureCapacity(numElems + 1);
        for (int pushing = numElems; pushing >= index + 1; pushing--) {
            arr[pushing] = arr[pushing - 1];
        }
        arr[index] = newElement;
        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 ret = get(index);
        for (int pushIndex = index; pushIndex < numElems - 1; pushIndex++) {
            arr[pushIndex] = arr[pushIndex + 1];
        }
        arr[numElems - 1] = null;
        numElems--;
        return ret;
    }

    // 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) {
        for (int index = 0; index < numElems; index++) {
            if (element.equals(arr[index])) {
                return index;
            }
        }
        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 = "[";
        for (int index = 0; index < numElems - 1; index++) {
            ret += arr[index] + ", ";
        }
        ret += arr[numElems - 1] + "]";
        return ret;
    }

    public static void main(String[] args) {
        ArrayList<String> test = new ArrayList<>();
        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);
    }
}
