/*
 * DigitalClock.java    V2.0    10/20/96
 *
 * Copyright (c) 1996-7 H.J. Tsai, Inc. All Rights Reserved.
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for any purposes and without
 * fee is hereby granted provided that this copyright notice
 * appears in all copies. 
 *
 * H.J. Tsai MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
 * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
 * PURPOSE, OR NON-INFRINGEMENT. H.J. Tsai SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
 *
 * Author: H.J. Tsai  hjtsai@cargobay.com
 *
 * Version 2.1a Feb 21, 1999
 *              Modified the parameter names match the document
 *
 * Version 2.1  Sep 9, 1997
 *			    Added option to disable copyright
 *				Added work around for NS 4.0 date string problem
 *				Added support to not display seconds
 *
 * Version 2.0  Oct 20, 1996
 *              Added many new options
 *              - a real, useful clock now
 *
 * Version 1.0  Sep 08, 1996
 *              Initial Version
 *               - just for fun
 */
import java.util.*;
import java.awt.*;
import java.applet.*;

public class DigitalClock extends Clock {

    // Date position when displayed
    protected String clockname       = "DigitalClock 97";
    protected String clocksmith      = "Copyright 1997 The Java ClockShop";

    // Date position when displayed
    private static final int DP_DATETIME        = 1;
    private static final int DP_TIMEDATE        = 2;
    private static final int DP_TIMEOVERDATE    = 3;
    private static final int DP_DATEOVERTIME    = 4;


    // Fields for program parameters
    protected int radix = 10;
    protected boolean show24Hours = false;
    protected boolean showSeconds = true;
  
    protected String clockFontName = "TimesRoman";
    protected int clockFontStyle = Font.PLAIN;
    protected int clockFontSize = 14;
    protected Color clrClockForeground = Color.black;
    protected Color clrClockBackground = Color.lightGray;

    protected boolean showFrame = true;
    protected Color clrFrame = Color.lightGray;
    protected int frameThickness = 5;
    protected int frameStyle = Rect.RS_NORMAL;
    private int outBevel = 0;
    private int outBevelStyle = Rect.RS_NORMAL;
    private int inBevel = 0;
    private int inBevelStyle = Rect.RS_NORMAL;
    
    protected boolean showDate = true;
    protected Color clrDate = Color.green;
    protected int dateposition = DP_DATETIME;
    protected String dateFontName = "Helvetic";
    protected int dateFontStyle = Font.ITALIC;
    protected int dateFontSize = 12;
    

    // program options
    protected boolean showZoneTime = true; /// false;
    protected long  tzdiff = 0L;  // diff between local and target zone in ms
    protected int tztarget;       // diff between GMT and target zone in minutes
        
    // clock dependent variables - vary with the radices
    // derived class should provide its own values
    protected int maxHourlen;
    protected int maxMinlen;
    protected int maxSeclen;
    protected String colon;

    // internal runtime variables
    private String sHours = null;
    private String sMinutes = null;
    private String sSeconds = null;
    private Font fontClock;
    private Font fontDate;
    private boolean showCopyright = true;
    private RectFrame rectFrame;
    private Rect rectClock;
    private Image memImage;
    private int lasts = -1;
    private int lastm = -1;
    private int lasth = -1;

   /*
    * Applet entry point
    */
    public void init() {

        // call Clock.init()
        super.init();

        // get program options
        getProgramParams();

        Rectangle r = bounds();
        memImage = createImage(r.width, r.height);
        if (showFrame) {
            rectFrame = new RectFrame(r,
                                outBevel,
                                outBevelStyle,
                                frameThickness,
                                frameStyle,
                                clrFrame,
                                inBevel,
                                inBevelStyle);
            
            rectClock = rectFrame.getInsideRect();
        }
        else {
            rectClock = new Rect(r);
        }

        // get the fonts to be used     
        fontClock = new Font(clockFontName, clockFontStyle, clockFontSize);
        fontDate = new Font(dateFontName, dateFontStyle, dateFontSize);
        
        // set the digits related values for the clock, since the clock can
        // be a binary, a ternary,...clock
        setDigitsForClock(radix);

        if (showZoneTime) {
            super.setTimezoneDiff(tztarget);
        }
    }
    

