public class IntLinkedListTest {

    static int passed = 0;
    static int failed = 0;

    public static void main(String[] args) {
        testEmpty();
        testAddOne();
        testAddMany();
        testGetAllPositions();
        testGetFromBothSides();
        testGetOutOfBounds();

        testRemoveOnlyElement();
        testRemoveHead();
        testRemoveTail();
        testRemoveMiddleHeadWalk();
        testRemoveMiddleTailWalk();
        testRemoveOutOfBounds();
        testRemoveThenAdd();
        testRemoveAllSequentially();

        System.out.println();
        System.out.println("Passed: " + passed + " / " + (passed + failed));
    }

    static void testEmpty() {
        IntLinkedList l = new IntLinkedList();
        check("empty size", l.getSize(), 0);
    }

    static void testAddOne() {
        IntLinkedList l = new IntLinkedList();
        l.add(42);
        check("size after 1 add", l.getSize(), 1);
        check("get(0) after 1 add", l.get(0), 42);
    }

    static void testAddMany() {
        IntLinkedList l = new IntLinkedList();
        for (int i = 0; i < 5; i++) l.add(i * 10);
        check("size after 5 adds", l.getSize(), 5);
    }

    static void testGetAllPositions() {
        IntLinkedList l = new IntLinkedList();
        int[] vals = {10, 20, 30, 40, 50, 60};
        for (int v : vals) l.add(v);
        for (int i = 0; i < vals.length; i++) {
            check("get(" + i + ")", l.get(i), vals[i]);
        }
    }

    static void testGetFromBothSides() {
        // size=4 -> indices 0,1 walk from head; 2,3 walk from tail
        IntLinkedList l = new IntLinkedList();
        l.add(1); l.add(2); l.add(3); l.add(4);
        check("get(0) head-walk", l.get(0), 1);
        check("get(1) head-walk", l.get(1), 2);
        check("get(2) tail-walk", l.get(2), 3);
        check("get(3) tail-walk", l.get(3), 4);
    }

    static void testGetOutOfBounds() {
        IntLinkedList l = new IntLinkedList();
        l.add(1); l.add(2);
        try { l.get(-1); System.out.println("FAIL get(-1) should throw"); failed++; }
        catch (IllegalArgumentException e) { System.out.println("PASS get(-1) throws"); passed++; }
        try { l.get(2); System.out.println("FAIL get(size) should throw"); failed++; }
        catch (IllegalArgumentException e) { System.out.println("PASS get(size) throws"); passed++; }
    }

    static void testRemoveOnlyElement() {
        IntLinkedList l = new IntLinkedList();
        l.add(99);
        l.remove(0);
        check("remove only: size", l.getSize(), 0);
        // Re-add to make sure head/tail were properly reset
        l.add(5);
        check("remove only then add: size", l.getSize(), 1);
        check("remove only then add: get(0)", l.get(0), 5);
    }

    static void testRemoveHead() {
        IntLinkedList l = new IntLinkedList();
        l.add(1); l.add(2); l.add(3);
        l.remove(0);
        check("remove head: size", l.getSize(), 2);
        check("remove head: get(0)", l.get(0), 2);
        check("remove head: get(1)", l.get(1), 3); // uses tail-walk -> tests prev chain
    }

    static void testRemoveTail() {
        IntLinkedList l = new IntLinkedList();
        l.add(1); l.add(2); l.add(3);
        l.remove(2);
        check("remove tail: size", l.getSize(), 2);
        check("remove tail: get(0)", l.get(0), 1);
        check("remove tail: get(1)", l.get(1), 2); // tail-walk -> new tail must be correct
    }

    static void testRemoveMiddleHeadWalk() {
        // size=4, remove index 1 (< size/2 -> head-walk branch)
        IntLinkedList l = new IntLinkedList();
        l.add(1); l.add(2); l.add(3); l.add(4);
        l.remove(1);
        check("remove mid (head-walk): size", l.getSize(), 3);
        check("remove mid (head-walk): get(0)", l.get(0), 1);
        check("remove mid (head-walk): get(1)", l.get(1), 3);
        check("remove mid (head-walk): get(2)", l.get(2), 4);
    }

    static void testRemoveMiddleTailWalk() {
        // size=6, remove index 4 (>= size/2 -> tail-walk branch)
        IntLinkedList l = new IntLinkedList();
        l.add(1); l.add(2); l.add(3); l.add(4); l.add(5); l.add(6);
        l.remove(4);
        check("remove mid (tail-walk): size", l.getSize(), 5);
        check("remove mid (tail-walk): get(0)", l.get(0), 1);
        check("remove mid (tail-walk): get(3)", l.get(3), 4);
        check("remove mid (tail-walk): get(4)", l.get(4), 6);
    }

    static void testRemoveOutOfBounds() {
        IntLinkedList l = new IntLinkedList();
        l.add(1); l.add(2);
        try { l.remove(-1); System.out.println("FAIL remove(-1) should throw"); failed++; }
        catch (IllegalArgumentException e) { System.out.println("PASS remove(-1) throws"); passed++; }
        try { l.remove(2); System.out.println("FAIL remove(size) should throw"); failed++; }
        catch (IllegalArgumentException e) { System.out.println("PASS remove(size) throws"); passed++; }
        check("oob did not mutate: size", l.getSize(), 2);
    }

    static void testRemoveThenAdd() {
        IntLinkedList l = new IntLinkedList();
        l.add(1); l.add(2); l.add(3);
        l.remove(2); // remove tail
        l.add(99);   // new tail
        check("remove-then-add: size", l.getSize(), 3);
        check("remove-then-add: get(0)", l.get(0), 1);
        check("remove-then-add: get(1)", l.get(1), 2);
        check("remove-then-add: get(2)", l.get(2), 99);
    }

    static void testRemoveAllSequentially() {
        // Remove from head repeatedly until empty, then re-fill.
        IntLinkedList l = new IntLinkedList();
        for (int i = 1; i <= 5; i++) l.add(i);
        while (l.getSize() > 0) l.remove(0);
        check("remove all: size", l.getSize(), 0);
        l.add(7); l.add(8);
        check("refill after remove-all: size", l.getSize(), 2);
        check("refill after remove-all: get(0)", l.get(0), 7);
        check("refill after remove-all: get(1)", l.get(1), 8);
    }

    static void check(String label, int actual, int expected) {
        if (actual == expected) {
            System.out.println("PASS " + label + " = " + actual);
            passed++;
        } else {
            System.out.println("FAIL " + label + " expected=" + expected + " actual=" + actual);
            failed++;
        }
    }
}
