import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTextField;

import objectdraw.ActiveObject;
import objectdraw.Location;
import objectdraw.WindowController;

/*
 * Class to demonstrate visually how searching and sorting algorithms work.
 */
//@width 850
//@height 600
public class SortDemo extends WindowController implements ActionListener {
  // Font to make the labels big
  private static final Font BIG_FONT = new Font("Time", Font.PLAIN, 20);

  // Used to control the speed of the animation
  private JSlider pauseTime;

  // The array being sorted
  private Array a;

  // Buttons used to select which sorting or searching algorithm to apply
  private JButton sort, random, randomSorted, binSearch, linSearch, sortLinSearch, mergeSort, quickSort;

  // Components for entering the number to search for
  private JTextField numField;
  private JLabel numFieldLabel;

  // Component to enter size of array to use
  private JTextField sizeField;

  // Shows value of current bar
  private JTextField valueLabel;

  // Shows statistics about the search
  private JTextField timeLabel;

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

    // Create the fields to show the value of a bar
    // and statistics about a search.
    JPanel pp = new JPanel(); 
    valueLabel = new JTextField("            ", 10);
    valueLabel.setEditable(false);
    valueLabel.setFont(BIG_FONT);
    timeLabel = new JTextField("       ", 32);
    timeLabel.setEditable(false);
    timeLabel.setFont(BIG_FONT);
    pp.add(valueLabel);
    pp.add(timeLabel);
    contentPane.add(pp, BorderLayout.NORTH);

    // Create components used to control searching and sorting
    JPanel grid3 = new JPanel(new GridLayout(3, 1));
    JPanel p = new JPanel();
    random = new JButton("Create New Array");
    randomSorted = new JButton ("Sort");
    sort = new JButton("Selection Sort");
    linSearch = new JButton("Linear search");
    sortLinSearch = new JButton("Sorted linear search");
    binSearch = new JButton("Binary search");
    mergeSort = new JButton("Merge sort");
    quickSort = new JButton("Quick sort");
    numFieldLabel = new JLabel("Search:");
    numField = new JTextField("", 5);
    JLabel sizeFieldLabel = new JLabel("Array Size: ");
    sizeField = new JTextField("8", 5);

    p.add(random);
    p.add(randomSorted);
    p.add(sizeFieldLabel);
    p.add(sizeField);
    p.add(sort);
    p.add(mergeSort);
//    p.add(quickSort);
    grid3.add(p);

    p = new JPanel();
    p.add(linSearch);
    // p.add(sortLinSearch);
    p.add(binSearch);
    p.add(numFieldLabel);
    p.add(numField);
    grid3.add(p);

    p = new JPanel();
    p.add(new JLabel("Speed: ", JLabel.RIGHT));

    // The slider is constructed with negative numbers because the value
    // is used to represent a length of time to pause.  By using negative
    // numbers, we negate the value before a pause.  This gives the result
    // that when the bubble is toward the left it means slower and when
    // it is toward the right it means faster, which seems more intuitive
    // than the other way around.
    p.add(pauseTime = new JSlider(JSlider.HORIZONTAL, -1000, 0, -100));
    grid3.add(p);

    contentPane.add(grid3, BorderLayout.SOUTH);

    random.addActionListener(this);
    randomSorted.addActionListener (this);
    sort.addActionListener(this);
    linSearch.addActionListener(this);
    sortLinSearch.addActionListener(this);
    binSearch.addActionListener(this);
    mergeSort.addActionListener(this);
    quickSort.addActionListener(this);