   /*
    * Applet starts running
    */
    public void start() {
        show();
        super.start();
    }

   /*
    * Applet stops running
    */
    public void stop() {
        super.stop();
    }
 
    public void tick() {
        repaint();
    }


    public void update(Graphics g) {
        // update the clock in offscreen memory image
        Graphics memg = memImage.getGraphics();
        paint(memg);

        // copy the offscreen image to the real screen
        g.drawImage(memImage, 0, 0, null);
    }

   /*
    * Paints the clock
    * painting is done by erasing the whole clock and 
    * repaint everything even when only the 'seconds' needs
    * to be updated.  If you change this, change update() too.
    */
    public void paint(Graphics g) {
        if (showCopyright) {
            showCopyright(g, clockname, clocksmith);
            showCopyright = false;
            return;
        }

        int s = getDisplaySeconds();
        int m = getDisplayMinutes();
        int h = getDisplayHours();

        //System.err.println("h="+h + " m="+m + " s="+s);

        sSeconds = i2a(s, maxSeclen, '0');

        if (lastm != m) {
            lastm = m;
            sMinutes = i2a(m, maxMinlen, '0');
        }

        if (lasth != h) {
            lasth = h;

            if ((h > 12) && (show24Hours == false)) 
                h -= 12;

            sHours = i2a(h, maxHourlen, ' ');
        }

        // now paint
        // erase the old time
        Dimension d = size();
        g.setColor(clrClockBackground);
        g.fillRect(0, 0, d.width, d.height);
         
        drawFrame(g); 
        String hhmmss = sHours + colon + sMinutes;
   
        if (showSeconds)
            hhmmss += colon + sSeconds;

        if (showDate) {
            int yy = getDisplayYear();
            int mm = getDisplayMonth();
            int dd = getDisplayDay();

            String yymmdd = getLocaleDateString(yy,mm,dd);

            if (dateposition == DP_DATETIME || dateposition == DP_TIMEDATE) 
                drawDateTimeOnSameLine(g, yymmdd, hhmmss);
            else
                drawDateTime(g, yymmdd, hhmmss);
        }
        else {
            drawTime(g, hhmmss);
        }
    }


   /*
    * Draws current time: hhmmss
    */
    public void drawTime(Graphics g, String hhmmss) {

        g.setFont(fontClock);
        FontMetrics fm = g.getFontMetrics();

        int sWidth = fm.stringWidth(hhmmss);
        int sHeight = fm.getAscent();
    
        g.setColor(clrClockForeground);
        g.drawString(hhmmss,
                        rectClock.x + (rectClock.width - sWidth)/2,
                        rectClock.y + (rectClock.height + sHeight)/2);
    }

   /*
    * Draw the locale dependent date string yymmdd and Time string: hhmmss on the
    * same line.  The date and time may use different font
    */
    public void drawDateTimeOnSameLine(Graphics g, String yymmdd, String hhmmss) {
        g.setFont(fontDate);
        FontMetrics fm = g.getFontMetrics();
        int ymdWidth = fm.stringWidth(yymmdd);
        int ymdHeight = fm.getAscent();
        
        g.setFont(fontClock);
        fm = g.getFontMetrics();
        int hmsWidth = fm.stringWidth(hhmmss);
        int hmsHeight = fm.getAscent();

        int width = ymdWidth + hmsWidth + 10; // 10 pixels to separate the two
        
        int x1; // x1 is x coordinate for date
        int x2; // x2 is x coordinate for time
        if (dateposition == DP_DATETIME) {
            x1 = rectClock.x + (rectClock.width - width)/2;
            x2 = x1 + ymdWidth + 10;
        }
        else {
            x2 = rectClock.x + (rectClock.width - width)/2;
            x1 = x2 + hmsWidth + 10;
        }

        // draw date
        g.setColor(clrDate);
        g.setFont(fontDate);
        g.drawString(yymmdd, x1, rectClock.y + (rectClock.height + ymdHeight)/2);

        // draw time
        g.setColor(clrClockForeground);
        g.setFont(fontClock);
        g.drawString(hhmmss, x2, rectClock.y + (rectClock.height + hmsHeight)/2);
    }

    
    /*
     * Draws the locale dependent date string: yymmdd and
     * time string: hhmmss on separate lines.
     */
    public void drawDateTime(Graphics g, String yymmdd, String hhmmss) {
        g.setFont(fontDate);
        FontMetrics fm = g.getFontMetrics();
        int ymdWidth = fm.stringWidth(yymmdd);
        int ymdHeight = fm.getAscent();
        
        g.setFont(fontClock);
        fm = g.getFontMetrics();
        int hmsWidth = fm.stringWidth(hhmmss);
        int hmsHeight = fm.getAscent();

        // draw date over time  or time over date
        int y1; // y1 is y coordinate for date
        int y2; // y2 is y coordinate for time
        if (dateposition == DP_DATEOVERTIME) {
            y1 = rectClock.y + rectClock.height/2;
            y2 = rectClock.y + rectClock.height/2 + hmsHeight + 2; // 2 pixels vertical space
        }
        else { // Time over Date
            y1 = rectClock.y + rectClock.height/2 + ymdHeight + 2;
            y2 = rectClock.y + rectClock.height/2;
        }

        // draw date
        g.setColor(clrDate);
        g.setFont(fontDate);
        g.drawString(yymmdd, rectClock.x + (rectClock.width - ymdWidth)/2, y1);

        // draw time
        g.setColor(clrClockForeground);
        g.setFont(fontClock);
        g.drawString(hhmmss, rectClock.x + (rectClock.width - hmsWidth)/2, y2);
    }

