import java.util.Arrays;
public class MoreRecursionExamples {

    private static String intArrayToString( int[] a ) {
        String ret = "[";
        if (a.length >= 1)
            ret += Integer.toString(a[0]);
        for (int i = 1; i < a.length; i++)
            ret += "," + Integer.toString(a[i]);
        ret += "]";
        return ret;
    }
    
    private static String makeSpaces(int n) {
        String ret = "";
        for (int i = 0; i < n; i++) 
            ret += " ";
        return ret;
    }
    
    private static void swapIntegers( int[] a, int i, int j ) {
        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }

    private static boolean binarySearch( int[] a, int start, int stop, int goal ) {
        if (start == stop)
            return a[start] == goal;
        int halfway = (stop+start)/2;
        if (a[halfway] == goal)
            return true;
        if (a[halfway] > goal)
            return binarySearch( a, start, halfway, goal );
        else
            return binarySearch( a, halfway+1, stop, goal );
    }    

    public static boolean binarySearch( int[] a, int goal ) {
        if (a.length == 0)
            return false;
        return binarySearch( a, 0, a.length-1, goal );
    }

    // merge and make a new array.
    public static int[] merge( int[] a, int[] b ) {
        //System.out.println( "merging " + intArrayToString(a) + " and " + intArrayToString(b) );
        int[] merged = new int[a.length+b.length];
        int ai = 0, bi = 0, mi = 0;
        for (mi = 0; mi < merged.length; mi++) {
            if (ai == a.length || bi == b.length)
                break;
            if (a[ai] <= b[bi]) {
                merged[mi] = a[ai];
                ai++;
            }
            else {
                merged[mi] = b[bi];
                bi++;
            }
        }
        // take care of the left-overs
        if (ai < a.length) {
            for (; mi < merged.length; mi++) {
                merged[mi] = a[ai];
                ai++;
            }
        }
        else if (bi < b.length) {
            for (; mi < merged.length; mi++) {
                merged[mi] = b[bi];
                bi++;
            }
        }
        return merged;
    }
    
    // merge a[start] .. a[mid]
    //  with a[mid+1] .. a[end]
    public static void merge( int[] a, int start, int mid, int end ) {
        int[] merged = merge( Arrays.copyOfRange( a, start, mid+1 ),
                              Arrays.copyOfRange( a, mid+1, end+1 ) );
        System.arraycopy( merged, 0, a, start, merged.length );
    }
    
    // merge a[start] .. a[mid]
    //  with a[mid+1] .. a[end]
    public static void annotatedMerge( int[] a, int start, int mid, int end, int level ) {
        int[] left = Arrays.copyOfRange( a, start, mid+1 );
        int[] right = Arrays.copyOfRange( a, mid+1, end+1 );
        int[] merged = merge( left, right );
        System.out.println( makeSpaces(level) + intArrayToString(left) + intArrayToString(right) + " ---> " + intArrayToString(merged) );
        System.arraycopy( merged, 0, a, start, merged.length );
    }

    // sorts (in place) a[start]..a[end]
    private static void mergeSort(int[] a, int start, int end) {
        if (start == end)
            return;
            
        mergeSort(a, start, (start+end)/2 );
        mergeSort(a, (start+end)/2+1, end );
        merge( a, start, (start+end)/2, end );
    }

    //merges (in place) a[start]..a[end]
    public static void mergeSort(int[] a) {
        mergeSort(a, 0, a.length-1);
    }
    
    //merges (in place) a[start]..a[end]
    private static void annotatedMergeSort(int[] a, int start, int end, int level) {
 //       System.out.println( makeSpaces(level) + "mergeSort( a, " + start + ", " + end + "), i.e. " + 
   //     intArrayToString(Arrays.) );
                if( start >= end ) return;
    
        annotatedMergeSort(a, start, (start+end)/2, level+1);
        annotatedMergeSort(a, (start+end)/2+1, end, level+1);
        annotatedMerge(a, start, (start+end)/2, end, level);
    }
    
    public static void annotatedMergeSort(int[] a) {
        annotatedMergeSort(a,0,a.length-1,0);
    }

    public static void quickSort(int[] a) {
        quickSort(a, 0, a.length - 1);
    }
    
