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;

        // 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();
                result.setFont(BIG_FONT);
                lin.setFont(BIG_FONT);
                bin.setFont(BIG_FONT);
                JPanel p = new JPanel(new GridLayout(1, 3));
                p.add(result);
                p.add(lin);
                p.add(bin);
                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");
                                        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 linear = testOne(new SelectionSort(sorted));
                                                sorted = newArray(sizes[i]);
                                                double binary = testOne(new MergeSort(sorted));
                                                lin.append(linear + "\n");
                                                bin.append(binary + "\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++;
                        }
                }
        }

}
