/*
 * @(#)SortItem.java	1.17f 95/04/10 James Gosling
 *
 * Copyright (c) 1994-1995 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for NON-COMMERCIAL or COMMERCIAL purposes and
 * without fee is hereby granted. 
 * Please refer to the file http://java.sun.com/copy_trademarks.html
 * for further important copyright and trademark information and to
 * http://java.sun.com/licensing.html for further important licensing
 * information for the Java (tm) Technology.
 * 
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
 * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 * 
 * THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS ON-LINE
 * CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
 * PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
 * NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
 * SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE
 * SOFTWARE COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE
 * PHYSICAL OR ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES").  SUN
 * SPECIFICALLY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY OF FITNESS FOR
 * HIGH RISK ACTIVITIES.
 */

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


/**
 * A simple applet class to demonstrate a sort algorithm.
 * You can specify a sorting algorithm using the "alg"
 * attribyte. When you click on the applet, a thread is
 * forked which animates the sorting algorithm.
 *
 * @author James Gosling
 * @version 	1.17f, 10 Apr 1995
 *   
 * 27 Nov 2006: (bwbecker@uwaterloo.ca) Changed the overall architecture to
 * use model-view-controller.  Re-examined algorithms to ensure that counting
 * was consistent across all algorithms (the quicksorts used to ignore most
 * of the comparisons in the partitioning, for example).  Added controls to 
 * pause the sort and display counts.
 */
public class SortItem extends JApplet implements IView
{
	/** 
	 * The component displaying the sort algorithm's progress.
	 */
	private SortDisplay sortDisplay;
	private SortAlgorithm model;
	
	private JButton startButton = new JButton("Start");
	private JButton resetButton = new JButton("Reset");
	private JRadioButton dataRandom = new JRadioButton("Random", true);
	private JRadioButton dataInorder = new JRadioButton("Inorder");
	private JRadioButton dataReverse = new JRadioButton("Reverse");
	
	private JLabel compares = new JLabel("0");
	private JLabel movements = new JLabel("0");
	private JLabel other = new JLabel("0");


	/**
	 * Initialize the applet.
	 */
	public void init()
	{
		String at = getParameter("alg");
		if (at == null)
		{
			at = "BubbleSort";
		}

		String algName = at + "Algorithm";
		try
		{
			this.model = (SortAlgorithm) Class.forName(algName)
					.newInstance();
			this.sortDisplay = new SortDisplay(this.model);
			this.layoutView();
			this.registerControllers();
			this.model.addView(this);
		} catch (Exception e)
		{
			throw new Error(e);
		}
	}

	private void layoutView()
	{
		
		ButtonGroup bg = new ButtonGroup();
		bg.add(this.dataRandom);
		bg.add(this.dataInorder);
		bg.add(this.dataReverse);
		JPanel controls = new JPanel(new GridBagLayout());
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.fill = GridBagConstraints.HORIZONTAL;
		gbc.gridx = 0;
		gbc.gridy = GridBagConstraints.RELATIVE;
		gbc.anchor = GridBagConstraints.NORTH;
		gbc.gridwidth = 2;
		controls.add(this.startButton, gbc);
		controls.add(this.resetButton, gbc);
		controls.add(this.dataRandom, gbc);
		controls.add(this.dataInorder, gbc);
		controls.add(this.dataReverse, gbc);
		
		JLabel cLabel = new JLabel("Compare:");
		JLabel mLabel = new JLabel("Moves:");
		JLabel oLabel = new JLabel("Other:");
		cLabel.setHorizontalAlignment(SwingConstants.TRAILING);
		mLabel.setHorizontalAlignment(SwingConstants.TRAILING);
		oLabel.setHorizontalAlignment(SwingConstants.TRAILING);
		this.compares.setHorizontalAlignment(SwingConstants.TRAILING);
		this.movements.setHorizontalAlignment(SwingConstants.TRAILING);
		this.other.setHorizontalAlignment(SwingConstants.TRAILING);
		
		gbc.gridwidth = 1;
		controls.add(cLabel, gbc);
		controls.add(mLabel, gbc);
		controls.add(oLabel, gbc);
		gbc.gridx = 1;
		gbc.gridy = 5;
		controls.add(this.compares, gbc);
		gbc.gridy++;
		controls.add(this.movements, gbc);
		gbc.gridy++;
		controls.add(this.other, gbc);
		
		Dimension d = this.startButton.getPreferredSize();
		d.width = d.width + d.width/2;
		this.startButton.setMinimumSize(d);
		this.startButton.setPreferredSize(d);
		
		
		JPanel p = new JPanel(new BorderLayout());
		p.add(this.sortDisplay, BorderLayout.CENTER);
		p.add(controls, BorderLayout.EAST);
		this.setContentPane(p);
	}
	