    public void drawFrame(Graphics g) {
        if (showFrame)
            rectFrame.paint(g);
    }


   /*
    * returns the Locale dependent Date string
    * -- may need a better way to build the locale date string
    */
    public String getLocaleDateString(int y, int m, int d) {
        String datestring = (new Date(y,m,d)).toLocaleString();
        int i = datestring.lastIndexOf(' ');
		if (i < 0)
			return datestring;

		String datestring2 = datestring.substring(0, i);

		int j = datestring2.lastIndexOf(' ');
		if (j < 0)
			return datestring2;
		else
			return datestring2.substring(0, j);
    }

   /*
    * Gets program pamameters
    */
    protected void getProgramParams() {   
    // get program parameters
        String arg;     
        
        // get digit radix
        radix = getNumParam("DigitRadix", 10);
        if (radix > Character.MAX_RADIX)
            radix = Character.MAX_RADIX;
        else if (radix < Character.MIN_RADIX)
            radix = Character.MIN_RADIX;

        arg = getParameter("Author");
        if (arg != null && arg.equalsIgnoreCase("hjtsai@cargobay.com"))
            showCopyright = false;
        else 
            showCopyright = true;
 
        // TimeZone desired in minutes
        arg = getParameter("TimeZone");
        if (arg == null) 
            showZoneTime = false;
        else {
            try {
                tztarget = Integer.parseInt(arg);
                //System.err.println("param timezone=" + tztarget);
                
                showZoneTime = true;

            } catch (NumberFormatException e) {
                showZoneTime = false;
            }
        }

        // show 24 hours format
        arg = getParameter("Show24Hours");
        if (arg != null) 
            show24Hours = arg.equalsIgnoreCase("yes")
                          || arg.equalsIgnoreCase("true");
            
        // show second
        arg = getParameter("ShowSeconds");
        if (arg != null) 
            showSeconds = arg.equalsIgnoreCase("yes")
                          || arg.equalsIgnoreCase("true");

        // ForegroundColor
        arg = getParameter("ForegroundColor");
        if (arg != null)
            clrClockForeground = ColorValue.s2color(arg, Color.black);

        // BackgroundColor
        arg = getParameter("BackgroundColor");
        if (arg != null)
            clrClockBackground = ColorValue.s2color(arg, Color.lightGray);
    
        // get clock font
        arg = getParameter("ClockFontName");
        if (arg != null) 
            clockFontName = arg;

        // get clock font style
        clockFontStyle = getfontStyleClockParam("ClockFontStyle", Font.BOLD);

        // get clock font pointsize
        clockFontSize = getNumParam("ClockFontSize", 14);
        if (clockFontSize < 3)
            clockFontSize = 3;
                 
        // show clock frame
        arg = getParameter("ShowFrame");
        if (arg != null) 
            showFrame = arg.equalsIgnoreCase("yes") 
                        || arg.equalsIgnoreCase("true");

        // clock frame color
        arg = getParameter("FrameColor");
        if (arg != null)
            clrFrame = ColorValue.s2color(arg, Color.lightGray);

        // get clock frame thickness
        frameThickness = getNumParam("FrameThickness", 3);
        if (frameThickness < 1)
            frameThickness = 1;

        // get clock frame style
        frameStyle = getRectStyleParam("FrameStyle", Rect.RS_NORMAL);
        
        // get outer bevel params
        outBevel = getNumParam("OutBevel", 0);
        outBevelStyle = getRectStyleParam("OutBevelStyle", Rect.RS_NORMAL);

        // get in-bevel params
        inBevel = getNumParam("InBevel", 0);
        inBevelStyle = getRectStyleParam("InBevelStyle", Rect.RS_NORMAL);

        // show date
        arg = getParameter("ShowDate");
        if (arg != null) 
            showDate = arg.equalsIgnoreCase("yes") 
                       || arg.equalsIgnoreCase("true");

        // Color for Date
        arg = getParameter("DateColor");
        if (arg != null) 
            clrDate = ColorValue.s2color(arg, clrClockForeground);
         
        // date position
        dateposition = getNumParam("DatePosition", DP_DATETIME);
        if (dateposition > 4 || dateposition < 1)
            dateposition = DP_DATETIME;

        // get font for 'date'
        arg = getParameter("DateFontName");
        if (arg != null) 
            dateFontName = arg;
        
        // get Date font style
        dateFontStyle = getfontStyleClockParam("DateFontStyle", Font.ITALIC);

        // get Date font pointsize
        dateFontSize = getNumParam("DateFontSize", 10);
        if (dateFontSize < 3)
            dateFontSize = 3;
    }

