import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.Integer;
import java.util.Vector;

/**
 * Applet to demonstrate Peter's Convenience Store Problem
 * Creation date: (02/25/2002 10:07:02 AM)
 * Modified date: (04/15/2002 04:35:00 PM)
 * @author: Andrew Freed, arf132@psu.edu
 */

/*
Development of this applet was sponsored by the Penn State Fund for 
Excellence in Learning and Teaching (FELT), project "Java-based 
Teaching of Mathematics in Information Sciences and Technology",
supervised by Frank Ritter and David Mudgett.
*/

public class PetersProblemApplet extends Applet {
	private Button 	 btnGo 	   = new Button("Go!");
	private TextArea   txtOutput	   = new TextArea("",11,80,TextArea.SCROLLBARS_VERTICAL_ONLY);

	private Label lblPtrChoice = new Label("Peter:");
	private Choice ptrChoice = new Choice();

	private Label lblTrainerChoice = new Label("Trainers:");
	private Choice trnTrainTrainerChoice = new Choice();
	private Choice trnTrainInstallerChoice = new Choice();
	private Choice trnInstallChoice = new Choice();
	private Label lblTrainingTrainers = new Label("Training Trainers");
	private Label lblTrainingInstallers = new Label("Training Installers");
	private Label lblInstallers = new Label("Installing");

	private Label lblInstallerChoice = new Label("Installers:");
	private Choice insChoice = new Choice();

	private Label lblTotalUnits = new Label("Total units:");
	private TextField txtTotalUnits = new TextField("10");
	private Label lblNumRemaining = new Label("Units remaining:");
	private TextField txtNumRemaining = new TextField("?");

	public  int numUnitsRemaining;     // Units left to be installed
	public  int TOTAL_UNITS_NEEDED;    // Initial number of units to install
	private int numWeek;               // Maintain running date

	private int numInstallers;         // Peter's workforce
	private int numTrainers;           // Peter's workforce
	private int numTrainersTrainingTrainers; // How many trainers started training a trainer last week
	
	private boolean   isPeterTrainingTrainer;  // Peter can not stop training a trainer to do something else
	private boolean   weekInProgress;          // boolean to determine current applet state
	private boolean   doReset;                 // boolean to determine current applet state

	//Keeps track of whether or not the applet has run.  
	//Else, every pass through init() will create ANOTHER actionListener on btnGo
	private boolean   active  = false; 

