import java.util.Comparator;

/**
 * Class for performing "instrumented" quicksort.
 * (See source code at <a href="../source/Quicksort.java">Quicksort.java</a>.)
 */
public class Quicksort extends Sorter {

    // ---- methods ----

    /**
     * Sorts array.
     * @param a array to sort (in compareTo orter)
     * @return counters showing number of comparisons and number of swaps
     */
    public static SortResult sort(Comparable[] a) {
        return sort(a, null);
    }

    /**
     * Sorts array.
     * @param a array to sort 
     * @param c comparator, or null to use array's compareTo method (in which 
     *   case its elements should be Comparable)
     * @return counters showing number of comparisons and number of swaps
     */
    public static SortResult sort(Object[] a, Comparator c) {
        Counters counters = new Counters();
        quicksort_r(a, 0, a.length-1, c, counters);
        return new SortResult(counters.numCompares(), counters.numSwaps());
    }

    /** 
     * Sorts part of array using quicksort.
     * @param a array to sort
     * @param firstIndex index of first element to sort
     * @param lastIndex index of first element to sort
     * @param c comparator, or null to use array's compareTo method (in which 
     *   case its elements should be Comparable)
     * @param counters object with counters to update
     */
    private static void quicksort_r(Object[] a, int firstIndex, int lastIndex,
            Comparator comparator, Counters counters) {
        if (firstIndex >= lastIndex)
            return;
        int pivotIndex = split(a, firstIndex, lastIndex, comparator, counters);
        quicksort_r(a, firstIndex, pivotIndex-1, comparator, counters);
        quicksort_r(a, pivotIndex+1, lastIndex, comparator, counters);
    }

    /*
     * Splits part of array for quicksort -- selects pivot and rearranges
     *   selected part of array so that all elements <= the pivot are to 
     *   its left and all elements > the pivot are to its right.
     * @param a array being sorted
     * @param firstIndex index of first element to look at
     * @param lastIndex index of first element to look at
     * @param c comparator, or null to use array's compareTo method (in which 
     *   case its elements should be Comparable)
     * @param counters object with counters to update
     * @return index of pivot
     */
    private static int split(Object[] a, int firstIndex, int lastIndex,
            Comparator comparator, Counters counters) {
        int k = firstIndex+1;
        int m = firstIndex+1;

        // loop invariant:
        // a[1 .. k-1] <= a[firstIndex]
        // a[k .. m-1] > a[firstIndex]
        while (m <= lastIndex) {
            if (counters.compare(a[m], a[firstIndex], comparator) > 0) { }
            else {
                counters.swap(a, k, m);
                ++k;
            }
            ++m;
        }
        counters.swap(a, firstIndex, k-1);
        return k-1;
    }

    /**
     * Tests sort:
     *   Performs sort, tests result, and prints values of counters, preceded 
     *   by header message.
     * @param testName "test name" for header message
     * @param a array to sort
     * @param c comparator, or null to use compareTo
     */
    public static void testSort(String testName, Object[] a, Comparator c) {
        SortResult r = sort(a, c);
        printSortResult(testName, "quicksort", a, c, r);
    }

    // ---- main method ----

    /**
     * Performs simple tests.
     * @param args command-line arguments -- data to sort, or none to use
     *   default data
    */
    public static void main(String[] args) {

        if (args.length < 1) {
        // Make some test data.
            args = new String[] {
                "hello", "bye", "HELLO", "abcd", "100", "!@#$" 
            };
        }

        // Make a comparator object to compare two Strings, ignoring case.
        Comparator ciCompare = new Comparator() {
            public int compare(Object o1, Object o2) {
                if ((o1 instanceof String) && (o2 instanceof String))
                    return ((String) o1).toUpperCase().compareTo(
                            ((String) o2).toUpperCase());
                else throw new ClassCastException();
            }
        };

        // Make "test names".
        String rName = "strings (regular)";
        String ciName = "strings (case-insensitive)";

        // Print test data.
        printArray("Input data:", args);

        // Try out sort.
        String[] copyOfData = (String[]) args.clone();
        System.out.println("");
        testSort(rName, copyOfData, null);
        printArray("Result:", copyOfData);
        testSort(ciName, copyOfData, ciCompare);
        printArray("Result:", copyOfData);
    }
}
