import java.awt.event.*;
import java.awt.*;
import javax.swing.*;
import objectdraw.*;

/*
 * Class to demonstrate visually how searching and sorting algorithms work.
 */
//@width 600
//@height 600
public class SortSearchTimer extends Controller implements ActionListener {

  // Buttons used to select which sorting or searching algorithm to apply
  private JButton search, sort, bsort;

  int a[];

  // Shows statistics about the search
  private JTextArea result;
  private JTextArea lin;
  private JTextArea bin, qin;

  // Font to make the labels big
  private static final Font BIG_FONT = new Font("Times", Font.PLAIN, 24);

  // Create the display
  public void begin() {
    resize(600,600);
    Container contentPane = getContentPane();

    result = new JTextArea();
    lin = new JTextArea();
    bin = new JTextArea();
    qin = new JTextArea();
    result.setFont(BIG_FONT);
    lin.setFont(BIG_FONT);
    bin.setFont(BIG_FONT);
    qin.setFont(BIG_FONT);
    JPanel p = new JPanel(new GridLayout(1, 4));
    p.add(result);
    p.add(lin);
    p.add(bin);
    p.add(qin);
    contentPane.add(p, BorderLayout.CENTER);
    bsort = new JButton("Selection Sort");
    sort = new JButton("Both Sorts");
    search = new JButton("Both Searches");
    p = new JPanel();
    p.add(search);
    p.add(bsort);
    p.add(sort);
    contentPane.add(p, BorderLayout.SOUTH);

    search.addActionListener(this);
    sort.addActionListener(this);
    bsort.addActionListener(this);

    validate();
  }

  int[] newArray(int n) {
    RandomIntGenerator rand = new RandomIntGenerator(0, n);
    int a[] = new int[n];
    for (int i = 0; i < n; i++) {
      a[i] = rand.nextValue();
    }
    return a;
  }

  int[] newSortedArray(int n) {
    int a[] = new int[n];
    for (int i = 0; i < n; i++) {
      a[i] = i;
    }
    return a;
  }

  double testOne(SearchAlgorithm m) {
    double total = 0;
    for (int j = 0; j < 5; j++) {
      double start = System.currentTimeMillis();
      m.find(-1);
      double end = System.currentTimeMillis();
      total += end - start;
    }
    return total / 5;
  }

  double testOne(SortAlgorithm m) {
    double start = System.currentTimeMillis();
    m.sort();
    double end = System.currentTimeMillis();
    return end - start;
  }
  // Handles button clicks.
  public void actionPerformed(final ActionEvent e) {
    if (e.getSource() == search) {
      final int sizes[] =
        {
          125000,
          250000,
          500000,
          1000000,
          2000000,
          4000000,
          8000000,
          16000000, 
          32000000 };
      new ActiveObject() {
        public void run() {
          result.setText("SIZE\n");
          lin.setText("LINEAR (ms)\n");
          bin.setText("BINARY (ms)\n");
          qin.setText("");
          for (int i = 0; i < sizes.length; i++) {
            int[] sorted = newSortedArray(sizes[i]);
            result.append(sizes[i] + "\n");
            double linear = testOne(new LinearSearch(sorted));
            double binary = testOne(new BinarySearch(sorted));
            lin.append(linear + "\n");
            bin.append(binary + "\n");
          }
        }
      }
      .start();
    } else if (e.getSource() == sort) {
      final int sizes[] =
        { 125, 250, 500, 1000, 2000, 4000, 8000, 16000, 32000 };
      new ActiveObject() {
        public void run() {
          result.setText("SIZE\n");
          lin.setText("SELECTION (ms)\n");
          bin.setText("MERGE (ms)\n");
          for (int i = 0; i < sizes.length; i++) {
            int[] sorted = newArray(sizes[i]);
            result.append(sizes[i] + "\n");
            double sel = testOne(new SelectionSort(sorted));
            sorted = newArray(sizes[i]);
            double merge = testOne(new MergeSort(sorted));
            lin.append(sel + "\n");
            bin.append(merge + "\n");
          }
        }
      }
      .start();

    } else {

      final int sizes[] =
        { 125, 250, 500, 1000, 2000, 4000, 8000, 16000, 32000 };
      new ActiveObject() {
        public void run() {
          result.setText("SIZE\n");
          lin.setText("SELECTION (ms)\n");
          bin.setText("");
          for (int i = 0; i < sizes.length; i++) {
            int[] sorted = newArray(sizes[i]);
            result.append(sizes[i] + "\n");
            double linear = testOne(new SelectionSort(sorted));
            sorted = newArray(sizes[i]);
            lin.append(linear + "\n");
          }
        }
      }
      .start();

    }
  }

}

// Class encapsulating the linear search algorithm.
// This is an active object so it can animate the search.
class LinearSearch implements SearchAlgorithm {
  // The array being searched
  private int a[];

  // The index where the element being searched for was found
  private int index = -1;

  // The number being searched for
  private int num;

  LinearSearch(int a[]) {
    this.a = a;
  }

  // Looks for the number in the array, returning where it is found.
  // It is synchronized so that it can wait until the run method 
  // completes before returning the value.
  public synchronized int find(int num) {
    for (int i = 0; i < a.length; i++) {
      if (a[i] == num) {
        return i;
      }
    }
    return -1;
  }
}