    // sorts a[start] to a[end] in place
    private static void quickSort(int[] a, int start, int end) {
        int pivot = a[(start+end)/2];
        int i = start;
        int j = end;
        do {
          while (a[i] < pivot)
            i++;
          while (pivot < a[j])
            j--;
          if (i <= j) {   //swap a[i] and a[j]
            swapIntegers(a, i, j);
            i++;
            j--;
          }
        } while (i <= j);
        if (start < j)
          quickSort(a, start, j);
        if (i < end)
          quickSort(a, i, end);
    }
    
    public static void annotatedQuickSort(int[] a) {
        annotatedQuickSort(a, 0, a.length - 1,0);
    }
    
    // sorts a[start] to a[end] in place
    private static void annotatedQuickSort(int[] a, int start, int end, int level) {
        int pivot = a[(start+end)/2];
        System.out.println( makeSpaces(level) + "Qsort, start = " + start + ", end = " + end + " array is " + intArrayToString(Arrays.copyOfRange(a,start,end+1)) + ", pivot value = " + pivot );
        int i = start;
        int j = end;
        do {
          while (a[i] < pivot)
            i++; // skip over values already in the right half of the list
          while (pivot < a[j])
            j--; // skip over values already in the right half of the list
          if (i <= j) {   //swap a[i] and a[j]
            // We have found a pair that are out of place, if we swap them, then
            // both will be in the correct "halves" of the list
            swapIntegers(a, i, j);
            i++;
            j--;
          }
        } while (i <= j); // once the indices cross, we know we have examined every item in the list
        if (start < j)
          annotatedQuickSort(a, start, j, level + 1);
        if (i < end)
          annotatedQuickSort(a, i, end, level + 1);
    }
    
    public static void testBinarySearch(  ) {
        int[] a = {1,2,3,4,5}; // odd number of elems, goal in middle
        int[] b = {1,3,4,5}; // even number of elems
        int[] c = {3,4,5,6,7}; // odd number of elems, goal in beginning
        int[] d = {3,4,5,6,7,8}; // even number of elems, goal in beginning
        int[] e = {-1,0,1,2,3}; // odd number of elems, goal at end
        int[] f = {0,1,2,3}; // even number of elems, goal at end
        int[] g = {1,2,4,5,6}; // odd number of elems, goal missing
        int[] h = {1,2,4,5}; // even number of elems, goal missing
        int[] i = {}; // empty list
        System.out.println( "binarySearch(a,3) returns " + binarySearch(a,3) );
        System.out.println( "binarySearch(b,3) returns " + binarySearch(b,3) );
        System.out.println( "binarySearch(c,3) returns " + binarySearch(c,3) );
        System.out.println( "binarySearch(d,3) returns " + binarySearch(d,3) );
        System.out.println( "binarySearch(e,3) returns " + binarySearch(e,3) );
        System.out.println( "binarySearch(f,3) returns " + binarySearch(f,3) );
        System.out.println( "binarySearch(g,3) returns " + binarySearch(g,3) );
        System.out.println( "binarySearch(h,3) returns " + binarySearch(h,3) );
        System.out.println( "binarySearch(i,3) returns " + binarySearch(i,3) ); 
    }  
    
    
    public static void testMergeSort() {
//         int[] a1 = {1,3,5};
//         int[] b1 = {2,4,6};
//         int[] c1 = merge( a1, b1 );
//         System.out.println( "testing merge v1" );
//         System.out.println( intArrayToString(c1) );
//         
//         int[] d1 = {1,3,5,2,4,6};
//         merge( d1, 0, 2, 5 );
//         System.out.println( "testing merge v2" );
//         System.out.println( intArrayToString(d1) );
//         
//     
//         System.out.println( "testing merge sort" );
//         int[] a = {4,1,5,2};
//         System.out.print( intArrayToString(a) + "--->" );
//         mergeSort(a);
//         System.out.println( intArrayToString(a) );
//         
        System.out.println( "Merge sort" );
        int[] b = {4,1,5,2};
        System.out.println( "At beginning: " + intArrayToString(b) );
        annotatedMergeSort(b);
        System.out.println( "At end: " + intArrayToString(b) );
    }   
    
    public static void testQuickSort() {
        System.out.println( "Quick sort" );
        int[] b = {4,1,5,2,-3,6};
        System.out.println( "At beginning: " + intArrayToString(b) );
        annotatedQuickSort(b);
        System.out.println( "At end: " + intArrayToString(b) );
    }   
       
    
    public static void main( String[] args ) {
        //testBinarySearch();
        //testMergeSort();
        testQuickSort();
    }
}