/*
 * AnalogClock.java      v1.0    Jan 8, 1997
 *
 * Copyright (c) 1997 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.
 *
 * Version 1.0  Jan 8 1996           Initial version
 */

import java.lang.Math;
import java.util.*;
import java.awt.*;
import java.applet.*;

public class AnalogClock extends Clock {

    static final private String clockname  = "AnalogClock 97";
    static final private String clocksmith = "Clocksmith: H.J. Tsai (c) 1997";

    static final private int SHOW_MARK_ALL = 1;
    static final private int SHOW_MARK_LARGE = 2;
    static final private int SHOW_MARK_NONE = 3;
    private int showMark = SHOW_MARK_ALL;
    private Color clrMark = Color.black;

    private boolean showRoundFrame = true;
    private int roundFrameWidth = 5;
    private Color clrRoundFrame = Color.red;
    private Color clrHandHour = Color.blue;
    private Color clrHandMinute = Color.blue;
    private Color clrHandSecond = Color.red;                          
    private Color clrCenterDot = Color.black;
    private Color clrClockFace = Color.yellow;
    private Color clrClockBackground = #ffffee;

    // the lengths of tails of the hands
    // tails are the other ends of the clock hands (closer to the center)
    private int lenHourTail = 8;
    private int lenMinuteTail = 10;
    private int lenSecondTail = 16;

    // widths of the clock hands (distance from the center to the tip of the side)
    private int widthHour = 5;
    private int widthMinute = 4;
    private int widthSecond = 3;

    private int radiusCenterDot = 2;
    private boolean counterClockwise = false;
    private boolean showFrame = true;
    private Color clrFrame = Color.black;
    private int frameThickness = 5;
    private int frameStyle = Rect.RS_NORMAL;
    private int outBevel = 0;
    private int outBevelStyle = Rect.RS_3DRAISED;
    private int inBevel = 0;
    private int inBevelStyle = Rect.RS_3DINSET;

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

    // internal variables
    private RectFrame rectFrame;
    private Rect rectClock;
    private int radius;
    private int xcenter;
    private int ycenter;
    private double xtrigtable[] = new double [61]; // work around netscape 3.0 bug
    private double ytrigtable[] = new double [60];
    private int lasts = -1;
    private int lastm = -1;
    private int lasth = -1;

    private int xpoints[] = new int[4];
    private int ypoints[] = new int[4];
    
    // last hand positions on the screen
    private Point   slastp[] = new Point[4];
    private Point   mlastp[] = new Point[4];
    private Point   hlastp[] = new Point[4];
    
    private Point   p0 = new Point(0,0);
    private Point   p1 = new Point(0,0);
    private Point   p2 = new Point(0,0);
    private Point   p3 = new Point(0,0);
    private Image   memImage;
    private boolean showCopyright = true;

    private Point   marks[] = new Point[60];
    private Point   marks2[] = new Point[60];

    public String getAppletInfo() {
        String info = clockname + " " + clocksmith;
        return info;
    }

    public String [][] getParamterInfo() {
        String info[][] = {
            {"ShowFrame", "boolean", "option to display the frame"},
            {"OutBevel", "integer", "Outer frame bevel width"},
            {"OutBevelStyle", "String", "Outer frame bevel style"},
            {"InBevel", "integer", "Inner Frame bevel width"},
            {"InBevelStyle", "String", "Inner frame bevel style"},
            {"FrameStyle", "String", "various styles for the frame"},
            {"FrameThickness", "integer", "the thickness of the clock frame"},
            {"FrameColor", "String", "color for the clock frame"},
        };

        return info;
    }

//
// formula for ellipses
//  x**2/a**2 + y**2/b**2 = 1;
//  b = sqrt(a**2 - c**2);
//
//  if (b > a)
//      swap(a,b) a = b, b = a;
//  x2/a2 = 1 - y2/b2 
//