	private Vector myTrainers   = new Vector();

// Training an installer involves updating the global count and inserting into choice box
private void addInstaller()
{
	numInstallers++;
	insChoice.removeAll();
	
	for( int idx = numInstallers; idx >= 0; idx-- )
		insChoice.add(new Integer(idx).toString());
}

// Peter training a trainer is a simplified case of a general trainer training a trainer
private void addTrainerByPeter()
{
	//If he isn't in the middle of the training process, it must be started
	if( !isPeterTrainingTrainer )
	{
		output("Peter began training a trainer");
		isPeterTrainingTrainer = true;
		ptrChoice.setEnabled(false);
	}

	//Otherwise finish the training process
	else
	{
		output("Peter finished training a trainer");
		myTrainers.addElement( new Trainer(++numTrainers) );
		isPeterTrainingTrainer = false;
		ptrChoice.setEnabled(true);
	}
}

//Accessor function to retrieve value from choice box
private int getNumChosenInstallers()
{
	return Integer.parseInt( insChoice.getSelectedItem() );
}

//Accessor function to retrieve value from choice box
private int getNumChosenTrainingTrainers()
{
	return Integer.parseInt( trnTrainTrainerChoice.getSelectedItem() );
}

//Accessor function to retrieve value from choice box
private int getNumChosenTrainingInstallers()
{
	return Integer.parseInt( trnTrainInstallerChoice.getSelectedItem() );
}

//Accessor function to retrieve value from choice box
private int getNumChosenTrainerInstallers()
{
	return Integer.parseInt( trnInstallChoice.getSelectedItem() );
}

//Sets available choices for the trainer boxes.  Note that trainers currently training
// other trainers are NOT available to do new tasks the following week.
private void setTrainerChoiceBoxes()
{
	int numUnavailable = numTrainersTrainingTrainers; //getNumChosenTrainingTrainers(); 
	trnTrainTrainerChoice.removeAll();
	trnTrainInstallerChoice.removeAll();
	trnInstallChoice.removeAll();

	//This creates a list from the top down, the maximal number is the "default" choice
	for( int idx = numTrainers - numUnavailable; idx >= 0; idx-- )
	{
		
		trnTrainTrainerChoice.add(new Integer(idx).toString());
		trnTrainInstallerChoice.add(new Integer(idx).toString());
		trnInstallChoice.add(new Integer(idx).toString());
	}
		//Default all available trainers to install.
		trnTrainTrainerChoice.select("0");
		trnTrainInstallerChoice.select("0");
}

//Fix the choice boxes based on user input.  Input argument is which choice box to hold constant:
// 1 - trnTrainTrainerChoice
// 2 - trnTrainInstallerChoice
// 3 - trnInstallChoice
private void fixTrainerBoxes(int numOkChoice)
{
try
{
	//Determine how many trainers are free
	int numAvailTrainers = numTrainers - numTrainersTrainingTrainers;
	int numTrainersUsed = 0;

	if( numOkChoice == 1 )
		numTrainersUsed += getNumChosenTrainingTrainers();
	else if (numOkChoice == 2)
		numTrainersUsed += getNumChosenTrainingInstallers();
	else
		numTrainersUsed += getNumChosenTrainerInstallers();

	if( numOkChoice != 3)
	{
		if(numAvailTrainers - numTrainersUsed <  getNumChosenTrainerInstallers())
			trnInstallChoice.select( new Integer(numAvailTrainers - numTrainersUsed).toString());

		numTrainersUsed += getNumChosenTrainerInstallers();
	}
	if( numOkChoice != 2)
	{
		if(numAvailTrainers - numTrainersUsed <  getNumChosenTrainingInstallers())
			trnTrainInstallerChoice.select( new Integer(numAvailTrainers - numTrainersUsed).toString());

		numTrainersUsed += getNumChosenTrainingInstallers();
	}
	if( numOkChoice != 1)
	{
		if(numAvailTrainers - numTrainersUsed <  getNumChosenTrainingTrainers());
			trnTrainTrainerChoice.select( new Integer(numAvailTrainers - numTrainersUsed).toString());

		numTrainersUsed += getNumChosenTrainingTrainers();
	}
}
catch( Exception e)
{
	output(e.toString());
	output("Doh!!!");
}
}

//Handles Peter's work. Based on selection in list box, routes work to appropriate function
private void doPetersOptions()
{
	boolean unitsRemaining = (numUnitsRemaining > 0);
	if( ptrChoice.getSelectedItem().equals("Install") )
	{
		if( unitsRemaining )
		{
			numUnitsRemaining--;
			output("Peter installed a unit");
		}
		else
		{
			output("Peter couldn't install -- All units installed");
		}
	}
	else if ( ptrChoice.getSelectedItem().equals("Train Installer") )
	{
		addInstaller();
		output("Peter trained an installer");
	}
	else //  ptrChoice.getSelectedItem.equals("Train Trainer") 
	{
		addTrainerByPeter();
	}
}

//Handles installer actions.  Essentially, the chosen number of installers either installs
// a unit, or reports that all units are installed.
private void doInstallersActions()
{
	boolean unitsRemaining = (numUnitsRemaining > 0);
	int numWorkingInstallers = getNumChosenInstallers();
	int idx;

	//Install available units
	for( idx = 0; idx < numWorkingInstallers && unitsRemaining; idx++ )
	{
		numUnitsRemaining--;
		output("Installer #" + (idx+1) + " installed a unit");
		unitsRemaining = (numUnitsRemaining > 0);
	}

	//If job is completed, report failure to install
	if( !unitsRemaining )
	for( int i = idx; i < numWorkingInstallers; i++)
		output("Installer #" + (i+1) + " couldn't install -- All units installed");
	
}

//Process actions for trainers.  Based on selections from listbox.
//Currently, if too many trainers are requested to do actions, the following priority is used:
//  1) Train trainers
//  2) Remaining trainers will train installers
//  3) Remaining trainers will install units
//
//  For instance, if there are 5 trainers and the boxes read "4", "3", "2" respectively, 4 will begin
//  training trainers, one will train and installer, and none will install units.
//
// The Trainer member variable "hasWorkedThisWeek" regulates this.  As a trainer is selected to perform 
// an action, the "hasWorkedThisWeek" flag is set.  Given the order of operations, it may be the case
// that all trainers have this flag set, before the requests to install units is processed.
private void doTrainerOptions()
{
	//Retrieve listbox values
	int trnTrainers = getNumChosenTrainingTrainers();
	int trnInstallers = getNumChosenTrainingInstallers();
	int trnInstalling = getNumChosenTrainerInstallers();

	int idx;
	int count;
	int countFinishedTrainers = 0;
	int countStartedTrainers = 0;

	//Handle trainers training trainers.  If a trainer is in the middle of training, allow them to finish.
	//Otherwise, note their progress.
	for( idx = 0; idx < numTrainers; idx++ )
	{
		Trainer temp = (Trainer) myTrainers.elementAt(idx);
		if( !temp.isTrainingATrainer )
		{
			if(countStartedTrainers < trnTrainers)
			{
			output("Trainer #" + (idx+1) + " began training a trainer");
			temp.isTrainingATrainer = true;
			temp.hasWorkedThisWeek = true;
			countStartedTrainers++;
			}
		}
		else //temp.IsTrainingTrainer
		{
			if( countFinishedTrainers < numTrainersTrainingTrainers)
			{
			output("Trainer #" + (idx+1) + " finished training a trainer");
			temp.isTrainingATrainer = false;
			temp.hasWorkedThisWeek = true;
			countFinishedTrainers++;		
			}
		}
	}

	//Train installers
	for( idx = 0, count = 0; idx < numTrainers && count < trnInstallers; idx++ )
	{
		Trainer temp = (Trainer) myTrainers.elementAt(idx);
		if( !temp.hasWorkedThisWeek )
		{
			addInstaller();
			output("Trainer #" + (idx+1) + " trained an installer");
			temp.hasWorkedThisWeek = true;
			count++;
		}
	}

	//Trainers who are installing units
	boolean unitsRemaining = ( numUnitsRemaining > 0 );
	for( idx = 0, count = 0; idx < numTrainers && count < trnInstalling && unitsRemaining; idx++ )
	{
		Trainer temp = (Trainer) myTrainers.elementAt(idx);
		if( !temp.hasWorkedThisWeek )
		{
			numUnitsRemaining--;
			output("Trainer #" + (idx+1) + " installed a unit");
			temp.hasWorkedThisWeek = true;
			count++;
		unitsRemaining = ( numUnitsRemaining > 0 );
		}
	}

	//If no work left, report the failure to install
	if( !unitsRemaining )
	for( int i = idx; i < numTrainers; i++)
	{
		Trainer temp = (Trainer) myTrainers.elementAt(idx);
		if( !temp.hasWorkedThisWeek )
		{
			output("Trainer #" + (i+1) + " couldn't install -- All units installed");
		}
	}

	//Actually add the new trainers... so that they can work NEXT week
	//If they are added before this point, they will be given work for THIS week
	for( idx=0; idx < numTrainersTrainingTrainers ; idx++ )
	{
		myTrainers.addElement( new Trainer(++numTrainers) );
	}

	//Advance one week
	numTrainersTrainingTrainers = trnTrainers; 

	//Reset 'hasWorkedThisWeek' flag
	for( idx=0; idx < numTrainers; idx++ )
	{
		Trainer temp = (Trainer) myTrainers.elementAt(idx);
		temp.hasWorkedThisWeek = false;
	}
}

private void printWeekHeader()
{
	output("********** BEGIN WEEK " + numWeek + " **********");
	output("Peter has " + numUnitsRemaining + " units left to install");
	output("Workforce: " + numInstallers + " installers, " + numTrainers + " trainers");
	output("----------- WORK -----------");
}

private void printWeekFooter()
{
	output("---------- RESULTS ----------");
	output("Peter has " + numUnitsRemaining + " units left to install");
	output("Workforce: " + numInstallers + " installers, " + numTrainers + " trainers");		
	output("********** END WEEK " + numWeek + " **********");
	numWeek++;

	if( numUnitsRemaining <= 0 )
	{
		output(">>> Done installing " + txtTotalUnits.getText() + " units!");
		output(">>> Change \"Total units\" and press Go! to reset simulation");
		weekInProgress = false;
		txtTotalUnits.setEnabled(true);
		doReset = true;
		return;	
	}
}

private void runWeek()
{
	//Apparently, there is a problem when this function dispatches it's work to the other functions	

	printWeekHeader();

	//Installer actions
	doInstallersActions();

	//Trainer options	
	doTrainerOptions();
	
	//Peter's options
	doPetersOptions();

	//Reset certain boxes
	setTrainerChoiceBoxes();

	//Update results
	txtNumRemaining.setText( new Integer(numUnitsRemaining).toString()  );

	printWeekFooter();
}

/**
 * Initializes the applet.
 */
public void init() {
	try {
		super.init();
		setName("PetersProblemApplet");
		setLayout(null);
		setSize(500, 400);

		btnGo.setBounds(0, 320, 50, 30);

		lblPtrChoice.setBounds(0,0,75,30);
		ptrChoice.setBounds(75,0,100,30);

		lblTrainerChoice.setBounds(0,50,75,30);
		trnTrainTrainerChoice.setBounds(75,50,100,30);
		trnTrainInstallerChoice.setBounds(200,50,100,30);
		trnInstallChoice.setBounds(325,50,100,30);

		lblTrainingTrainers.setBounds(75, 25, 125, 30);
		lblTrainingInstallers.setBounds(200, 25, 125, 30);
		lblInstallers.setBounds(325, 25, 100, 30);

		lblInstallerChoice.setBounds(0,80,75,30);
		insChoice.setBounds(75,80,100,30);

		lblTotalUnits.setBounds(100,320,100,30);
		txtTotalUnits.setBounds(200,320,30,30);
		lblNumRemaining.setBounds(250,320,100,30);
		txtNumRemaining.setBounds(350,320,30,30);

		txtTotalUnits.setEnabled(true);
		txtNumRemaining.setEditable(false);

		txtOutput.setBounds(0,120,495,200);
		txtOutput.setEditable(false);

		add(txtOutput);
		add(btnGo);
		add(lblPtrChoice);
		add(ptrChoice);	
		add(lblTrainerChoice);
		add(trnTrainTrainerChoice);
		add(trnTrainInstallerChoice);
		add(trnInstallChoice);
		add(lblTrainingTrainers);
		add(lblTrainingInstallers);
		add(lblInstallers);
		add(lblInstallerChoice);
		add(insChoice);
		add(lblTotalUnits);
		add(txtTotalUnits);
		add(lblNumRemaining);
		add(txtNumRemaining);

		trnTrainTrainerChoice.removeAll();
		trnTrainInstallerChoice.removeAll();
		trnInstallChoice.removeAll();
		insChoice.removeAll();

		trnTrainTrainerChoice.add("0");
		trnTrainInstallerChoice.add("0");
		trnInstallChoice.add("0");
		insChoice.add("0");

		ptrChoice.removeAll();
		ptrChoice.add("Install");
		ptrChoice.add("Train Installer");
		ptrChoice.add("Train Trainer");
		ptrChoice.setEnabled(true);

		numWeek = 1;
		isPeterTrainingTrainer = false;
		myTrainers   = new Vector();
		numInstallers = 0;
		numTrainers = 0;
		numTrainersTrainingTrainers = 0;
		weekInProgress = false; 

		btnGo.setActionCommand("btnGo");

		//Without this check, new ActionListeners are added on every Reset action
		if( !active )
		{
			//Identical ActionListener added to both btnGo and txtTotalUnits

			btnGo.addActionListener(new java.awt.event.ActionListener(){
			public void actionPerformed(java.awt.event.ActionEvent e)
			{
				//If called at end of simulation, need to reset values
				if( doReset )
				{
					doReset = false;
					init();
				}

				//If simulation is just beginning, need to load unit info
				if( !weekInProgress )
				{
					TOTAL_UNITS_NEEDED = Integer.parseInt( txtTotalUnits.getText() );
					numUnitsRemaining = TOTAL_UNITS_NEEDED;
					txtNumRemaining.setText( new Integer(numUnitsRemaining).toString() );
				}

				//Normal execution path
				if( numUnitsRemaining > 0 )
				{
					weekInProgress = true;
					txtTotalUnits.setEnabled(false);
					runWeek();
				}
			}
			});

			txtTotalUnits.addActionListener(new java.awt.event.ActionListener(){
			public void actionPerformed(java.awt.event.ActionEvent e)
			{
				//If called at end of simulation, need to reset values
				if( doReset )
				{
					doReset = false;
					init();
				}

				//If simulation is just beginning, need to load unit info
				if( !weekInProgress )
				{
					TOTAL_UNITS_NEEDED = Integer.parseInt( txtTotalUnits.getText() );
					numUnitsRemaining = TOTAL_UNITS_NEEDED;
					txtNumRemaining.setText( new Integer(numUnitsRemaining).toString() );
				}

				//Normal execution path
				if( numUnitsRemaining > 0 )
				{
					output("here");
					weekInProgress = true;
					txtTotalUnits.setEnabled(false);
					runWeek();
				}
			}
			});

			trnTrainTrainerChoice.addItemListener( new java.awt.event.ItemListener(){
			public void itemStateChanged(java.awt.event.ItemEvent e)
			{
				fixTrainerBoxes(1);
			}
			});
			trnTrainInstallerChoice.addItemListener( new java.awt.event.ItemListener(){
			public void itemStateChanged(java.awt.event.ItemEvent e)
			{
				fixTrainerBoxes(2);
			}
			});
			trnInstallChoice.addItemListener( new java.awt.event.ItemListener(){
			public void itemStateChanged(java.awt.event.ItemEvent e)
			{
				fixTrainerBoxes(3);
			}
			});
		}

		txtOutput.setText( "********** BEGIN SIMULATION **********\n"
					+ "Peter has to install " + numUnitsRemaining + " units into "
					+ " convenience stores.  He may either install a unit himself,"
					+ " train another installer, or train a trainer.  Please choose"
					+ " an action for Peter, and hit Go!\n\n");

		active = true;

	} catch (java.lang.Throwable Exc) {
		handleException(Exc);
	}
}

//Function to simplify output of messages
private void output(String msg)
{
	txtOutput.append(msg + "\n");
}
    
/**
 * Called whenever the part throws an exception.
 * @param exception java.lang.Throwable
 */
private void handleException(java.lang.Throwable exception) {
	output(exception.toString());
}

//Main function allows you to run this applet as an application
/**
 * main entrypoint - starts the part when it is run as an application
 * @param args java.lang.String[]
 */
public static void main(java.lang.String[] args) {
	try {
		Frame frame = new java.awt.Frame();
		PetersProblemApplet aPetersProblemApplet;
		Class iiCls = Class.forName("PetersProblemApplet");
		ClassLoader iiClsLoader = iiCls.getClassLoader();
		aPetersProblemApplet = (PetersProblemApplet)java.beans.Beans.instantiate(iiClsLoader,"PetersProblemApplet");
		frame.add("Center", aPetersProblemApplet);
		frame.setSize(aPetersProblemApplet.getSize());
		frame.addWindowListener(new java.awt.event.WindowAdapter() {
			public void windowClosing(java.awt.event.WindowEvent e) {
				System.exit(0);
			};
		});
		frame.show();
		java.awt.Insets insets = frame.getInsets();
		frame.setSize(frame.getWidth() + insets.left + insets.right, frame.getHeight() + insets.top + insets.bottom);
		frame.setVisible(true);
	} catch (Throwable exception) {
		System.err.println("Exception occurred in main() of java.applet.Applet");
		exception.printStackTrace(System.out);
	}
}

public class Trainer
{
	public Trainer(int _id)
	{
		trainerId = _id;
	}

	public boolean isTrainingATrainer = false;
	public boolean hasWorkedThisWeek = false;
	public int trainerId;
}

}