   /*
    * get the specified numeric programeter param, negative number now allowed
    */
    public int getNumParam(String param, int defaultvalue) {
        String arg = getParameter(param);
        int i = 0;
        
        if (arg == null)
            i = defaultvalue;
        else {
            try {
                i = Integer.parseInt(arg);
            } catch (NumberFormatException e) {
                i = defaultvalue;
            }
        }

        if (i < 0)
            i = 0;

        return i;
    }

   /*
    * Gets the specified Font style program parameter
    */
    public int getfontStyleClockParam(String param, int defaultvalue) {
        String arg = getParameter(param);
        int fs;
        if (arg == null)
            fs = defaultvalue;
        else if (arg.equalsIgnoreCase("bold")) 
            fs = Font.BOLD;
        else if (arg.equalsIgnoreCase("Plain"))
            fs = Font.PLAIN;
        else if (arg.equalsIgnoreCase("Italic"))
            fs = Font.ITALIC;
        else if (arg.equalsIgnoreCase("boldItalic")) 
            fs = Font.BOLD + Font.ITALIC;
        else 
            fs = defaultvalue;    

        return fs;
    }

   /*
    * Gets the specified Rect style parameter
    */
    public int getRectStyleParam(String param, int defaultstyle) {
        String arg = getParameter(param);
        int rs;
        if (arg == null)
            rs = defaultstyle;
        else if (arg.equalsIgnoreCase("etchedin")) 
            rs = Rect.RS_ETCHEDIN;
        else if (arg.equalsIgnoreCase("etchedout"))
            rs = Rect.RS_ETCHEDOUT;
        else if (arg.equalsIgnoreCase("raised"))
            rs = Rect.RS_3DRAISED;
        else if (arg.equalsIgnoreCase("inset")) 
            rs = Rect.RS_3DINSET;
        else if (arg.equalsIgnoreCase("normal"))
            rs = Rect.RS_NORMAL;
        else
            rs = defaultstyle;
        
            return rs;
     }
   