    public void init() {

        super.init();

        double sintable[] = new double [8];
        double costable[] = new double [8];
        double delta = Math.PI * 2.0 / 60.0;
        double angle = 0f;
        for (int i = 0; i < 8; i++) {
             sintable[i] = Math.sin(angle);
             costable[i] = Math.cos(angle);
             angle += delta;
        }

        // setup the trig table to have 0-59
        // clock marker positions]
        xtrigtable[0] = 0;
        ytrigtable[0] = 1;

        // 0-15
        for (int i = 1; i <= 7; i++) {
             xtrigtable[i]    = sintable[i];
             xtrigtable[7+i]  = costable[8-i];
             ytrigtable[i]    = costable[i];
             ytrigtable[7+i]  = sintable[8-i];
        }
    
        // 16-59
        for (int i=0; i < 15; i++) {
             xtrigtable[15+i] = ytrigtable[i];
             ytrigtable[15+i] = -xtrigtable[i];
            
             xtrigtable[30+i] = -xtrigtable[i];
             ytrigtable[30+i] = -ytrigtable[i];

             xtrigtable[45+i] = -ytrigtable[i];
             ytrigtable[45+i] = xtrigtable[i];
        }

        // xtrigtable[] and ytrigtable[] now has the standard
        // (x,y) coordinates in the cartensian space
        // where (0,0) is the origin and the radius is 1
        // On the screen <0,0> is upper left hand corner,
        // y values are the exact oppsite to what we have
        // so negate them
        for (int i = 0; i < 60; i++) {
             ytrigtable[i] = -ytrigtable[i];
        }

        Rectangle r = bounds();
        memImage = createImage(r.width, r.height);

        // get program options
        getProgramParams();
        if (showZoneTime) {
            super.setTimezoneDiff(tztarget);
        }

        // for the couter clock wise
        if (counterClockwise)
            for (int i = 0; i < 60; i++) 
                 xtrigtable[i] = -xtrigtable[i];
    
        // set up the size for the clock and its frame 
        if (showFrame) {
            rectFrame = new RectFrame(r,
                                      outBevel,
                                      outBevelStyle,
                                      frameThickness,
                                      frameStyle,
                                      clrFrame,
                                      inBevel,
                                      inBevelStyle);
            
             rectClock = rectFrame.getInsideRect();
         }
         else {
             rectClock = new Rect(r);
         }

         radius = Math.min(rectClock.width, rectClock.height);
         radius /= 2;

         // leave some room
         radius -= roundFrameWidth;
         xcenter = rectClock.x + rectClock.width / 2;
         ycenter = rectClock.y + rectClock.height / 2;

         for (int i = 0 ; i < 4; i++) {
              slastp[i] = new Point(0,0);
              mlastp[i] = new Point(0,0);
              hlastp[i] = new Point(0,0);
         }

         for (int i = 0; i < 60; i++) {
              marks[i] = new Point(0,0);
              marks2[i] = new Point(0,0);
         }

         double a, b;
         a = (rectClock.width-roundFrameWidth)/2.0;
         b = (rectClock.height-roundFrameWidth)/2.0;

         // make a,b smaller, so the marks will be inside
         // the elipse, not on it
         a -= 2;
         b -= 2;
         int x, y;
         int x2 = 0;
         int y2 = 0;
         int r2;

         //System.out.println("RectClock=" + rectClock);
         //System.out.println("a="+a+",b="+b);

         double longmark;
         double shortmark;

         if (a == b) {
             longmark = 0.85;
             shortmark = 0.95;
         }
         else {
             longmark = 0.85;
             shortmark = 0.95;
         }

         for (int i = 0; i <= 15; i++) {
              r2 = radius;

              // find the closest distance (radius)
              // where <x,y> is still inside the elipse
              do {
                  x = (int)(xtrigtable[i] * r2);
                  y = (int)(ytrigtable[i] * r2);
                  r2 += 2;
              } while((x*x)/(a*a) + (y*y)/(b*b) < 0.95);
        
              // we found it,
              // find the coordinates for the marker
              if (i % 5 == 0) {
                  x2 = (int) (xtrigtable[i] * r2 * longmark);
                  y2 = (int) (ytrigtable[i] * r2 * longmark);
              }
              else {
                  x2 = (int) (xtrigtable[i] * r2 * shortmark);
                  y2 = (int) (ytrigtable[i] * r2 * shortmark);
              }

              marks[i].x = x;
              marks[i].y = y;
            
              marks[30-i].x = x;
              marks[30-i].y = -y;
            
              marks[30+i].x = -x;
              marks[30+i].y = -y;

              marks[(60-i)%60].x = -x;
              marks[(60-i)%60].y = y;

              marks2[i].x = x2;
              marks2[i].y = y2;
            
              marks2[30-i].x = x2;
              marks2[30-i].y = -y2;
            
              marks2[30+i].x = -x2;
              marks2[30+i].y = -y2;

              marks2[(60-i)%60].x = -x2;
              marks2[(60-i)%60].y = y2;
          }

          for (int i = 0; i <= 59; i++) {
              marks[i].y += ycenter;
              marks[i].x += xcenter;
              marks2[i].x += xcenter;
              marks2[i].y += ycenter;
          }    
    }

