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
    }
}