// Class encapsulating the binary search algorithm.  This
// only works if the array is sorted.
// This is an active object so it can animate the search.
class BinarySearch implements SearchAlgorithm {
  // The array being searched
  private int a[];

  // The number being searched for
  private int num;

  BinarySearch(int a[]) {
    this.a = a;
  }

  // Looks for the number in the array, returning where it is found.
  // It is synchronized so that it can wait until the run method 
  // completes before returning the value.
  public synchronized int find(int num) {
    return binSearch(0, a.length, num);
  }

  // This is the binary search algorithm.  It looks for the number num
  // at the midpoint of the range between start and end.  It either
  // finds the value it is looking for or else has divided the problem
  // in half by knowing which side to look in.
  private int binSearch(int start, int end, int num) {
    if (start > end) {
      // It's not in the array.
      return -1;
    }

    // Find the mid point
    int mid = (start + end) / 2;

    if (a[mid] == num) {
      // Found it
      return mid;
    }
    if (a[mid] < num) {
      // It's in the top half.  Recurse.
      return binSearch(mid + 1, end, num);
    } else {
      // It's in the bottom half. Recurse.
      return binSearch(start, mid - 1, num);
    }
  }
}

// Class encapsulating the selection sort algorithm.
// This is an active object so it can animate the search.
class SelectionSort implements SortAlgorithm {
  // The array being searched
  private int a[];

  SelectionSort(int a[]) {
    this.a = a;
  }

  public void sort() {
    selSort(a, 0);
  }

  public void selSort(int a[], int start) {
    for (int i = start; i < a.length; i++) {
      int smallestIndex = indexOfSmallest(i);

      int temp = a[i];
      a[i] = a[smallestIndex];
      a[smallestIndex] = temp;
    }
  }

  // Return the smallest element at index start or higher
  private int indexOfSmallest(int start) {
    int smallestIndex = start;
    for (int i = start + 1; i < a.length; i++) {
      if (a[i] < a[smallestIndex]) {
        smallestIndex = i;
      }
    }
    return smallestIndex;
  }
}

// Class encapsulating the merge sort algorithm.
// This is an active object so it can animate the search.
class MergeSort implements SortAlgorithm {
  // The array being searched
  private int a[];
  int temp[];

  MergeSort(int a[]) {
    this.a = a;
    temp = new int[a.length];
  }

  public void sort() {
    mergeSort(0, a.length - 1);
  }

  // Use merge sort to sort the array between indexes start
  // and end.  This algorithm works by sorting the first
  // half of the array independently from the second half,
  // giving two smaller sorted lists.  These sorted lists
  // are then merged into one long list.
  private void mergeSort(int start, int end) {
    if (start >= end) {
      // Done
      return;
    }

    // Find the mid point.  Sort the first half and second
    // half separately
    int middle = (end + start) / 2;
    mergeSort(start, middle);
    mergeSort(middle + 1, end);
    for (int i = start; i <= end; i++) {
      temp[i] = a[i];
    }

    // Merge the sorted arrays together to form one sorted array.
    merge(start, middle, end);
  }

  // Merge the portion of the array starting at left
  // and going to middle 
  // with the portion starting at middle+1 and going to right.
  private void merge(int left, int middle, int right) {
    int indexLeft = left;
    int indexRight = middle + 1;
    int endLeft = middle;
    int endRight = right;
    for (int target = left; target <= endRight; target++) {
      if (indexLeft > endLeft) {
        // Used up all the elements in left portion
        a[target] = temp[indexRight];
        indexRight++;
      } else if (indexRight > endRight) {
        // Used up all the elements in right portion
        a[target] = temp[indexLeft];
        indexLeft++;
      } else if (temp[indexLeft] < temp[indexRight]) {
        // Left value is less.  Use it.
        a[target] = temp[indexLeft];
        indexLeft++;
      } else {
        // Right value is less.  Use it.
        a[target] = temp[indexRight];
        indexRight++;
      }
    }
  }

}

class QuickSort implements SortAlgorithm {
  private int[] a;

  int temp[];

  QuickSort(int[] a) {
    this.a = a;
    temp = new int[a.length];
  }

  public synchronized void sort() {
    qSort(0, a.length - 1);
  }

  private void qSort(int left, int right) {
    int pivot;   // the final location of the leftmost value
    if (left >= right) return;
    pivot = partition(left,right);    /* 1 - place pivot */
    qSort(left,pivot-1); /* 2 - sort small */
    qSort(pivot+1,right);/* 3 - sort large */
  }

  private void swap(int i, int j) {
    int tmp = a[i];
    a[i] = a[j];
    a[j] = tmp;
  }

  private int partition(int left, int right)
  // pre: left <= right
  // post: data[left] placed in the correct (returned) location
  {
    //        a.visit(left, right);
    while (true) {
      // move right "pointer" toward left
      while (left < right && a[left] < a[right]) {
        right--;
      }            
      if (left < right) {
        left++;
        swap(left-1,right);
      } else {
        return left;
      }

      // move left pointer toward right
      while (left < right && a[left] < a[right]) {
        left++;
      }
      if (left < right) {
        right--;
      } else { 
        return right;
      }
    }
  }
}
