//////////////////////////////////////////////////////////////////////
//
// Grad.java
// version 1.0
//
// This applet displays a simple timer indicating the amount of
// time until or since a specified moment in time.
//
// The concept behind this program originated as a simple C program
// to countdown the days, hours, minutes, and seconds until the
// moment of my high school graduation in 1993.
//
// This program is in the public domain.
//
// David Simmons
// March 12, 2000 
//
//////////////////////////////////////////////////////////////////////

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

public class Grad extends Applet implements Runnable {

    // debug?
    static final boolean DEBUG = false;

    // some constants for calculating time
    static final int SECS_IN_MIN = 60;
    static final int SECS_IN_HR  = SECS_IN_MIN * 60;
    static final int SECS_IN_DAY = SECS_IN_HR  * 24;

    // how many milliseconds to sleep between updates
    static final int SLEEP_TIME = 1000;

    // Grad control thread
    Thread thread;

    // applet display parameters
    String output1;
    String output2;
    int width;
    int height;

    // target time, in milliseconds
    long target_ms = 0;

    // these strings will contain the text messages to
    // be displayed around the timer
    String before = "";
    String after  = "";

    // double-buffering
    Image offScreenImage;
    Graphics offScreenGC;
    Color backgroundColor;
    Color foregroundColor;

    // Grad() constructor
    //
    // The constructor currently does nothing.

    public Grad() {
    }

    // start() -- overridden Applet method
    //
    // start the thread when the applet receives a start().

    public void start() {
        (thread = new Thread(this)).start();
    }

    // stop() -- overridden Applet method
    //
    // stop the thread (get rid of the thread reference) when the
    // applet receives a stop().

    public void stop() {
        thread = null;
    }

    // init() -- overridden Applet method
    //
    // The init() method is called by the AWT to do some setup and
    // initialization of variables.

    public void init() {

	// Get the dimensions of the applet window
        Dimension dimension = getSize();
        width = dimension.width;
        height = dimension.height;

	// Get the "bgcolor" parameter
        String bgcolor = getParameter("bgcolor");
        if (bgcolor != null) {
	    if (bgcolor.charAt(0) == '#') {
		bgcolor = bgcolor.substring(1);
	    }
	    backgroundColor = new Color(Integer.parseInt(bgcolor, 16));
            setBackground(backgroundColor);
        } else {
	    backgroundColor = new Color(0xFFFFFF);
	}

	// Get the "fgcolor" parameter
        String fgcolor = getParameter("fgcolor");
        if(fgcolor != null) {
	    if (fgcolor.charAt(0) == '#') {
		fgcolor = fgcolor.substring(1);
	    }
	    foregroundColor = new Color(Integer.parseInt(fgcolor, 16));
            setForeground(foregroundColor);
        } else {
	    foregroundColor = new Color(0x000000);
	}

	// Get the "target" parameter, which contains the
	// target time in the format "YYYYMMDDhhmm", and convert
	// it to an absolute measurement of time in milliseconds.
        String target_string = getParameter("target");
	if (target_string != null) {
	    int year = Integer.parseInt(target_string.substring(0, 4));
	    int month = Integer.parseInt(target_string.substring(4, 6)) - 1;
	    int day = Integer.parseInt(target_string.substring(6, 8));
	    int hour = Integer.parseInt(target_string.substring(8, 10));
	    int minute = Integer.parseInt(target_string.substring(10, 12));
	    int seconds = 0;
	    GregorianCalendar gregoriancalendar = new GregorianCalendar();
	    gregoriancalendar.setTimeZone(TimeZone.getTimeZone("GMT"));
	    gregoriancalendar.set(year, month, day, hour, minute, seconds);
	    target_ms = gregoriancalendar.getTime().getTime();
	} else {
	    // default to new year's 2001 if no target specified
	    GregorianCalendar gregoriancalendar = new GregorianCalendar();
	    gregoriancalendar.setTimeZone(TimeZone.getTimeZone("GMT"));
	    gregoriancalendar.set(2001, 0, 1, 0, 0, 0);
	    target_ms = gregoriancalendar.getTime().getTime();
	}

	if (DEBUG) {
	    System.out.println("target_ms is " + target_ms);
	}

	// Get the "stringXX" parameters which contain the
	// text to display after the timer.

        if (getParameter("before") != null) {
            before = getParameter("before");
        }

        if (getParameter("after") != null) {
            after = getParameter("after");
        }

    }

