import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;

/**
 * A class to test the TreeMapBag implementation. (The iterator is not included
 * in the testing.
 *
 * @author Mark Young (A00000000)
 */
public class TestBag {

    public static void main(String[] args) {
        System.out.println("\n"
                + "Zero-Arg Constructor\n"
                + "--------------------\n");
        testABag(new TreeMapBag<>());
        System.out.println();
        
        System.out.println("\n"
                + "One-Arg Constructor\n"
                + "-------------------\n");
        testABag(new TreeMapBag<>((a, b) -> Integer.compare(b, a)));
        System.out.println();
        
        System.out.println("\n"
                + "The All Methods\n"
                + "---------------\n");
        testAlls();
    }

    /**
     * Run tests on most of the Bag methods.
     *
     * @param bag the Bag to use for the tests
     */
    private static void testABag(Bag<Integer> bag) {
        // test size/isEmpty/getFrequency/contains
        testOthers(bag);
        pause();
        
        // test remove() exception
        try {
            Integer num = bag.remove();
            System.out.println("removed " + num);
        } catch (NoSuchElementException nse) {
            System.out.println("Got expected exception");
        } catch (Exception nse) {
            System.out.println("Got unexpected " + nse);
        }
        System.out.println(bag);
        pause();
        
        // test add
        for (int i = 0; i < 12; ++i) {
            if (bag.add(i)) {
                System.out.println("Added " + i);
            } else {
                System.out.println("Failed to add " + i);
            }
        }
        pause();
        
        // test toString
        System.out.println(bag);
        pause();

        // test toArray(...)
        testArray(bag);
        pause();

        // test size/isEmpty/getFrequency/contains
        bag.add(6);
        testOthers(bag);
        pause();

        // test remove(T)
        for (int i = 0; i < 20; i += 3) {
            if (bag.remove(i)) {
                System.out.println("Removed " + i);
            } else {
                System.out.println("Failed to remove " + i);
            }
        }
        System.out.println(bag);
        pause();

        // test size/isEmpty/getFrequency/contains
        testOthers(bag);
        pause();

        // test remove()
        try {
            while (true) {
                System.out.println("Removing " + bag.remove());
            }
        } catch (NoSuchElementException nsee) {
            System.out.println("No more to remove!");
        }
        System.out.println(bag);
        pause();

        // test size/isEmpty/getFrequency/contains
        testOthers(bag);
        pause();
    }

    /**
     * Test the containsAll, addAll, removeAll and retainAll methods.
     */
    private static void testAlls() {
        Bag<String> bag = new TreeMapBag<>();
        String[] words = "to be or not to be that is the question".split(" ");
        Set<String> set = new TreeSet<>();
        
        // fill bag and set
        for (String word : words) {
            bag.add(word);
            set.add(word);
        }
        
        // test containsAll
        Set<String> toBe = Set.of("to", "be");
        Set<String> cantBe = Set.of("can't", "be");
        Set<String> nevertheless = Set.of("nevertheless");
        
        System.out.println(bag + ".containsAll(" + toBe + ") == "
                + bag.containsAll(toBe)
                + "\n(should be true)");
        System.out.println(bag + ".containsAll(" + cantBe + ") == "
                + bag.containsAll(cantBe)
                + "\n(should be false)");
        System.out.println(bag + ".containsAll(" + nevertheless + ") == "
                + bag.containsAll(nevertheless)
                + "\n(should be false)");
        System.out.println(bag);
        pause();
        
        // test removeAll
        System.out.println(bag + ".removeAll(" + nevertheless + ") == "
                + bag.removeAll(nevertheless)
                + "\n(should be false)");
        System.out.println(bag + ".removeAll(" + cantBe + ") == "
                + bag.removeAll(cantBe)
                + "\n(should be true)");
        System.out.println(bag + ".removeAll(" + toBe + ") == "
                + bag.removeAll(toBe)
                + "\n(should be true)");
        System.out.println(bag);
        pause();
        
        // test retainAll
        Set<String> toKeep = Set.of("never", "that", "the", "not", "is");
        System.out.println(bag + ".retainAll(" + toKeep + ") == "
                + bag.retainAll(toKeep)
                + "\n(should be true)");
        System.out.println(bag + ".retainAll(" + toKeep + ") == "
                + bag.retainAll(toKeep)
                + "\n(should be false)");
        System.out.println(bag);
        pause();
        
        // test addAll
        List<String> toAdd = List.of("monotonic", "or", "logic", "or", "not");
        Set<String> moreToAdd = new TreeSet<>();
        System.out.println(bag + ".addAll(" + toAdd + ") == "
                + bag.addAll(toAdd)
                + "\n(should be true)");
        System.out.println(bag + ".addAll(" + moreToAdd + ") == "
                + bag.addAll(moreToAdd)
                + "\n(should be false)");
        System.out.println(bag);
        pause();

        // test clear (!)
        System.out.println(bag + ".clear()");
        bag.clear();
        System.out.println(bag);
        pause();
    }

    /**
     * Test the toArray methods.
     *
     * @param bag the Bag to run the tests on.
     */
    private static void testArray(Bag<Integer> bag) {
        // toArray()
        Object[] numbers = bag.toArray();
        System.out.println(bag + ".toArray() == " + Arrays.toString(numbers));

        // toArray(T[]) -- big array
        Integer[] big = new Integer[20];
        Arrays.fill(big, -1);
        System.out.println("Array 'big' before: " + Arrays.toString(big));
        Integer[] returnedBig = bag.toArray(big);
        System.out.println(bag + ".toArray(big) == " 
                + Arrays.toString(returnedBig));
        System.out.println("Array 'big' after: " + Arrays.toString(big));

        // toArray(T[]) -- small array
        Integer[] small = new Integer[2];
        Arrays.fill(small, -1);
        System.out.println("Array 'small' before: " + Arrays.toString(small));
        Integer[] returnedSmall = bag.toArray(small);
        System.out.println(bag + ".toArray(small) == " 
                + Arrays.toString(returnedSmall));
        System.out.println("Array 'small' after: " + Arrays.toString(small));
    }

    /**
     * Test the simple Bag operations: size, isEmpty, contains and getFrequency.
     *
     * @param bag the Bag to test the methods on.
     */
    private static void testOthers(Bag<Integer> bag) {
        System.out.println(bag + ".size() == " + bag.size());
        System.out.println(bag + ".isEmpty() == " + bag.isEmpty());
        System.out.println(bag + ".contains(6) == " + bag.contains(6));
        System.out.println(bag + ".contains(7) == " + bag.contains(7));
        System.out.println(bag + ".getFrequency(6) == " + bag.getFrequency(6));
        System.out.println(bag + ".getFrequency(7) == " + bag.getFrequency(7));
    }
    
    /**
     * The one and only Scanner on System.in.
     */
    private static final Scanner KBD = new Scanner(System.in);

    /**
     * Prompt the user and wait for them to press the enter key.
     */
    private static void pause() {
        System.out.print("\n...press enter...");
        KBD.nextLine();
        System.out.println();
    }

}