	/** Update the view with the most recent information from the model.
	 * @param totalOps The total number of comparisons, movements, and
	 * other array operations since the last update.  Used to control the
	 * length of the pause in the display. */
	private SortState updateViewPrevState = null;
	public void updateView(int totalOps)
	{
		this.compares.setText(""+this.model.getTotalCompares());
		this.movements.setText(""+this.model.getTotalMoves());
		this.other.setText(""+this.model.getTotalOther());
		
		if (this.updateViewPrevState != this.sortDisplay.state)
		{
			switch(this.sortDisplay.state)
			{
			case RUNNING:
				this.startButton.setText("Pause");
				break;
				
			case PAUSED:
				this.startButton.setText("Resume");
				break;
			
			case STOPPED:
				this.startButton.setText("Start");
				break;
			}
			this.updateViewPrevState = this.sortDisplay.state;
		}
	}
	
	private void registerControllers()
	{
		this.startButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e)
			{
				switch (sortDisplay.state)
				{
				case RUNNING:
					sortDisplay.pauseSort();
					break;
					
				case PAUSED:
					sortDisplay.resumeSort();
					break;
				
				case STOPPED:
					sortDisplay.startSort();
					break;
				}
				updateView(0);
			}
		});
		this.resetButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e)
			{
				sortDisplay.stopSort();
				updateView(0);
			}
		});
		this.dataInorder.addActionListener(new DataOrderController(DataOrderEnum.INORDER));
		this.dataRandom.addActionListener(new DataOrderController(DataOrderEnum.RANDOM));
		this.dataReverse.addActionListener(new DataOrderController(DataOrderEnum.REVERSE_ORDER));
	}
	
	/**
	 * Stop the applet. Kill any sorting algorithm that
	 * is still sorting.
	 */
	public void stop()
	{
		this.sortDisplay.stopSort();
	}

	
	private class DataOrderController implements ActionListener
	{
		private DataOrderEnum order;
		
		public DataOrderController(DataOrderEnum o)
		{
			this.order = o;
		}
		public void actionPerformed(ActionEvent e)
		{
			sortDisplay.setDataOrder(this.order);
			updateView(0);
		}
	}
}

enum DataOrderEnum
{
	RANDOM, INORDER, REVERSE_ORDER;
}

enum SortState
{
	STOPPED, PAUSED, RUNNING;
}

class SortDisplay extends JComponent implements Runnable, IView
{
	/**
	 * The thread that is sorting (or null).
	 */
	private Thread kicker;

	/**
	 * What's the current state of the animation?
	 */
	SortState state = SortState.STOPPED;
	
	/**
	 * The array that is being sorted.
	 */
	int arr[];

	/**
	 * How long to sleep at each pause (milliseconds);
	 */
	int sleepTime = 10;

	/**
	 * The sorting algorithm (or null).
	 */
	SortAlgorithm algorithm;
	
	DataOrderEnum dataOrder = DataOrderEnum.RANDOM;

	SortDisplay(SortAlgorithm alg)
	{
		this.algorithm = alg;
		this.algorithm.addView(this);
		
		this.addComponentListener(new ComponentAdapter() {
			public void componentResized(ComponentEvent e)
			{	if (SortDisplay.this.getWidth() > 0 && state == SortState.STOPPED)
				{
					scramble();
					repaint();
				}
			}
		});
	}
	
