import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.Scanner;

/**
 *
 * @author Mark Young (A00000000)
 */
public class TestKeepSmall {

    public static final int NUM_ASGNS = 10;
    public static final Scanner kbd = new Scanner(System.in);
    public static final Random r = new Random();

    public static void main(String[] args) {
        for (int numDrops = 2; numDrops < NUM_ASGNS / 2; ++numDrops) {
            int numKeeps = NUM_ASGNS - numDrops;
            int[] grades = new int[NUM_ASGNS];
            int numKept = 0;

            System.out.println("\n"
                    + "Keeping the best " + numKeeps + " of " + NUM_ASGNS
                    + " assignments.");

            // set up the small-values-saver
            KeepSmallInt drops = new KeepSmallInt(numDrops);

            // test size/capacity/isEmpty
            System.out.println(" --> starts with size 0: "
                    + (drops.size() == 0 ? "PASS" : "FAIL ***"));
            System.out.println(" --> starts with given capacity: "
                    + (drops.capacity() == numDrops ? "PASS" : "FAIL ***"));
            System.out.println(" --> starts empty: "
                    + (drops.isEmpty() ? "PASS" : "FAIL ***"));

            // test bad get
            try {
                Integer noSuch = drops.get(0);
                System.out.println(" --> get(0) throws an exception: "
                        + "FAIL ***");
            } catch (Exception e) {
                System.out.println(" --> get(0) throws correct exception: "
                        + ((e instanceof NoSuchElementException)
                                ? "PASS" : "FAIL ***"));
            }
            pause();

            // toArray
            int[] dropObjects = drops.toArray();
            System.out.println(" --> toArray() returns correct size: "
                    + (dropObjects.length == drops.size()
                            ? "PASS" : "FAIL ***"));
            pause();

            // test add
            for (int i = 1; i <= NUM_ASGNS; ++i) {
                // get a random grade
                int grade = randomGrade();
                System.out.printf("A%02d grade is %3d.%n", i, grade);

                // see if it belongs on the drop list
                Integer keeper = drops.add(grade);

                // if not, add it to the kept grades array
                if (keeper != null) {
                    grades[numKept] = keeper;
                    ++numKept;

                    // test get
                    Integer newMaxDrop = drops.get(drops.size() - 1);
                    System.out.println(" --> \"bumped out\" largest value: "
                            + (newMaxDrop <= keeper ? "PASS" : "FAIL ***"
                            + "(dropped " + keeper + " instead of " 
                            + newMaxDrop + ")"));
                }
            }
            System.out.println(" --> correct number of non-null get returns: "
                    + (numKept == numKeeps
                            ? "PASS" : "FAIL ***"));
            pause();

            // test bad get
            try {
                Integer noSuch = drops.get(numDrops);
                System.out.println(" --> get(" + numDrops + ") throws an "
                        + "exception: FAIL ***");
            } catch (Exception e) {
                System.out.println(" --> get(" + numDrops + ") throws correct "
                        + "exception: "
                        + ((e instanceof NoSuchElementException)
                                ? "PASS" : "FAIL ***"));
            }
            pause();

            // toArray
            dropObjects = drops.toArray();
            System.out.println(" --> toArray() returns correct size: "
                    + (dropObjects.length == drops.size()
                            ? "PASS" : "FAIL ***"));
            System.out.println("\n"
                    + "Your dropped grades are " + Arrays.toString(dropObjects)
                    + "\nYour kept grades are " + Arrays.toString(grades));

            boolean inOrder = true;
            int upperBound = Math.min(dropObjects.length, drops.size());
            for (int j = 1; j < upperBound; ++j) {
                if (dropObjects[j - 1] > dropObjects[j]) {
                    inOrder = false;
                }
            }
            System.out.println(" --> toArray(T[]) returns ordered array: "
                    + (inOrder ? "PASS" : "FAIL ***"));

            // contains
            Integer in = oneOf(dropObjects);
            System.out.println(" --> contains " + in + ": "
                    + (drops.contains(in) ? "PASS" : "FAIL ***"));
            Integer out = oneNotOf(dropObjects);
            System.out.println(" --> !contains " + out + ": "
                    + (!drops.contains(out) ? "PASS" : "FAIL ***"));
            pause();
        }
    }

    /**
     * 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();
    }

    /**
     * Create a random grade in the range 0..100.
     * Use a distribution slightly skewed to higer numbers.
     *
     * @return a non-uniformly random number in the range 0..100.
     */
    private static int randomGrade() {
        return Math.max(r.nextInt(101), r.nextInt(90));
    }

    /**
     * Grab one of the values from an array.
     *
     * @param dropComps the array to take from
     * @return a randomly-chosen element of that array
     */
    private static int oneOf(int[] dropComps) {
        int len = dropComps.length;
        int n = r.nextInt(len);
        return dropComps[n];
    }

    /**
     * Grab a random grade that's NOT in the given array.
     *
     * @param dropComps the array to avoid values from
     * @return a random number in the range 0..100 that is not in 
     * {@code dropComps}
     */
    private static int oneNotOf(int[] dropComps) {
        int len = dropComps.length;
        int result = 0;
        boolean ok;
        do {
            ok = true;
            result = r.nextInt(101);
            for (int i = 0; ok && i < dropComps.length; ++i) {
                if (dropComps[i] == result) {
                    ok = false;
                }
            }
        } while (!ok);
        return result;
    }

}
