import java.util.Comparator;

/**
 * Class for performing "instrumented" binary search.
 * (See source code at <a href="../source/BinarySearch.java">
 *   BinarySearch.java</a>.)
 */
public class BinarySearch extends Sorter {

    // ---- methods ----

    /**
     * Searches an array for an element.
     * @param a array to search (assumed to be sorted)
     * @param x element to search for
     * @return object containing count of comparison plus search result 
     *   (true if x is an element of a, false otherwise)
     */
    public static SearchResult search(Comparable[] a, Comparable x) {
        return search(a, x, null);
    }

    /**
     * Searches an array for an element.
     * @param a array to search (assumed to be sorted)
     * @param x element to search for
     * @param c comparator containing compare method to use, or null to use
     *   objects' compareTo method (in which case a and x should be Comparable)
     * @return object containing count of comparison plus search result 
     *   (true if x is an element of a, false otherwise)
     */
    public static SearchResult search(Object[] a, Object x, Comparator c) {
        Counters counters = new Counters();
        boolean found = false;
        int first = 0, last = a.length-1;
        while ((first <= last) && !found) {
            int mid = (first + last) / 2;
            int compareResult = counters.compare(x, a[mid], c);
            if (compareResult < 0)
                last = mid-1;
            else if (compareResult > 0)
                first = mid+1;
            else
                found = true;
        }
        return new SearchResult(counters.numCompares(), found);
    }

    /**
     * Tests search:  
     *   Performs search, prints result, and prints values of counter, preceded
     *   by header message.
     * @param testName "test name" for header message 
     * @param a array to search
     * @param x element to search for
     * @param c comparator, or null to use compareTo
     */
    public static void testSearch(String testName, Object[] a, Object x, 
            Comparator c) {
        SearchResult r = search(a, x, c);
        printSearchResult(testName, "sequential search", a, x, r);
    }

    // ---- main method ----

    /**
     * Performs simple tests.
     * @param args command-line arguments -- data to search, 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 search.
        System.out.println("");
        Quicksort.sort(args, null);
        testSearch(rName, args, args[0], null);
        testSearch(rName, args, args[args.length-1], null);
        testSearch(rName, args, args[args.length/2], null);
        testSearch(rName, args, "junk", null);
        Quicksort.sort(args, ciCompare);
        testSearch(ciName, args, args[0], ciCompare);
        testSearch(ciName, args, args[args.length-1], ciCompare);
        testSearch(ciName, args, args[args.length/2].toLowerCase(), ciCompare);
        testSearch(ciName, args, "junk", ciCompare);
    }
}