    // update() -- overriden Component method
    //
    // The update() method is called by the AWT when a repaint
    // is requested.  This method is responsible for setting up
    // an offscreen image buffer for paint() to draw into, calling
    // paint(), and blatting the new image to the screen.

    public void update(Graphics g) {

	// create an offscreen image for double-buffering
	if (offScreenImage == null) {
	    offScreenImage = createImage(width, height);
	    offScreenGC = offScreenImage.getGraphics();
	}

	// clear the offscreen image
	offScreenGC.setColor(backgroundColor);
	offScreenGC.fillRect(0,0,width,height);
	offScreenGC.setColor(foregroundColor);

	// have the paint() method draw the new text into the
	// offscreen image buffer
	paint(offScreenGC);

	// blat the offscreen image to the screen
	g.drawImage(offScreenImage,0,0,this);
    }

    // paint() -- overriden Component method
    //
    // paint() is called by update() with the graphics context
    // set to an offscreen image buffer.  update(), in turn, is
    // called by the AWT whenever a repaint() is requested.

    public void paint(Graphics g) {

	// these variables will hold the x and y coordinates of
	// each of the two strings to be displayed.
	int x1,y1,x2,y2;

	// get a FontMetrics object which can be used to determine the
	// dimensions of the strings with the font in use.
        Font font = g.getFont();
        FontMetrics fontmetrics = getFontMetrics(font);

	// calculate the y coordinates for each string, based on the
	// y dimensions of the font.
        y1 = fontmetrics.getMaxAscent();
        y2 = y1*2 + fontmetrics.getMaxDescent() + fontmetrics.getLeading();

	// these are the lengths (in characters) of each string.
        int length1 = output1.length();
        int length2 = output2.length();

	// create a character-array for each string, since
	// the Fontmetrics.charsWidth method wants to examine a
	// character-array instead of a String object.
        char ca1[] = new char[length1];
        char ca2[] = new char[length2];
        output1.getChars(0, length1, ca1, 0);
        output2.getChars(0, length2, ca2, 0);

	// calculate the pixel width of each string.
        int cw1 = fontmetrics.charsWidth(ca1, 0, length1);
        int cw2 = fontmetrics.charsWidth(ca2, 0, length2);

	// calculate the x coordinates for each string, centering
	// each string within the applet window.
        x1 = width / 2 - cw1 / 2;
        x2 = width / 2 - cw2 / 2;

	// finally, draw each string into the graphics context.
        g.drawString(output1, x1, y1);
        g.drawString(output2, x2, y2);
    }

    // run() -- required for the Runnable interface
    //
    // This is the heart of the applet.  The applet's start() method
    // creates a thread which calls this run() method via the applet's
    // Runnable interface.  run() will enter an infinite loop (until the
    // applet's stop() is called) which creates the strings to be displayed,
    // draws them, and sleeps one second.

    public void run() {
        boolean flag = false;
        try {
	    // does "thread" still represent the current thread? then continue...
            for (; thread == Thread.currentThread(); Thread.sleep(SLEEP_TIME)) {

		// near-infinite loop, pauses for SLEEP_TIME every iteration

		// calculate the elapsed seconds, and if this is the seconds
		// "since" or the seconds "until".
                int elapsed  = (int)((System.currentTimeMillis() - target_ms) / 1000L);
                if (elapsed < 0) {
                    flag = true;
                    elapsed = -elapsed;
                }

		// calculate the days, hours, minutes
                int days = elapsed / SECS_IN_DAY;
                elapsed -= days * SECS_IN_DAY;
                int hours = elapsed / 3600;
                elapsed -= hours * SECS_IN_HR;
                int minutes = elapsed / 60;
                elapsed -= minutes * SECS_IN_MIN;
                int seconds = elapsed;

		// formulate the first string which contains the
		// text of the countdown
                output1 = days + " day" + (days == 1 ? "" : "s") + " " +
		    hours + " hour" + (hours == 1 ? "" : "s") + " " +
		    minutes + " minute" + (minutes == 1 ? "" : "s") + " " +
		    seconds + " second" + (seconds == 1 ? "" : "s");

		// formulate the second string which contains the
		// informative text supplied by the user
                if (flag) {
                    output2 = before;
		} else {
                    output2 = after;
		}

		// ask AWT for a repaint
                repaint();
            }

        } catch(Exception e) { }
    }

}
