import java.awt.*;

/** 
  * This is the frame to use for each of the dial clocks launched.
  * The frame is created and starts its own thread.
  */
public class DialClockFrame extends Frame implements Runnable{
	private Thread frameThread;
	private TimeCalc timeCalc;
	private GridBagLayout mainGBL = new GridBagLayout();
	private Panel mainPanel = new Panel();

	private PercentDial 
		minPD = new PercentDial(80), 
		hrPD  = new PercentDial(80), 
		dayPD = new PercentDial(80);

	private TextField 
		cSecInMinTF	= new TextField(15),
		tSecInMinTF	= new TextField(15),
		cMinInMinTF	= new TextField(15),
		tMinInMinTF	= new TextField(15),
		cSecInHrTF	= new TextField(15),
		tSecInHrTF	= new TextField(15),
		cMinInHrTF	= new TextField(15),
		tMinInHrTF	= new TextField(15),
		cHrInHrTF	= new TextField(15),
		tHrInHrTF	= new TextField(15),
		cSecInDayTF	= new TextField(15),
		tSecInDayTF	= new TextField(15),
		cMinInDayTF	= new TextField(15),
		tMinInDayTF	= new TextField(15),
		cHrInDayTF	= new TextField(15),
		tHrInDayTF	= new TextField(15),
		cDayInDayTF	= new TextField(15),
		tDayInDayTF	= new TextField(15);

	/**
	  * Constructor sets up frame and starts thread running for this
	  * newly created object.
	  */
	public DialClockFrame () {
		super("Dial Clock");

		// Use flow layout since all "CENTER" section of borderlayout
		// doesn't always get some space when Frame.pack() is used 
		// in some browsers
		setLayout(new FlowLayout() );
		setFont(new Font("Courier", Font.PLAIN, 12));
		setBackground(Color.lightGray);

		// First call to paint() will set the dimensions properly
		resize(1, 1);

		// let's see the frame even though it is the wrong size
		show();

		// Constructing this frame automatically creates and starts a 
		// thread running.
		frameThread = new Thread(this);
		frameThread.start();
	}

	/**
	  * Return the thread running the frame.  Generally used by the caller
	  * to stop the frame from running.
	  */
	public Thread getThread() {
		return frameThread;
	}

	/**
	  * Calls pack() to make the frame as small as possible regardless of
	  * any attempts to resize it.
	  */
	public void paint(Graphics g) {
		pack();
	}

	public boolean handleEvent(Event e) {
		if ( e.id == Event.WINDOW_DESTROY ) {
			dispose();  // dump the window

			// Stop the thread running the time calculations.
			// This is required, otherwise the thread continues
			// to run until the VM exits.
			stopTimeCalc();

			// Stop the thread running this frame.
			// Definitely don't want to stop "current" thread since
			// this is the app event handler and not the frame's thread.
			getThread().stop();

			return true;  // we've completely handled this event
		}

		return super.handleEvent(e);
	}

	// helper method to simplify adding constraints and components
	private void addIt(Component c, GridBagConstraints g) {
		mainGBL.setConstraints(c, g);
		mainPanel.add(c);
	}