    validate();
    newArray();
  }

  // Create an array which is displayed as vertical rectangles
  // to be sorted.
  private void newArray() {
    a =
    new Array(
      getNumField(sizeField),
      canvas);
  }

  // Create an array which is displayed as vertical rectangles
  // to be sorted.
  private void newSortedArray() {
    a =
    new Array(
      getNumField(sizeField),
      canvas,
      true);
  }

  // Reads a value from a text field and converts
  // it to an integer.  Returns 0 if the user enters
  // a non=integer.
  public int getNumField(JTextField f) {
    try {
      return Integer.parseInt(f.getText());
    } catch (Exception e) {
      f.setText("0");
      return 0;
    }
  }

  // Handles button clicks.
  public void actionPerformed(final ActionEvent e) {
    a.clear();
    Object button = e.getSource();
    if (button == sort) {
      sort (new SelectionSort (a, pauseTime));
    }
    else if (button == mergeSort) {
      sort (new MergeSort (a, pauseTime));
      //      sort (new MergeSort (a, null));
    }
    else if (button == quickSort) {
      sort (new QuickSort (a, pauseTime));
      //      sort (new MergeSort (a, null));
    }
    else if (button == linSearch) {
      find (new LinearSearch(a, pauseTime));
    }
    else if (button == binSearch) {
      find (new BinarySearch (a, pauseTime));
    }
    else if (button == sortLinSearch) {
      find (new SortedLinearSearch (a, pauseTime));
    }
    else if (button == random) {
      a.removeFromCanvas();
      newArray();
    }
    else if (button == randomSorted) {
      sort(new MergeSort(a, null));
      //      a.removeFromCanvas();
      //      newSortedArray();
    }
  }

  /*
   * Searches for the requested number using the algorithm selected
   * by the button click.  Times how long it takes and reports
   * the statistics.
   */
  private void find(final SearchAlgorithm searcher) {
    new Thread() {
      public void run() {
        long time = System.currentTimeMillis();
        int index = searcher.find(getNumField(numField));
        String message;
        if (index != -1) {
          a.markFound(index);
          message =
          "Found "
          + numField.getText()
          + " at index "
          + index
          + " in "
          + searcher.getSteps()
          + " steps ("
          + (System.currentTimeMillis() - time)
          + " ms)";
        }
        else {
          message =
          "Not found "
          + numField.getText()
          + " in "
          + searcher.getSteps()
          + " steps ("
          + (System.currentTimeMillis() - time)
          + " ms)";

        }
        timeLabel.setText(message);
      }
    }.start();
  }

  /*
   * Sorts the array using the sorter selected by the last mouse
   * click.  Times how long it takes and reports statistics.
   */
  private void sort(final SortAlgorithm sorter) {
    new Thread() {
      public void run() {
        long time = System.currentTimeMillis();
        sorter.sort();
        String message =
          "Sorted in "
          + sorter.getSteps()
          + " steps ("
          + (System.currentTimeMillis() - time)
          + " ms)";
        a.clear();
        timeLabel.setText(message);
      }
    }.start();
  }

  // Display the value of bars as the mouse moves over them
  public void onMouseMove(Location p) {
    int x = a.getValueForPoint(p);
    if (x == -1) {
      valueLabel.setText("");
    } else {
      String s = "value = " + x;
      valueLabel.setText(s);
    }
  }

  public void onMouseDrag(Location p) {
    onMouseMove(p);
  }
}

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

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

  // The number being searched for
  private int num;

  // The slider that controls the speed of the algorithm
  private JSlider pauseTime;

  // The number of elements examined during the search
  private int steps = 0;

  LinearSearch(Array a, JSlider pauseTime) {
    this.a = a;
    this.pauseTime = pauseTime;
  }

  // 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) {
    this.num = num;
    start();
    try {
      // Wait for the run method to complete and then return
      // the index that it found.
      wait();
    } catch (java.lang.InterruptedException e) {
    }
    return index;
  }

  // Performs the search.  As each element is visited, the
  // color of the rectangle changes.  This method is synchronized
  // so that it can call notify to wake up the thread waiting in find.
  public synchronized void run() {
    a.clear();
    linearSearch(num);
    notify();
  }

  // Perform the linear search algorithm, stopping when
  // the value is found or we visit all elements, whichever
  // comes first.
  private void linearSearch(int num) {
    for (int i = 0; i < a.length(); i++) {
      a.visit(i);
      steps++;
      if (pauseTime != null)
        pause(-pauseTime.getValue());
      if (a.getElement(i) == num) {
        index = i;
        return;
      }
    }
  }

  // Return the number of steps that it took to find the item.
  public int getSteps() {
    return steps;
  }

}

