public class DoublyLinkedListInt {
    private DLLNodeInt head;
    private DLLNodeInt tail;
    private int numElems;

    // creates a linked list with 0 elements; head is null
    public DoublyLinkedListInt() {
        head = null;
        tail = 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 DLLNodeInt getNode(int index) {
		if(index >= size()/2) {
			DLLNodeInt current = tail; //node size() - 1

			for (int skips = size() - 1; skips > index; skips--) {
				// current is now at node "skips" in the list
				current = current.getPrevious();
			}
			return current;
		}
		else {
			DLLNodeInt current = head;
			for (int skips = 0; skips < index; skips++) {
				// current is now at node "skips" in the list
				current = current.getNext();
			}
			return current;
		}
    }

    // returns the int stored in slot index
    public int get(int index) {
        checkInBounds(index);
        DLLNodeInt 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);
        DLLNodeInt 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) {
        DLLNodeInt newNode = new DLLNodeInt(newElement);
		newNode.setPrevious(tail);
		if(numElems > 0) {
			tail.setNext(newNode);
		} else {
			head = newNode;
		}
		tail = newNode;
		numElems++;
    }

    public void addFirst(int newElement) {
        DLLNodeInt newNode = new DLLNodeInt(newElement);
		newNode.setNext(head);
		if(numElems > 0) {
			head.setPrevious(newNode);
		} else {
			tail = newNode;
		}
		head = newNode;
		numElems++;
    }

    // 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 == numElems) {
			add(newElement);
			return;
		}
		checkInBounds(index);
        DLLNodeInt newNode = new DLLNodeInt(newElement);
		DLLNodeInt previous = getNode(index - 1); // find the node immediately before the index-th node

		DLLNodeInt 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
		newNode.setPrevious(previous);
        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();
			head.setPrevious(null);
        } else {
            DLLNodeInt previous = getNode(index - 1);
            DLLNodeInt toRemove = previous.getNext();
            DLLNodeInt nodeAfter = toRemove.getNext();
            previous.setNext(nodeAfter);
			if(nodeAfter != null) {
				nodeAfter.setPrevious(previous);
			}
            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) {
        DLLNodeInt 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 void concat(DoublyLinkedListInt other) {
		DLLNodeInt ourLast = tail;
		DLLNodeInt theirFirst = other.head;
		ourLast.setNext(theirFirst);
		theirFirst.setPrevious(ourLast);
		tail = other.tail;
		numElems += other.numElems;
	}

	public boolean isPalindrome() {
		DLLNodeInt backward = tail; //node size() - 1
		DLLNodeInt forward = head; //node size() - 1
		for (int skips = 0; skips < size()/2; skips++) {
			// forward is now at node skips
			// backward is at node size() - 1 - skips
			if(forward.getData() != backward.getData()) {
				return false;
			}
			backward = backward.getPrevious();
			forward = forward.getNext();
		}
		return true;
	}


    public String toString() {
        if (numElems == 0) {
            return "[ ]";
        }
        String ret = "[";
        DLLNodeInt 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) {
        DoublyLinkedListInt test = new DoublyLinkedListInt();
        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(3); 
		System.out.println("Remove the element at slot 3"); System.out.println(test);			  
		System.out.println("Now, 30 is at slot " + test.indexOf(30));
    }
}
