// Copyright 1996, David Neto
/* MergeSortApp.java

PURPOSE:

Demonstrates MergeSort, with comparison counts.


AUTHOR: 

David Neto
neto@cs.utoronto.ca
Department of Computer Science
10 King's College Road
University of Toronto
Toronto, Ontario
Canada
M5S 1A4


VERSION DATE: March 24, 1996.


DEVELOPED UNDER:

Linux 1.2.13, the 1.0 JDK as ported as of March 1, 1996.


BUGS (bugs in Netscape 2.01, really):

1) Disappearing inputChoice.

Under Netscape, if the current input choice is not "Custom" and change
the inputChoice selection by some means other than direct manipulation
(i.e. changing it via the "Shuffle now" button, or by direct
manipulation of the values in the text field) then the inputChoice
button disappears.  It can be restored by making the windowing system
repaint the Netscape window, or by clicking once on the area where the
inputChoice is hidden.

The applet works fine in this regard under the JDK applet viewer.

The event is properly being sent to the ActiveChoice button, and the
paint() method is properly being called, but nothing is being repainted.

Perhaps there is something wrong with the peers implementation within
Netscape?

WORKAROUND: I am indebted to jabecker@mit.edu who gave me a 
workaround to this problem.  He said that adding a hide() and show() pair
fixes the problem.  It does.

2) Hardcoded parameters.  (Not really a bug...)

I have put in support to get runtime parameters from the applet tag.
However, Netscape does not always properly pass on all these values.
For example, sometimes null is passed in place of the actual value of
the second parameter.

The JDK applet viewer does not have this problem.

To get around this problem with Netscape, I have hardcoded the constants
for the n=16 case.  It works just as well with suitable parameters for
the n=21 case.  The downside is that the code needs to be replicated
in order to get the n=21 case.  I don't think we can get around this
code replication via subclassing because of the strict filename-class
correspondence.

*/

import java.awt.*;
import java.applet.Applet;

public class MergeSortApp extends Applet {
	
	GridBagLayout gridBag;
	GridBagConstraints c;

	Ticker ticker;

	int numberParam;	// number of elements in the array.
	String ascendingParam;
	String descendingParam;
	String strangeParam;
	int barWidthParam, barHeightMultipleParam;

	LoadButton loadButton;
	RewindButton rewindButton;
	GoButton goButton;
	StopButton stopButton;
	StepButton stepButton;
	Scrollbar speedScrollbar;

	SortInput inputs[];
	final int numInputs = 5;
	int currentInputIndex;
	SortInput currentInput;
	PreviousSortInput previousInput;
	CustomSortInput customInput;
	MergeSortActivation rootActivation = null;

	ActiveChoice inputChoice;
	InputRepField inputRepresentation;

	public void init() {
// System.out.println("MergeSortApp init");
		doRealConstruction();
	}

	public void start() {
// System.out.println("MergeSortApp start");
		loadButton.click();
		rootActivation.start();
	}

	public void stop() {
// System.out.println("MergeSortApp stop");
		ticker.killSortNow();
		rootActivation.stop();
	}

	public void destroy() {
// System.out.println("MergeSortApp destroy");
		removeAll();
		rootActivation = null;
	}