// Class encapsulating the linear search algorithm assuming
// the array is sorted.
// This is an active object so it can animate the search.
class SortedLinearSearch extends ActiveObject implements SearchAlgorithm {
  // The array being searched
  private Array a;

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

  // The number being searched for
  private int num;

  // The slider that controls the speed of the algorithm
  private JSlider pauseTime;

  // The number of elements examined during the search
  private int steps = 0;

  SortedLinearSearch(Array a, JSlider pauseTime) {
    this.a = a;
    this.pauseTime = pauseTime;
  }

  // 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.
  synchronized public int find(int num) {
    this.num = num;
    start();
    try {
      wait();
    } catch (java.lang.InterruptedException e) {
    }
    return index;
  }

  // Performs the search.  As each element is visited, the
  // color of the rectangle changes.  This method is synchronized
  // so that it can call notify to wake up the thread waiting in find.
  synchronized public void run() {
    a.clear();
    linearSearchSortedArray();
    notify();
  }

  // Perform a linear search on a sorted array.  Stop
  // when the element is found, we visit all elements, or
  // we find an element larger than the one we are looking for.
  private void linearSearchSortedArray() {
    for (int i = 0; i < a.length() && a.getElement(i) <= num; i++) {
      steps++;
      a.visit(i);
      if (pauseTime != null)
        pause(-pauseTime.getValue());
      if (a.getElement(i) == num) {
        index = i;
        return;
      }
    }
  }

  // Return the number of steps that it took to find the item.
  public int getSteps() {
    return steps;
  }
}

