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;
        
        // 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");
                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);
                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);

                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 == 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);
                }
        }

}

// 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;
        }

}