   /**
    * 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) {
        paint(g);
    }

    public void paint(Graphics g) {
        if (showCopyright) {
            showCopyright(g, clockname, clocksmith);
            showCopyright = false;
            return;
        }

        // update the clock in offscreen memory image
        Graphics memg = memImage.getGraphics();

        // clear old time
        memg.setPaintMode();
        Rectangle r = g.getClipRect();
        memg.clipRect(r.x, r.y, r.width, r.height);
        memg.setColor(clrClockBackground);
        memg.fillRect(r.x, r.y, r.width, r.height);

        if (showFrame)
            drawFrame(memg);

        if (showRoundFrame) {
            drawRoundFrame(memg);
        }
    
        if (showMark != SHOW_MARK_NONE) {
            drawMarks(memg);
        }

        int s = getDisplaySeconds();
        int m = getDisplayMinutes();
        int h = getDisplayHours();

        //System.err.println("h="+h + " m="+m + " s=" + s);

        // hour positions: 1-12 only
        if (h >= 12)
            h -= 12;

        drawHand(memg, h*5 + m/12, 
                 hlastp, clrHandHour, radius/2, lenHourTail, widthHour);
        drawHand(memg, m, mlastp, clrHandMinute, radius*3/4, lenMinuteTail, widthMinute);
        drawHand(memg, s, slastp, clrHandSecond, radius, lenSecondTail, widthSecond);

        memg.setColor(clrCenterDot);
        int circlesize = radiusCenterDot + radiusCenterDot;    // * 2;
        memg.fillOval(xcenter-radiusCenterDot,
                      ycenter-radiusCenterDot, 
                      circlesize, circlesize);

        // copy the offscreen image to the real screen
        g.drawImage(memImage, 0, 0, null);
    }

    public void drawHand(Graphics g, 
                         int t, 
                         Point lastp[],
                         Color c, 
                         int radius,
                         int tail,
                         int width) {
            
        // new coordinate for the hand's tip
        Point tip = new Point(0,0);
        tip.x = (int)(xtrigtable[t] * radius + xcenter);
        tip.y = (int)(ytrigtable[t] * radius + ycenter);

        // new coordinate for the second's rear end and side points
        p0.x = xcenter + (int)(xtrigtable[(t+30)%60] * tail);
        p0.y = ycenter + (int)(ytrigtable[(t+30)%60] * tail);
        p1.x = xcenter + (int)(xtrigtable[(t+15)%60] * width);
        p1.y = ycenter + (int)(ytrigtable[(t+15)%60] * width);
        p2.x = xcenter + (int)(xtrigtable[(t+45)%60] * width);
        p2.y = ycenter + (int)(ytrigtable[(t+45)%60] * width);

        g.setColor(c);
        xpoints[0] = p0.x;
        ypoints[0] = p0.y;
        xpoints[1] = p1.x;
        ypoints[1] = p1.y;
        xpoints[2] = tip.x;
        ypoints[2] = tip.y;
        xpoints[3] = p2.x;
        ypoints[3] = p2.y;
        g.fillPolygon(xpoints, ypoints, 4);
    }

    public void drawFrame(Graphics g) {
        rectFrame.paint(g);
    }

    public void drawRoundFrame(Graphics g) {
        g.setColor(clrRoundFrame);
        for (int i = 0; i < roundFrameWidth; i++) 
            g.drawArc(rectClock.x+i,
                      rectClock.y+i, 
                      rectClock.width-i*2,
                      rectClock.height-i*2,
                      0, 360);

        g.setColor(clrClockFace);
        g.fillArc(rectClock.x+roundFrameWidth,
                  rectClock.y+roundFrameWidth, 
                  rectClock.width-roundFrameWidth*2,
                  rectClock.height-roundFrameWidth*2,
                  0, 360);


    }

    public void drawMarks(Graphics g) {
        int x0, y0;
        int x1, y1;
        double d;

        g.setColor(clrMark);

        if (showMark == SHOW_MARK_NONE)
            return;

        if (showMark == SHOW_MARK_ALL) {
            for (int i = 0; i < 60; i++) {
                g.drawLine(marks[i].x, marks[i].y,
                           marks2[i].x, marks2[i].y); 
            }

        }
        else { // show large marks only
            for (int i = 0; i < 60; i += 5) {
                g.drawLine(marks[i].x, marks[i].y,
                           marks2[i].x, marks2[i].y); 

            }
        }
    }

   /**
    * Gets program pamameters
    */
    protected void getProgramParams() {   

        // get program parameters
        String arg;     

        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 second
        arg = getParameter("CounterClockwise");
        if (arg != null) 
            counterClockwise = arg.equalsIgnoreCase("yes")
                               || arg.equalsIgnoreCase("true");

        arg = getParameter("ShowMark");
        if (arg.equalsIgnoreCase("none"))
            showMark = SHOW_MARK_NONE;
        else if (arg.equalsIgnoreCase("large"))
            showMark = SHOW_MARK_LARGE;
        else
            showMark = SHOW_MARK_ALL;

        // Round Frame Color
        arg = getParameter("MarkColor");
        if (arg != null)
            clrMark = ColorValue.s2color(arg, Color.black);
    

        arg = getParameter("ShowRoundFrame");
        if (arg != null)
            showRoundFrame = arg.equalsIgnoreCase("yes")
                             || arg.equalsIgnoreCase("true");

        roundFrameWidth = getNumParam("roundFrameWidth", 4);
        if (roundFrameWidth < 1)
            roundFrameWidth = 1;


        // Round Frame Color
        arg = getParameter("RoundFrameColor");
        if (arg != null)
            clrRoundFrame = ColorValue.s2color(arg, Color.black);
    
        // clock face color
        arg = getParameter("ClockFaceColor");
        if (arg != null)
            clrClockFace = ColorValue.s2color(arg, Color.yellow);
        
        // BackgroundColor
        arg = getParameter("BackgroundColor");
        if (arg != null)
            clrClockBackground = ColorValue.s2color(arg, Color.lightGray);

        // Hand Colors
        arg = getParameter("HourHandColor");
        if (arg != null)
            clrHandHour = ColorValue.s2color(arg, Color.blue);

        arg = getParameter("MinuteHandColor");
        if (arg != null)
            clrHandMinute = ColorValue.s2color(arg, Color.blue);

        arg = getParameter("SecondHandColor");
        if (arg != null)
            clrHandSecond = ColorValue.s2color(arg, Color.red);

        // hand sizes
        lenHourTail = getNumParam("LengthHourTail", 8);
        if (lenHourTail < 1)
            lenHourTail = 8;

        lenMinuteTail = getNumParam("LengthMinuteTail", 12);
        if (lenMinuteTail < 1)
            lenMinuteTail = 12;
        
        lenSecondTail = getNumParam("LengthSecondTail", 16);
        if (lenSecondTail < 1)
            lenSecondTail = 16;

        // hand fatness
        widthHour = getNumParam("HourWaistSize", 5);
        if (widthHour < 1)
            widthHour = 5;

        widthMinute = getNumParam("MinuteWaistSize", 4);
        if (widthMinute < 1)
            widthMinute = 4;
        
        widthSecond = getNumParam("SecondWaistSize", 3);
        if (widthSecond < 1)
            widthSecond = 3;

        // center circle color
        arg = getParameter("CenterDotColor");
        if (arg != null)
            clrCenterDot = ColorValue.s2color(arg, Color.black);

        // center dot size
        radiusCenterDot = getNumParam("CenterDotRadius", 2);
        if (radiusCenterDot < 0)
            radiusCenterDot = 2;

        // 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.black);

        // get clock frame thickness
        frameThickness = getNumParam("FrameThickness", 4);
        if (frameThickness < 1)
            frameThickness = 1;

    }

   /**
    * 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;
    }


   /**
    * 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;
    }
}