//======================================================================
//  PROJECT:            programming.java, Chapter 8
//  FILE:               Sortmeister.java
//  VERSION:            2.1
//  TARGET:             Java 1.1 and above
//  UPDATE HISTORY:     version 2.1        5/25/99     replaced shell with quick sort
//                      version 2.0        10/9/98     1.1 event model, canvas
//                      version 1.1        6/8/97      tidied up
//                      version 1.0        11/22/96    
//======================================================================

//------------------------------- NOTES --------------------------------
/*
   Instead of drawing directly on the applet frame, we've made a Display
   class, subclassing Canvas, on which all drawing takes place. This
   modification isn't so important here, but later on we might want to
   show two sorting routines working at the same time and it'll turn
   out to be more convenient to have each sort draw in its own Display.
*/
//------------------------------ IMPORTS -------------------------------

import java.applet.*;
import java.awt.*;
import java.awt.event.*;

//========================== Sortmeister CLASS =========================

/**
 * This applet demonstrates two standard sorting algorithms
 * (the Selection and Quick sorts) and displays the data
 * items in graphical form as sorting takes place.
 */
public class Sortmeister extends    Applet
                         implements ActionListener,
                                    ItemListener,
                                    Runnable   // ignore for now
{
    private final int    SIZE = 30;         // number of data values
    private final int    DELAY_MS = 150;    // pause length (miliseconds)
    
    private int          data[];            // the data to be sorted
    private Button       reset,             // to reset the data
                         sort;              // to perform the sort
    private Choice       sortChoice;        // to select the algorithm
    private String       sortName;          // name of the chosen algorithm
    private Display      display;           // where we do the drawing
    private Thread       runner = null;     // pay no attention to this
    
    public void init() 
    {
        setLayout(new BorderLayout());
        
        data = new int[SIZE];         // Create an array of the desired SIZE
        
        // Lay the controls out in their own panel
        Panel p = new Panel();
        p.setBackground(new Color(212, 212, 255));
        reset = new Button("Reset");
        sort = new Button("Sort");
        sortChoice = new Choice();
        sortChoice.addItem("Selection");
        sortChoice.addItem("Quick");
        p.add(reset);
        p.add(sort);
        p.add(sortChoice);
        add(BorderLayout.SOUTH, p);
        
        // Create the display Canvas and add it to the applet
        display = new Display(data);
        display.setBackground(new Color(255, 255, 212));
        add("Center", display);
        
        // Register the buttons and the Choice object
        sortChoice.addItemListener(this);
        reset.addActionListener(this);
        sort.addActionListener(this);
        
        // Set the default sort method and initialize the data
        sortName = "Selection";
        shuffle(data);
        display.repaint();
    }

    //------------------------- Event handlers -------------------------
    
    /**
     * Respond to a click on the Reset or Sort button.
     */
    public void actionPerformed(ActionEvent e)
    {
        String cmd = e.getActionCommand();
        if (cmd.equals("Reset"))     // Reshuffle the data and display it.
        {
            shuffle(data);
            display.repaint();
        }
        else                         // Do the sorting (Huh?  How?).
        {
            if (runner != null)
            {
                runner.stop();
            }
            runner = new Thread(this);
            runner.start();         // This does some things and then calls run().
        }
    }
    
    /**
     * Set the name of the current sort method to be the one selected
     * in the sort Choice object.
     */
    public void itemStateChanged(ItemEvent e)
    {
        sortName = "" + e.getItem();
    }

    //----------------------- Private utilities ------------------------

    /**
     * Shuffle the array a so that it contains a random reordering
     * of the numbers 1...a.length.
     */
    private void shuffle(int[] a)
    {
        for (int i = 0; i < a.length; i++)
        {
            a[i] = i + 1;
        }
        for (int i = 0; i < a.length - 1; i++)
        {
            // Pick a random index in the range i ... a.length - 1.
            int index = i + (int)(Math.random() * (a.length - i));
            
            // Swap a[i] and a[index].
            int temp = a[i];
            a[i] = a[index];
            a[index] = temp;
        }
    }
    
    /**
     * Do the sorting by calling the chosen sorting routine.
     */
    private void doSort()
    {
        if (sortName.equals("Selection"))
        { 
            selectionSort(data);
        }
        else if (sortName.equals("Quick"))
        { 
            quickSort(data, 0, data.length - 1);
        }
    }

    //------------------------ Sorting methods -------------------------

    /**
     * Perform a Selection Sort on the argument array.
     */
    private void selectionSort(int[] a)
    {
        for (int i = 0; i < a.length - 1; i++)
        {
            int minPos = i;
            
            // Find the smallest element in the range i ... a.length - 1.
            for (int j = i + 1; j < a.length; j++)
            {
                if (a[j] < a[minPos]) 
                    minPos = j;
            
                stall(i, j);
            }
            // Swap current element with the smallest element.
            int temp = a[i];                            
            a[i] = a[minPos];
            a[minPos] = temp;
            
            stall(i, minPos);
        }
        stall(-1, -1);
    }
    
    /**
     * Perform a QuickSort on the  argument array.
     */
    private void quickSort(int[] a, int start, int end)
    {
        if (start < end)
        {
            // Rearrange the array into two subranges
            int split = partition(a, start, end);
            
            // Sort each subrange.
            quickSort(a, start, split);
            quickSort(a, split + 1, end);
        }
        stall(-1, -1);
    }
    
    /**
     * Helper routine for quickSort().  Rearrange a[start]...
     * a[end] so that all elements a[start]...a[top] are
     * less than or equal to all elements a[top+1]...a[end],
     * and then return top.
     */
    private int partition(int[] a, int start, int end)
    {
        int bottom = start - 1,
            top = end + 1;
        int pivot = a[start];
        
        while (bottom < top)
        {
            do
            {
                bottom++;
                stall(start, bottom);
            } while (a[bottom] < pivot);
            do
            {
                top--;
                stall(start, top);
            } while (a[top] > pivot);
            
            int temp = a[top];
            a[top] = a[bottom];
            a[bottom] = temp;
            
            stall(top, bottom);
        }
        int temp = a[top];
        a[top] = a[bottom];
        a[bottom] = temp;
        
        stall(top, bottom);
        
        return top;
    }
    
    //--------------- Thread utilities (Ch.11 material) ----------------
    
    /**
     * Redraw the display and pause the sorting
     */
    private void stall(int i, int j)
    {
        display.draw(i, j);
        try
        {
            Thread.sleep(DELAY_MS);
        }
        catch(InterruptedException e)
        { }
    }
    
    /**
     * If you're really curious about what's going on here,
     * check out the material on threads in Ch. 11.
     */
    public void run()
    {
        doSort();
    }   
}

