import java.util.Comparator;

/**
 * Class for performing "instrumented" sorts and searches (where we count how 
 *   many comparisons and swaps were needed).  This class provides convenience
 *   methods; subclasses implement various sort/search algorithms.
 * (See source code at <a href="../source/Sorter.java">Sorter.java</a>.)
 */
public class Sorter {

    // ---- methods ----

    /**
     * Prints array, preceded by header message.
     * @param msg header message
     * @param a array
     */
    public static void printArray(String msg, Object[] a) {
        System.out.println(msg);
        for (int i = 0; i < a.length; ++i) 
            System.out.println("  " + a[i]);
    }

    /**
     * Tests whether array is sorted.
     * @param a array
     * @param c comparator, or null to use arrays's compareTo method (in which 
     *   case a should be Comparable)
     * @return true if elements of a are in non-descending order, false
     *   otherwise
     */
    public static boolean isSorted(Object[] a, Comparator c) {
        for (int i = 0; i < a.length-1; ++i) {
            if (c == null) {
                if (((Comparable) a[i]).compareTo((Comparable) a[i+1]) > 0)
                    return false;
            }
            else {
                if (c.compare(a[i], a[i+1]) > 0)
                    return false;
            }
        }
        return true;
    }

    /**
     * Shows results of testing sort:
     *   Tests result and prints values of counters, preceded by header message.
     * @param testName "test name" for header message
     * @param sortName "sort name" for header message
     * @param a array (presumably sorted)
     * @param c comparator, or null to use compareTo
     * @param r result of performing sort
     */
    public static void printSortResult(String testName, String sortName,
            Object[] a, Comparator c, SortResult r) {
        printSortResult(testName, sortName, a, c, r, false);
    }

    /**
     * Shows results of testing sort:
     *   Tests result and prints values of counters, preceded by header message.
     * @param testName "test name" for header message
     * @param sortName "sort name" for header message
     * @param a array (presumably sorted)
     * @param c comparator, or null to use compareTo
     * @param r result of performing sort
     * @param copyNotSwap whether sort performed copies (true) or swaps (false)
     */
    public static void printSortResult(String testName, String sortName,
            Object[] a, Comparator c, SortResult r, boolean copyNotSwap) {
        System.out.println("Testing " + sortName + " for " + testName + ":");
        System.out.println("  Sort succeeded?  " + isSorted(a, c));
        String copyOrSwap = copyNotSwap ? " copies" : " swaps";
        System.out.println("  Sorting " + a.length + " elements required " + 
                r.comparisons + " comparisons and " + r.swaps + copyOrSwap);
    }


    /**
     * Shows results of testing search:
     *   Tests result and prints values of counters, preceded by header message.
     * @param testName "test name" for header message
     * @param searchName "sort name" for header message
     * @param a array being searched 
     * @param x element being searched for
     * @param r result of performing search
     */
    public static void printSearchResult(String testName, String searchName,
            Object[] a, Object x, SearchResult r) {
        System.out.println("Testing " + searchName + " for " + testName + ":");
        System.out.println("  Element '" + x + "' found?  " + r.found);
        System.out.println("  Searching " + a.length + " elements required " + 
                r.comparisons + " comparisons");
    }

    // ---- classes ----

    /** 
     * Class to hold counter values resulting from a sort.  
     *   This class is immutable and intended to be used only to return values
     *   from sort methods.
     */
    public static class SortResult {

        /**
         * Constructs a SortResult object with specified values.
         */
        public SortResult(int comparisons, int swaps) {
            this.comparisons = comparisons;
            this.swaps = swaps;
        }
        public final int comparisons;
        public final int swaps;
    }

    /** 
     * Class to hold counter and "found" values resulting from a search.  
     *   This class is immutable and intended to be used only to return values
     *   from search methods.
     */
    public static class SearchResult {

        /**
         * Constructs a SearchResult object with specified values.
         */
        public SearchResult(int comparisons, boolean found) {
            this.comparisons = comparisons;
            this.found = found;
        }

        public final int comparisons;
        public final boolean found;
    }

    /** 
     * Class to hold counter values during sort/search.  
     */
    protected static class Counters {

        /**
         * Returns value of "number of comparisons" counter.
         */
        public int numCompares() {
            return comparisons;
        }

        /**
         * Returns value of "number of swaps" counter.
         */
        public int numSwaps() {
            return swaps;
        }

        /**
         * Compares two objects, incrementing the "number of comparisons" 
         *   counter.
         * @param x first object
         * @param y second  object
         * @param c comparator containing compare method to use, or null to use
         *   objects' compareTo method (in which case x and y should be
         *   Comparable)
         * @return result of comparing x and y -- negative if x less than y,
         *   positive if x greater than y, or zero if x and y equal
         */
        public int compare(Object x, Object y, Comparator c) {
            ++comparisons;
            if (c != null)
                return c.compare(x, y);
            else
                return ((Comparable) x).compareTo((Comparable) y);
        }

        /**
         * Swaps two array elements, incrementing the "number of swaps" counter.
         * @param a array
         * @param i first index
         * @param j second index
         */
        public void swap(Object[] a, int i, int j) {
            ++swaps;
            Object temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }

        // ---- variables ----

        private int comparisons = 0;
        private int swaps = 0;
    }
}
