import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

public class TestIterator {

    public static void main(String[] args) {
        testActivities1And2();
        // testActivity3();
        // testActivity4();
        // testActivity5();
        // testActivity6();
    }

    private static void testActivities1And2() {
        System.out.println("\n=== Activities 1 & 2 ===");
        // create the Bag
        Bag<String> bag = new ArrayBag<>();
        bag.add("This");
        bag.add("is");
        bag.add("the");
        bag.add("bag");

        // show the Bag
        System.out.println("Here is the bag: " + bag);

        // create the iterator
        Iterator<String> it = bag.iterator();
        while (it.hasNext()) {
            String word = it.next();
            System.out.println("There is a \"" + word + "\" in the Bag");
        }
        if (it.hasNext()) {
            System.out.println("This is very weird!");
        } else {
            System.out.println("There is nothing more in the Bag");
        }

        // mess around
        bag.add("extended");
        if (it.hasNext()) {
            try {
                System.out.println("OK, now there is a \"" + it.next() 
                        + "\" in the Bag");
            } catch (ConcurrentModificationException cme) {
                System.out.println("Good! You noticed that change!");
            }
        } else {
            System.out.println("Didn't notice the extra thing in the bag...");
        }

        // try to go past the end
        try {
            System.out.println("This is no " + it.next() + " in the bag, "
                    + "so this line should NOT get printed!");
            System.out.println("You may be ready for Activity 2");
        } catch (ConcurrentModificationException cme) {
            System.out.println("Good! You noticed that change!");
        } catch (NoSuchElementException nse) {
            System.out.println("Good! You noticed the end.");
        }
    }

    private static void testActivity3() {
        System.out.println("\n=== Activity 3 ===");
        // make the bag
        Bag<Integer> bag = new ArrayBag<>();
        bag.add(10);
        bag.add(20);
        bag.add(30);
        bag.add(42);
        bag.add(50);

        // show the bag
        System.out.println("The bag is: " + bag);

        // create the iterator
        Iterator<Integer> it = bag.iterator();
        System.out.println("The first call to next returns " + it.next());
        System.out.println("The second call to next returns " + it.next());

        // test remove
        System.out.println("I will try to remove that.");
        try {
            it.remove();
            System.out.println("Good. That seems to have worked.");
        } catch (Exception e) {
            System.out.println("FAIL: threw " + e);
        }
        System.out.println("The next item should be 52.");
        try {
            Integer num = it.next();
            if (num == 50) {
                System.out.println("Good!");
            } else {
                System.out.println("FAIL: it was " + num);
            }
        } catch (Exception e) {
            System.out.println("FAIL: threw " + e);
        }

        System.out.println("The remaining items should be 30 and 42:");
        while (it.hasNext()) {
            System.out.println("\t" + it.next());
        }
        System.out.println("The bag is now: " + bag);
    }

    private static void testActivity4() {
        System.out.println("\n=== Activity 4 ===");

        // create and show the bag
        Bag<Double> bag = new ArrayBag<>();
        bag.add(10.1);
        bag.add(20.2);
        bag.add(30.3);
        System.out.println("The bag is " + bag);

        // create the iterator
        Iterator<Double> it = bag.iterator();

        // test throw for premature removal
        System.out.println("Testing remove before next");
        try {
            it.remove();
            System.out.println("FAIL: no exception thrown!");
        } catch (IllegalStateException ise) {
            System.out.println("Good! correct exception thrown");
        } catch (Exception e) {
            System.out.println("FAIL: wrong exception thrown -- " + e);
        }

        // test double remove after throw
        System.out.println("Testing remove after remove");
        try {
            it.next();
            it.remove();
            it.remove();
            System.out.println("FAIL: no exception thrown!");
        } catch (IllegalStateException ise) {
            System.out.println("Good! correct exception thrown");
        } catch (Exception e) {
            System.out.println("FAIL: wrong exception thrown -- " + e);
        }

        // test next and remove
        System.out.println("The remaining items should be 30.3 and 20.2");
        while (it.hasNext()) {
            System.out.println("\t" + it.next());
            it.remove();
        }

        // now empty?
        System.out.println("The Bag should now be empty: " + bag);
    }

    private static void testActivity5() {
        System.out.println("\n=== Activity 5 ===");

        // create and show the bag
        Bag<String> bag = new ArrayBag<>();
        String[] words = "to boldly go where no one has gone before".split(" ");
        for (String word : words) {
            bag.add(word);
        }
        System.out.println("Here is the bag: " + bag);

        // test removeAll
        System.out.println("Removing words 'go' 'no' 'so' 'to' and 'zo'");
        bag.removeAll(List.of("go", "no", "so", "to", "zo"));
        System.out.println("Bag is now: " + bag);

        // test retainAll
        System.out.println("Retaining words 'before' 'boldly' 'go' 'sleep'");
        bag.retainAll(List.of("before", "boldly", "go", "sleep"));
        System.out.println("Bag is now: " + bag);
    }

    private static void testActivity6() {
        System.out.println("\n=== Activity 6 ===");

        // create and show the bag
        Bag<Integer> bag = new ArrayBag<>();
        for (int i = 1; i <= 5; ++i) {
            bag.add(i);
        }
        System.out.println("Here is the bag: " + bag);

        // create the iterator
        Iterator<Integer> it1 = bag.iterator();
        Iterator<Integer> it2 = bag.iterator();

        // take one step
        Integer num1, num2;
        num1 = it1.next();
        num2 = it2.next();
        if (num1 != num2) {
            System.out.println("FAIL: it1.next() != it2.next()");
            System.out.println("\tit1.next() = " + num1);
            System.out.println("\tit2.next() = " + num2);
        }

        // use it1 to remove
        try {
            System.out.println("Removing an item using it1");
            it1.remove();
            System.out.println("That seems to have worked");
        } catch (Exception e) {
            System.out.println("FAIL: threw " + e);
        }

        // use it1 to advance
        try {
            System.out.println("Advancing using it1");
            num1 = it1.next();
            System.out.println("That seemed to work -- got " + num1);
        } catch (Exception e) {
            System.out.println("FAIL: threw " + e);
        }

        // try to advance using it2
        try {
            System.out.println("Advancing using it2");
            num2 = it2.next();
            System.out.println("FAIL: did not throw CME");
        } catch (ConcurrentModificationException cme) {
            System.out.println("Good! Noticed the external change");
        } catch (Exception e) {
            System.out.println("FAIL: threw " + e);
        }

        // try to remove using it2
        try {
            System.out.println("Removing using it2");
            num2 = it2.next();
            System.out.println("FAIL: did not throw CME");
        } catch (ConcurrentModificationException cme) {
            System.out.println("Good! Noticed the external change");
        } catch (Exception e) {
            System.out.println("FAIL: threw " + e);
        }

        // use it1 to advance
        try {
            System.out.println("Advancing (again) using it1");
            num1 = it1.next();
            System.out.println("That seemed to work -- got " + num1);
        } catch (Exception e) {
            System.out.println("FAIL: threw " + e);
        }
    }
}