	// utility method to modularize
	private void addAllComp() {
		add (mainPanel);

		GridBagConstraints rowHeaderGBC = new GridBagConstraints();
		GridBagConstraints innerGBC 	= new GridBagConstraints();
		GridBagConstraints lastGBC 		= new GridBagConstraints();
		GridBagConstraints inner2GBC 	= new GridBagConstraints();
		GridBagConstraints last2GBC		= new GridBagConstraints();

		rowHeaderGBC.insets = new Insets(2, 2, 2, 2);
		innerGBC.insets 	= new Insets(2, 2, 2, 2);
		lastGBC.insets 		= new Insets(2, 2, 2, 2);
		inner2GBC.insets 	= new Insets(2, 2, 2, 2);
		last2GBC.insets 	= new Insets(2, 2, 2, 2);

		rowHeaderGBC.gridheight = 2;

		lastGBC.gridwidth 	= GridBagConstraints.REMAINDER;
		last2GBC.gridwidth 	= GridBagConstraints.REMAINDER;

		innerGBC.anchor	= GridBagConstraints.WEST;
		lastGBC.anchor 	= GridBagConstraints.WEST;

		mainPanel.setLayout(mainGBL);

		Label fillerL 	= new Label("");
		Label secL		= new Label("Seconds");
		Label minL		= new Label("Minutes");
		Label hrL		= new Label("Hours");
		Label dayL		= new Label("Days");
		Label minTopL	= new Label("Min");
		Label hrTopL	= new Label("Hr");
		Label dayTopL	= new Label("Day");

		addIt(fillerL, rowHeaderGBC);
		addIt(dayTopL, inner2GBC);
		addIt(hrTopL, inner2GBC);
		addIt(minTopL, last2GBC);
		addIt(dayPD, inner2GBC);
		addIt(hrPD, inner2GBC);
		addIt(minPD, last2GBC);

		addIt(secL, rowHeaderGBC);
		addIt(cSecInDayTF, innerGBC);
		addIt(cSecInHrTF, innerGBC);
		addIt(cSecInMinTF, lastGBC);
		addIt(tSecInDayTF, innerGBC);
		addIt(tSecInHrTF, innerGBC);
		addIt(tSecInMinTF, lastGBC);

		addIt(minL, rowHeaderGBC);
		addIt(cMinInDayTF, innerGBC);
		addIt(cMinInHrTF, innerGBC);
		addIt(cMinInMinTF, lastGBC);
		addIt(tMinInDayTF, innerGBC);
		addIt(tMinInHrTF, innerGBC);
		addIt(tMinInMinTF, lastGBC);

		addIt(hrL, rowHeaderGBC);
		addIt(cHrInDayTF, innerGBC);
		addIt(cHrInHrTF, lastGBC);
		addIt(tHrInDayTF, innerGBC);
		addIt(tHrInHrTF, lastGBC);

		addIt(dayL, rowHeaderGBC);
		addIt(cDayInDayTF, lastGBC);
		addIt(tDayInDayTF, lastGBC);
	}

	/**
	  * Called by TimeCalc (obserable object) when new TimeGrid info
	  * is available to that we don't have to continually poll.
	  */
	public void update(TimeGrid grid) {
		NumFmt nf = new NumFmt(14, 3);  // like "%14.3f" in C

		// Take grid values and format them with a fixed number of
		// decimal places and right justification and put them into
		// the screen TextFields.

		cSecInDayTF.setText( nf.format(grid.cSecInDay) );
		tSecInDayTF.setText( nf.format(grid.tSecInDay) );
		cMinInDayTF.setText( nf.format(grid.cMinInDay) );
		tMinInDayTF.setText( nf.format(grid.tMinInDay) );
		cHrInDayTF.setText(  nf.format(grid.cHrInDay) );
		tHrInDayTF.setText(  nf.format(grid.tHrInDay) );
		cDayInDayTF.setText( nf.format(grid.cDayInDay) );
		tDayInDayTF.setText( nf.format(grid.tDayInDay) );

		cSecInHrTF.setText( nf.format(grid.cSecInHr) );
		tSecInHrTF.setText( nf.format(grid.tSecInHr) );
		cMinInHrTF.setText( nf.format(grid.cMinInHr) );
		tMinInHrTF.setText( nf.format(grid.tMinInHr) );
		cHrInHrTF.setText(  nf.format(grid.cHrInHr) );
		tHrInHrTF.setText(  nf.format(grid.tHrInHr) );

		cSecInMinTF.setText( nf.format(grid.cSecInMin) );
		tSecInMinTF.setText( nf.format(grid.tSecInMin) );
		cMinInMinTF.setText( nf.format(grid.cMinInMin) );
		tMinInMinTF.setText( nf.format(grid.tMinInMin) );

		// Update the dials to show the current percentage
		minPD.setCurrFrac(grid.cMinInMin);
		hrPD.setCurrFrac( grid.cHrInHr);
		dayPD.setCurrFrac(grid.cDayInDay);
	}

	// construct TimeCalc object and hold it reference
	private void startTimeCalc(long start, long delay, DialClockFrame dcf) {
		timeCalc = new TimeCalc(start, delay, dcf);
	}

	// Using the ref held when started, stop the TimeCalc's processing
	private void stopTimeCalc() {
		Thread t = timeCalc.getThread();
		t.stop();
	}

	/**
	  *  Main loop for DialClockFrame.  Sets up GUI and then relaxes.
	  */
	public void run() {
		// put all the components on the screen and repaint it.
		addAllComp();
		repaint();

		// Start up a TimeCalc object running from the system time
		// updating every 500ms.  Pass in a reference to ourself so
		// that TimeCalc can notify us when it has new info.
		startTimeCalc(System.currentTimeMillis(), 500, this);

		// ok to leave run(), other thread is running
	}
}