// 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 extends ActiveObject implements SearchAlgorithm {
  // The array being searched
  private Array a;

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

  // The number being searched for
  private int num;

  // The slider that controls the speed of the algorithm
  private JSlider pauseTime;

  // The number of elements examined during the search
  private int steps = 0;

  BinarySearch(Array a, JSlider pauseTime) {
    this.a = a;
    this.pauseTime = pauseTime;
  }

  // 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) {
    this.num = num;
    start();
    try {
      wait();
    } catch (java.lang.InterruptedException e) {
    }
    return index;
  }

  // Performs the search.  As each element is visited, the
  // color of the rectangle changes.  This method is synchronized
  // so that it can call notify to wake up the thread waiting in find.
  public synchronized void run() {
    a.clear();
    index = binSearch(0, a.length() - 1, num);
    notify();
  }

  // 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 (pauseTime != null) {
      pause(-pauseTime.getValue());
    }

    a.visit(mid);
    steps++;
    if (a.getElement(mid) == num) {
      // Found it
      return mid;
    }
    if (a.getElement(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);
    }
  }

  // Return the number of steps that it took to find the item.
  public int getSteps() {
    return steps;
  }

}

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

  // The slider that controls the speed of the algorithm
  private JSlider pauseTime;

  // The number of elements examined during the search
  private int steps = 0;

  SelectionSort(Array a, JSlider pauseTime) {
    this.a = a;
    this.pauseTime = pauseTime;
  }

  // Starts the active object that sorts the array 
  // while animating it.
  public synchronized void sort() {
    start();
    try {
      this.wait();
    } catch (InterruptedException e) {
    }
  }

  // Animates the sorting of the array
  public synchronized void run() {
    selectionSort();
    this.notify();
  }

  // Performs the selection sort algorithm.  It finds the
  // smallest value in the unsorted part of the array and moves
  // it to the end of the sorted part of the array.  It does
  // this repeatedly until the entire array is sorted.
  private void selectionSort() {
    for (int i = 0; i < a.length(); i++) {
      int smallest = this.findSmallest(i);
      a.swap(i, smallest);
      a.visit(i);
    }
  }

  // Return the smallest element at index start or higher
  private int findSmallest(int start) {
    int smallest = start;
    for (int i = start; i < a.length(); i++) {
      steps++;
      a.markFound(i);
      if (a.getElement(i) < a.getElement(smallest)) {
        smallest = i;
      }
      if (pauseTime != null)
        pause(-pauseTime.getValue());
      a.clear(i, i);
    }
    return smallest;
  }

  // Return the number of steps that it took to sort thte array.
  public int getSteps() {
    return steps;
  }

}

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

  // The slider that controls the speed of the algorithm
  private JSlider pauseTime;

  // The number of elements examined during the search
  private int steps = 0;

  int temp[];

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

  // Starts the active object that sorts the array 
  // while animating it.
  public synchronized void sort() {
    start();
    try {
      this.wait();
    } catch (InterruptedException e) {
    }
  }

  // Animates the sorting of the array
  public synchronized void run() {
    mergeSort(0, a.length() - 1);
    this.notify();
  }

  // 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.getElement(i);
    }
    a.visit(start, end);

    // Merge the sorted arrays together to form one sorted array.
    merge(start, middle, end);
    if (pauseTime != null)
      pause(-pauseTime.getValue());
    a.clear(start, 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.setElement(target, temp[indexRight]);
        indexRight++;
      } else if (indexRight > endRight) {
        // Used up all the elements in right portion
        a.setElement(target, temp[indexLeft]);
        indexLeft++;
      } else if (temp[indexLeft] < temp[indexRight]) {
        // Left value is less.  Use it.
        a.setElement(target, temp[indexLeft]);
        indexLeft++;
      } else {
        // Right value is less.  Use it.
        a.setElement(target, temp[indexRight]);
        indexRight++;
      }
      if (pauseTime != null && pauseTime.getValue() != 0)
        pause(-pauseTime.getValue());
      steps++;
    }
  }

  // Return the number of steps that it took to sort thte array.
  public int getSteps() {
    return steps;

  }
}

class QuickSort extends ActiveObject implements SortAlgorithm {
  private Array a;
  private JSlider pauseTime;
  private int steps = 0;

  int temp[];

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

  public synchronized void sort() {
    start();
    try {
      this.wait();
    } catch (InterruptedException e) {
    }
  }

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

  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 int partition(int left, int right)
  // pre: left <= right
  // post: data[left] placed in the correct (returned) location
  {
    //        a.visit(left, right);
    a.markFound(left);
    a.markFound(right);
    try {
      while (true)
      {
        // move right "pointer" toward left
        while (left < right && a.getElement(left) < a.getElement(right)) {
          steps++;
          a.visit(right);
          right--;
          a.markFound(right);
          if (pauseTime != null && pauseTime.getValue() != 0)
            pause(-pauseTime.getValue());
        }            
        if (left < right) {
          a.visit(left);
          left++;
          a.markFound(left);
          a.swap(left-1,right);
          if (pauseTime != null && pauseTime.getValue() != 0)
            pause(-pauseTime.getValue());
        } else {
          return left;
        }

        // move left pointer toward right
        while (left < right && a.getElement(left) < a.getElement(right)) {
          steps++;
          a.visit(left);
          left++;
          a.markFound(left);
          if (pauseTime != null && pauseTime.getValue() != 0)
            pause(-pauseTime.getValue());
        }
        if (left < right) {
          a.visit(right);
          right--;
          a.markFound(right);
          a.swap(left,right+1);
          if (pauseTime != null && pauseTime.getValue() != 0)
            pause(-pauseTime.getValue());
        } else { 
          return right;
        }
      }
    }finally{a.clear();}
  }

  // Return the number of steps that it took to sort thte array.
  public int getSteps() {
    return steps;
  }
}