   /*
    * Sets the digits to be used for the clock. The digits
    * are used to calc the maximum size it will occupy on the screen.
    * Note: the digits are assumed to be separated by ':'s
    */
    protected void setDigitsForClock(int radix) {

        colon = ":";
        switch (radix) {
        case 2: // 10111.111011:111011
            maxHourlen = 5;
            maxMinlen = 6;
            maxSeclen = 6;
            break;

        case 3: // 212:2012:2012
            maxHourlen = 3;
            maxMinlen = 4;
            maxSeclen = 4;
            break;

        case 4: // 113:323:323
            maxHourlen =3;
            maxMinlen = 3;
            maxSeclen = 3;
            break;

        case 5: // 43:214:214
        case 6: // 35:135;135
        case 7: // 32:113:113
            maxHourlen = 2;
            maxMinlen = 3;
            maxSeclen = 3;
            break;

        case 8: // 27:73:73
        default:
            maxHourlen = 2;
            maxMinlen = 2;
            maxSeclen = 2;
        }
    }

   

   /*
    * Converts an integer n into a string of length len with
    * leading padding char padch applied if the length of the converted
    * integer is shorter then len
    */
    protected String i2a(int n, int len, char padch) {

        String s = Integer.toString(n, radix);  

        if (s.length() >= len)
            return s.toUpperCase();

        // pad in front of the string
        StringBuffer sbuf = new StringBuffer(len);
        sbuf.setLength(len);
          
        // init to pad
        for (int i=0; i < len; i++) {
            sbuf.setCharAt(i, padch);   
        }
            
        // insert the string s to the right place so
        // the last char is at the end of the buffer
        try {
            for (int i=0; i < s.length(); i++) {
                sbuf.setCharAt(len-s.length()+i, s.charAt(i));
            }
        } catch (StringIndexOutOfBoundsException e) {
        }

        return sbuf.toString().toUpperCase();   
    }

   /*
    * Displays a copyright statement
    */
    public void showCopyright(Graphics g, String clockname, String author) {
        Rectangle r = bounds();

        // clear background
        Color c = Color.lightGray;
        g.setColor(c);
        g.fillRect(r.x, r.y, r.width, r.height);

        // draw a frame 
        RectFrame rectFrame = new RectFrame(r,
                                            2,
                                            Rect.RS_3DRAISED,
                                            3,
                                            Rect.RS_NORMAL,
                                            Color.lightGray,
                                            2,
                                            Rect.RS_ETCHEDIN);
        rectFrame.paint(g);

        // create the marquee for displaying copyright
        Rectangle newr = new Rectangle(r.x+7,r.y+7, r.width-14, r.height-14);
        g.clipRect(newr.x, newr.y, newr.width, newr.height);

        // the amount=1 and delay=5 generate fast and smooth scrolling
        // unfortuntely, Netscape Navigator V3.0 Java VM seems unable
        // to handle short delay (delay=4 is too fast)
        Marquee marquee = new Marquee(
                                      newr,
                                      1,                      // loop
                                      5,   // 1,              // amount
                                      100, // 5,              // delay
                                      Marquee.LEFT,           // direction
                                      Marquee.SCROLL,         // behavior
                                      clockname+" "+author,
                                      Color.lightGray,        // background
                                      Color.darkGray,         // foreground
                                      this);
    
        Thread mthread = new Thread(marquee);
        mthread.start();

        // wait until the marquee thread ends
        while (mthread.isAlive()) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
        }
        mthread.stop();
        mthread = null;
    }


    public String getAppletInfo() {
        String info = clockname + " " + clocksmith;
        return info;
    }

    public String [][] getParamterInfo() {
        String info[][] = {
            {"DigitRadix", "integer", "the number radix for the digits"},
            {"show24Hours", "boolean", "use 24 hours format"},
            {"ForegroundColor", "String", "foreground color"},
            {"BackgroundColor", "String", "background color"},
            {"ClockFontName", "String", "font name for the clock digits"},
            {"ClockFontStyle", "String", "font style for the clock digits"},
            {"ClockFontSize", "integer", "font size for the clock digits"},
            {"ShowFrame", "boolean", "option to display the frame"},
            {"FrameThickness", "integer", "the thickness of the clock frame"},
            {"FrameStyle", "String", "various styles for the frame"},
            {"FrameColor", "String", "color for the clock frame"},
            {"ShowDate", "boolean", "option to display the date"},
            {"DatePosition", "integer", "date position"},
            {"DateColor", "String", "color for the date"},
            {"DateFontName", "String", "font name for the date"},
            {"DateFontStyle", "String", "style name for the date"},
            {"DateFontSize", "String", "font size for the date"},
        };

        return info;
    }

}