	private void doRealConstruction() {
		setLayout(gridBag = new GridBagLayout());
		c = new GridBagConstraints();
		c.weighty = -1;
		c.gridwidth = GridBagConstraints.REMAINDER;
		c.anchor = GridBagConstraints.WEST;

/* This is the only place where the n=21 and n=16 applets differ. */
		numberParam = 16;
		ascendingParam = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16";
		descendingParam = "16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1";
		strangeParam = "1 12 6 10 7 2 3 9 14 15 8 11 4 16 13 5";
		barWidthParam = 10;
		barHeightMultipleParam = 3;

		
		// Input specification panel.
        Panel inputPanel = new Panel();
        inputPanel.add(new Label("Input:"));
        inputChoice = new ActiveChoice();
		inputs = new SortInput[numInputs];
		inputs[0] = new FixedSortInput("Ascending", 
			numberParam, ascendingParam, this, 0);	
		inputs[1] = new FixedSortInput("Descending", 
			numberParam, descendingParam,this,1);
		inputs[2] = new FixedSortInput("Strange", 
			numberParam, strangeParam, this, 2);
		inputs[3] = customInput = new CustomSortInput("Custom", 
			numberParam, strangeParam, this, 3);
		inputs[4] = previousInput = new PreviousSortInput("Previous", 
			numberParam, strangeParam, this, 4);

		for (int i=0;i<numInputs;i++)
			inputChoice.addItem((Clickable)inputs[i]);
        inputPanel.add(inputChoice);

		inputRepresentation = new InputRepField(this,inputs[2].getRepresentation());
        inputPanel.add(inputRepresentation);
		// Prime the ``previousInput'' queue.
		inputs[2].click();
		inputs[2].click();

		inputPanel.add(new ShuffleButton("Shuffle now",this));
		gridBag.setConstraints(inputPanel,c);
        add("input", inputPanel);

		// Execution control panel.
        Panel executionPanel = new Panel();

		loadButton = new LoadButton("Load input",this);
		rewindButton = new RewindButton("Rewind",this);
		goButton = new GoButton("Go",this);
		stopButton = new StopButton("Stop",this);
		stopButton.enable(false);
		stepButton = new StepButton("Step",this);
		stepButton.enable(false);


        executionPanel.add(loadButton);
        executionPanel.add(rewindButton);
        executionPanel.add(goButton);
        executionPanel.add(stopButton);
        executionPanel.add(stepButton);

		Label speedLabel = new Label("Speed:");
        executionPanel.add(speedLabel);

		speedScrollbar = new Scrollbar(Scrollbar.HORIZONTAL,7,1,0,10);
        executionPanel.add(speedScrollbar);

		c.fill = GridBagConstraints.HORIZONTAL;
		gridBag.setConstraints(executionPanel,c);
        add("execution", executionPanel);

		Bar.init(getBackground(),barWidthParam,barHeightMultipleParam);
        ticker = new Ticker(new Speed(this));
    }

	public void setRootActivation() {
		int values[] = currentInput.getValues();
		int len = values.length;
		if ( rootActivation == null ) {
			rootActivation = new MergeSortActivation(this,ticker,null,0,len,values,0,len,true,true);
			gridBag.setConstraints(rootActivation,c);
			add("rootActivation",rootActivation);
		}
		rootActivation.setInput(len,values,0,len);
		rootActivation.rootUnsortedOnly();
	}


    public void doneSorting() {
        stopButton.click();
        goButton.enable(false);
        stepButton.enable(false);
    }
}

class LoadButton extends AbleButton {
	MergeSortApp msa;

	public LoadButton(String l, MergeSortApp msa ) {
		setLabel(l);
		enable(true);	
		this.msa = msa;
	}
	
	public void click() {
		msa.ticker.killSortNow();
		msa.setRootActivation();
		msa.loadButton.enable(true);	// Redundant, but good documentation.
		msa.rewindButton.enable(false);
		msa.goButton.enable(true);
		msa.stopButton.enable(false);
		msa.stepButton.enable(true);
	}
}

class RewindButton extends AbleButton {
	MergeSortApp msa;

	public RewindButton(String l, MergeSortApp msa ) {
		setLabel(l);
		enable(false);	
		this.msa = msa;
	}

	public void click() {
		// Rewind the instance...
		msa.ticker.killSortNow();
		msa.rootActivation.rootUnsortedOnly();
		msa.rootActivation.rewindInput();
		msa.loadButton.enable(true);
		msa.rewindButton.enable(false);
		msa.goButton.enable(true);
		msa.stopButton.enable(false);
		msa.stepButton.enable(true);
	}
}

class GoButton extends AbleButton {
	MergeSortApp msa;

	public GoButton(String l, MergeSortApp msa ) {
		setLabel(l);
		enable(true);	
		this.msa = msa;
	}

	public void click() {
		msa.loadButton.enable(false);
		msa.rewindButton.enable(false);
		msa.goButton.enable(false);
		msa.stopButton.enable(true);
		msa.stepButton.enable(false);
		msa.ticker.goForNow();
	}
}

class StopButton extends AbleButton {
	MergeSortApp msa;

	public StopButton(String l, MergeSortApp msa ) {
		setLabel(l);
		enable(true);	
		this.msa = msa;
	}

	public void click() {
		msa.ticker.stopForNow();
		msa.loadButton.enable(true);
		msa.rewindButton.enable(true);
		msa.goButton.enable(true);
		msa.stopButton.enable(false);
		msa.stepButton.enable(true);
	}
}

class StepButton extends AbleButton {
	MergeSortApp msa;

	public StepButton(String l, MergeSortApp msa ) {
		setLabel(l);
		enable(true);	
		this.msa = msa;
	}

	public void click() {
		msa.ticker.takeAStep();
		msa.loadButton.enable(true);
		msa.rewindButton.enable(true);
		msa.goButton.enable(true);
		msa.stopButton.enable(false);
		msa.stepButton.enable(true);
	}
}

class ShuffleButton extends AbleButton {
	MergeSortApp msa;