	/** Update the view with the most recent information from the model.
	 * @param totalOps The total number of comparisons, movements, and
	 * other array operations since the last update.  Used to control the
	 * length of the pause in the display. */
	public synchronized void updateView(int totalOps)
	{
		this.repaint();
		try
		{
			while (this.state == SortState.PAUSED)
			{
				this.wait();
			}
			Thread.sleep(totalOps * this.sleepTime);
		} catch (InterruptedException e)
		{	
			System.out.println("caught exception");
		}

	}
	
	/**
	 * Resume sorting after the sort was paused.
	 */
	synchronized void resumeSort()
	{
		this.state = SortState.RUNNING;
		this.notifyAll();
	}
	
	/** 
	 * Pause the sort until the user resumes it.  The actual call to wait is
	 * in updateView.
	 */
	void pauseSort()
	{
		this.state = SortState.PAUSED;
	}
	
	void setDataOrder(DataOrderEnum order)
	{
		this.dataOrder = order;
		this.stopSort();
		this.scramble();
		this.repaint();
	}

	synchronized void stopSort()
	{
		this.state = SortState.STOPPED;
		this.algorithm.stop();
		this.algorithm.hiMarker = -1;
		this.algorithm.lowMarker = -1;
		this.algorithm.activeMarker = -1;
		this.scramble();
		this.repaint();
	}

	/**
	 * For a Thread to actually do the sorting. This routine makes
	 * sure we do not simultaneously start several sorts if the user
	 * repeatedly clicks on the sort item.  It needs to be
	 * synchronoized with the stop() method because they both
	 * manipulate the common kicker variable.
	 */
	synchronized void startSort()
	{
		this.algorithm.init();
		this.state = SortState.RUNNING;
		kicker = new Thread(this);
		kicker.start();
	}
	
	/**
	 * Fill the array with random numbers from 0..n-1.
	 */
	void scramble()
	{
		int a[] = new int[this.getWidth() / 2];
		switch (this.dataOrder)
		{
		case RANDOM:
			int h = this.getHeight();
			for(int i=0; i<a.length; i++)
			{
				a[i] = (int)(h * Math.random());
			}
			break;
			
		case INORDER:
			double f = this.getHeight()/(double)a.length;
			for(int i=0; i<a.length; i++)
			{
				a[i] = (int)(i*f);
			}
			break;
			
		case REVERSE_ORDER:
			f = this.getHeight()/(double)a.length;
			int l = a.length;
			for(int i=0; i<l; i++)
			{
				a[l-i-1] = (int)(i*f);
			}
			break;
		}
		
		arr = a;
	}

	
	/**
	 * Paint the array of numbers as a list
	 * of horizontal lines of varying lenghts.
	 */
	public void paintComponent(Graphics g)
	{
		if (this.arr == null)
			return;
		
		int a[] = this.arr;
		int x = this.getWidth() - 1;
		int h = this.getHeight();

		// Draw new lines
		g.setColor(Color.black);
		x = this.getWidth() - 1;
		for (int i = a.length; --i >= 0; x -= 2)
		{
			g.drawLine(x, h, x, h-arr[i]);
		}

		if (this.algorithm.hiMarker >= 0)
		{
			g.setColor(Color.red);
			x = this.algorithm.hiMarker * 2 + 1;
			g.drawLine(x, h, x, 0);
		}
		if (this.algorithm.lowMarker >= 0)
		{
			g.setColor(Color.blue);
			x = this.algorithm.lowMarker * 2 + 1;
			g.drawLine(x, h, x, 0);
		}
		if (this.algorithm.activeMarker >= 0)
		{
			g.setColor(Color.GREEN);
			x = this.algorithm.activeMarker * 2 + 1;
			g.drawLine(x, h, x, 0);
		}
	}

	/**
	 * Run the sorting algorithm. This method is
	 * called by class Thread once the sorting algorithm
	 * is started.
	 * @see java.lang.Thread#run
	 */
	public void run()
	{
		try
		{
			algorithm.init();
			algorithm.sort(arr);
		} catch (Exception e)
		{
			System.out.println(e);
		}
		this.kicker = null;
	}

}