//=========================== Display CLASS ============================

/**
 * A Display object handles the graphical representation of the data in
 * an array of ints. The array elements are represented by a line segment
 * with length proportional to the value of the data, with data[0] at
 * the top and data[length - 1] at the bottom. When the display is to
 * be redrawn by calling draw(i, j), the i-th value is displayed in 
 * red and the j-th value is drawn in green.
 */
class Display extends Canvas
{
    private final int INSET = 10;    // size of the left and right margins
    
    private int[]     data;          // the data to be displayed
    private int       swapIndex,     // index of blue line
                      compareIndex;  // index of the green line
    
    /**
     * Construct a new Display object, by getting a reference to
     * the data array to be used.
     */
    public Display(int[] a)
    {
        data = a;
    }
    
    /**
     * Do the redrawing.
     */
    public void draw(int i, int j)
    {
        swapIndex = i;
        compareIndex = j;
        repaint();
    }
    
    public void update(Graphics g)
    {
        paint(g);
    }
    
    public void paint(Graphics g)
    {
        // Compute the y distance between lines and the x distance
        // corresponding to one unit of data.
        
        Dimension size = getSize();
        int unitY = size.height / data.length;
        double unitX = (size.width - 2.0 * INSET) / data.length;
        
        // Draw all the lines.
        
        for (int i = 0; i < data.length; i++)
        {
            int y = unitY / 2 + i * unitY;
            // Erase the old i-th line.
            g.setColor(getBackground());
            g.drawLine(INSET, y, INSET + (int)(unitX * data.length), y);
                        
            // Draw the new i-th line.
            if (i == swapIndex)
            {
                g.setColor(Color.red);
            }
            else if (i == compareIndex)
            {
                g.setColor(Color.green);
            }
            else
            {
                g.setColor(Color.black);
            }
            g.drawLine(INSET, y, INSET + (int)(unitX * data[i]), y);      
        }
    }
}