	public ShuffleButton(String l, MergeSortApp msa ) {
		setLabel(l);
		enable(true);	
		this.msa = msa;
	}

	public void click() {
		int j,i;
		String origReps[] = msa.currentInput.getRepArray();
		int len = origReps.length;
		String reps[] = new String[len];
		StringBuffer newRep = new StringBuffer();
		String t;
		for (i=0;i<len;i++) reps[i]=origReps[i];
		for ( i=len-1 ; i > 0 ; i-- ) { // Switch i with j
			j = randint(i+1);
			t = reps[i]; reps[i]=reps[j]; reps[j]=t;
		}
		for ( i=0;i<len;i++ ) {
			newRep.append(reps[i]);
			if ( i<len-1 ) newRep.append(" ");
		}
		msa.customInput.click(newRep.toString());
	}
	
	int randint(int hi) {
		// We need to work around the fact that the random() interval is
		// closed on the right.
		int answer;
		while ( (answer = (int)Math.floor(Math.random() * hi)) == hi )
			;
		return answer;
	}
}

class FixedSortInput extends SortInput {
	MergeSortApp msa;
	int selection;

	public FixedSortInput(String name, int len, String rep, MergeSortApp msa, int selection) {
		setLabel(name);
		setLength(len);
		setRepresentation(rep);
		this.msa = msa;
		this.selection = selection;
	}

	public void click() {
// System.out.println("FixedSortInput click()");
		msa.previousInput.setPrevious();
		msa.inputChoice.select(selection);
		msa.inputRepresentation.setText(getRepresentation());
		msa.currentInput = this;
	}
	
	public int getSelectionIndex() {
		return selection;
	}
}

class CustomSortInput extends SortInput {
	MergeSortApp msa;
	int selection;

	public CustomSortInput(String name, int len, String rep, MergeSortApp msa, int selection) {
		setLabel(name);
		setLength(len);
		setRepresentation(rep);
		this.msa = msa;
		this.selection = selection;
	}

	public void click() { // When Custom is selected in Choice.
		String thisCustomRep = msa.currentInput.getRepresentation();
		super.setRepresentation(thisCustomRep);
		msa.previousInput.setCustom(this,thisCustomRep);
		msa.inputChoice.select(selection);
		msa.inputRepresentation.setText(thisCustomRep);
		msa.currentInput = this;
	}
	
	public void click(String text) { // All other times.
		// When selected from click on Previous or answering to an Enter event
		// or a shuffle.
		super.setRepresentation(text);
		text = getRepresentation();
		msa.previousInput.setCustom(this,text);
		// Work around an unexpected design "feature" of the JDK applet 
		// viewer, and work around some of the "features" in Netscape.
		if ( msa.inputChoice.getSelectedIndex() != selection ) {
			msa.inputChoice.select(selection);
			msa.inputChoice.hide();	// Workaround to a Netscape bug.
			msa.inputChoice.show();
		}
		msa.inputRepresentation.setText(text);
		msa.currentInput = this;
	}
	
	public int getSelectionIndex() {
		return selection;
	}
}

class PreviousSortInput extends SortInput {
	MergeSortApp msa;
	int selection;
	SortInput previous;
	String previousCustomRep, currentCustomRep;
	CustomSortInput previousCustom, currentCustom;

	public PreviousSortInput(String name, int len, String rep, MergeSortApp msa, int selection) {
		setLabel(name);
		setLength(len);
		setRepresentation(rep);
		previousCustomRep = currentCustomRep = null;
		this.msa = msa;
		this.selection = selection;
	}

	public synchronized void click() {
		SortInput newInput = previous;
		// previous == previousCustom iff previousCustomRep != null;
		if ( previous == previousCustom ) {
			previousCustom.click(previousCustomRep);
		} else {
			newInput.click();
		}
	}

	// Exactly one of setPrevious and setCustom must be called per ACTION_EVENT
	public synchronized void setPrevious() {
		previous = msa.currentInput;
		previousCustom = currentCustom;
		currentCustom = null;
		previousCustomRep = currentCustomRep;
		currentCustomRep = null;
	}

	public synchronized void setCustom(CustomSortInput input, String text) {
		previous = msa.currentInput;
		previousCustom = currentCustom;
		currentCustom = input;
		previousCustomRep = currentCustomRep;
		currentCustomRep = text;
	}
}

class InputRepField extends TextField {
	MergeSortApp msa;

	public InputRepField(MergeSortApp msa, String text) {
		this.msa = msa;
		super.setText(text);
	}

	public synchronized boolean handleEvent(Event e) {
		if ( e.id == Event.ACTION_EVENT ) {
			msa.customInput.click(getText());
			return true;
		}
		return super.handleEvent(e);
	}
}

