/*
 *
 * Copyright (c) 1998 IGELIN Software, Christoph Wagner
 * All Rights Reserved.
 *
 * IGELIN Software, Christoph Wagner (the author) grant you ("Licensee") a 
 * non-exclusive, royalty free, license to use and redistribute this 
 * software in licenxe and binary code form, provided that i) this copyright 
 * notice and license appear on all copies of the software; and ii) Licensee 
 * does not utilize the software in a manner which is disparaging to the authors.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. THE AUTHOR AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING OR DISTRIBUTING 
 * THE SOFTWARE. IN NO EVENT WILL THE AUTHOR OR THE LICENSORS BE LIABLE FOR ANY 
 * LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, 
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF 
 * LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF THE 
 * AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 */

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.applet.*;

public class MonsterClock extends Applet implements Runnable, ItemListener
{
  private int pts;  // Anzahl der Uhrpunkte
  private int secPtr, minPtr, hourPtr;  // ArrayNr des Sekunden, Minuten und Stundenzeigers
  private double minAngle = 0.1047197551;  // Winkel um den der Minuten- und Stundenzeiger weiterrückt
  private double hourAngle = 0.5235987756;  // Winkel auf den der Stundenzeiger eingestellt wird
  // Position der Uhrpunkte und der Startpunkte der Uhrzeiger
  private double x[]={  0, 25,  45, 50, 45, 25,  0,-25,-45,-50,-45,-25, // vordere Reihe
                        0, 25,  45, 50, 45, 25,  0,-25,-45,-50,-45,-25, // hintere Reihe
                        0,  0,  38, 30,  0,  0,-38,-30, 30, 27, 30, 27,-30,-27,-30,-27,  //Ziffernblatt
                       60,-60,   2, -2,  0,  0,  0,  0,  0};                     // die Zeigerpunkte
  private double y[]={-50,-45, -25,  0, 25, 45, 50, 45, 25,  0,-25,-45, // vordere Reihe
                      -50,-45, -25,  0, 25, 45, 50, 45, 25,  0,-25,-45, // hintere Reihe
                      -38,-30,   0,  0, 38, 30,  0,  0,-30,-27, 30, 27, 30, 27,-30,-27,  //Ziffernblatt
                        0,  0,   0,  0, -60, 60,-50,-40,-22};                     // die Zeigerpunkte
  private double z[]={  0,  0,   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // vordere Reihe
                       10, 10,  10, 10, 10, 10, 10, 10, 10, 10, 10, 10,  // hintere Reihe
                        5,  5,   5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  5,  //Ziffernblatt
                        5,  5,   5,  5,  5,  5,  5,  5,  5};                     // die Zeigerpunkte
  private double xt[],yt[],zt[];  // Arrays der transformierten Werte
  private double AxSec, AxMin, AxHour, Ay, Az;  // Winkelwerte
  private double da;  // Geschwindigkeit der Winkeländerung
  private Thread myThread;  // Thread
  private int secs;  // temporären Speicherung des Sekundenwertes
  private myclockcan uhrflaeche;  // Malflaeche der Uhr
  private Choice gmt;  // Auswahlfläche zur Zeitzone
  private int zzchoice; // momentan aktive zeitzone
  private Checkbox zdreh, ydreh;
  // globale Fonts
  private boolean fontsSet = false;
  private Font f_times;

  public void init()
  {
    System.out.println ("\n\n********************************************************");
    System.out.println ("*                                                      *");
    System.out.println ("*                     MonsterClock                     *");
    System.out.println ("*                                                      *");
    System.out.println ("********************************************************");
    System.out.println ("* Copyright (c) 1998 IGELIN Software, Christoph Wagner *");
    System.out.println ("********************************************************");

    System.out.println ("passing applet.init()");

    setLayout (new BorderLayout());
    // ********************************* Layout ****************************************
    // Menü einrichten mit den angebotenen Zeitzonen
    Panel optionen = new Panel(new BorderLayout());
    Panel p2 = new Panel (new FlowLayout(FlowLayout.CENTER,0,0));
    zdreh = new Checkbox("Z-Drehung", true);
    ydreh = new Checkbox("Y-Drehung", true);
    p2.add(zdreh);
    p2.add(ydreh);
    optionen.add ("North", p2);
    gmt = new Choice();
    gmt.addItem("GMT-12 (Eniwetok, Kwajalein)");
    gmt.addItem("GMT-11 (Midway, Samoa)");
    gmt.addItem("GMT-10 (Hawaii)");
    gmt.addItem("GMT-9 (Alaska)");
    gmt.addItem("GMT-8 (Pacific (USA, Kanada), Tijuana)");
    gmt.addItem("GMT-7 (Arizona, Mountain (USA, Kanada))");
    gmt.addItem("GMT-6 (Mexico Stadt, Central (USA, Kanada), Saskatchewan)");
    gmt.addItem("GMT-5 (Bogota, Lima, Eastern (USA, Kanada))");
    gmt.addItem("GMT-4 (Atlantic (Kanada), Caracas, La Paz");
    gmt.addItem("GMT-3 (Brasilia, Buenos Aires)");
    gmt.addItem("GMT-2 (Mittelatlantik)");
    gmt.addItem("GMT-1 (Azoren)");
    gmt.addItem("GMT (London, Monrovia, Casablanca)");  
    gmt.addItem("GMT+1 (Paris, Berlin, Madrid, Prag)");  
    gmt.addItem("GMT+2 (Harare, Prätoria, Israel, Kairo)");  
    gmt.addItem("GMT+3 (Bagdad, Kuwait, Nairobi, Moskau, St. Petersburg)");  
    gmt.addItem("GMT+4 (Abu Dhabi, Muskat, Tiflis, Wolgograd)");  
    gmt.addItem("GMT+5 (Islamabad, Karatschi, Taschkent)");  
    gmt.addItem("GMT+6 (Almaty, Dakka)");  
    gmt.addItem("GMT+7 (Bangkok, Jakarta, Hanoi)");
    gmt.addItem("GMT+8 (Honkong, Perth, Singapur, Taipeh, Peking)");
    gmt.addItem("GMT+9 (Tokio, Osaka, Sapporo, Seoul, Irkutsk)");
    gmt.addItem("GMT+10 (Brisbane, Melbourne, Sydney, Wladiwostok, Guam)");
    gmt.addItem("GMT+11 (Magadan, Neu-Kaledonien)");
    gmt.addItem("GMT+12 (Fidschi, Wellington, Auckland)");
    gmt.select(13);
  	zzchoice = 1;
    gmt.addItemListener (this);
    optionen.add("South", gmt);
    add("South", optionen);
    setFonts();
    Label copyright = new Label ("Copyright (c) 1998 IGELIN Software, Christoph Wagner", Label.CENTER);
    copyright.setForeground (Color.red);
    copyright.setFont(f_times);
    add("North", copyright);
    uhrflaeche = new myclockcan (150, 150);
    add ("Center", uhrflaeche);
    // ********************************* Layout Ende **********************************
    Calendar calendar = new GregorianCalendar();  // neuen Calendar erzeugen
    TimeZone tz=TimeZone.getDefault();
    tz.setRawOffset(zzchoice*3600000);
    calendar.setTimeZone(tz);
    calendar.setTime(new Date());  // lokale Zeit in Kalender eintragen
    // Arraygröße eintragen
    pts=x.length;
    secPtr = pts-3;
    minPtr = pts-2;
    hourPtr= pts-1;
    xt=new double[pts]; yt=new double[pts]; zt=new double[pts];  // Transformationsarrays erzeugen
    da=Math.PI/100;  // Winkelgeschwindigkeit definieren
    secs = calendar.get(Calendar.SECOND);  // Sekunden speichern
    AxSec =  minAngle*calendar.get(Calendar.SECOND); // Startwinkel der Sekunden definieren
    AxMin =  minAngle*calendar.get(Calendar.MINUTE); // Startwinkel der Minuten definieren
    // Startwinkel der Stunde definieren + etwas Übertrag für dei bereits in dieser
    // Stunde vergangene Zeit
    AxHour= hourAngle*calendar.get(Calendar.HOUR)+(calendar.get(Calendar.MINUTE)*0.008726646258);
    // System-Zeit ausgeben
    if (calendar.get(Calendar.MINUTE)==1) 
      System.out.println ("Mit dem Schlag des Quarz wurde es: "+calendar.get(Calendar.HOUR_OF_DAY)+" Uhr und "
      +calendar.get(Calendar.MINUTE)+" Minute");
    else
      System.out.println ("Mit dem Schlag des Quarz wurde es: "+calendar.get(Calendar.HOUR_OF_DAY)+" Uhr und "
      +calendar.get(Calendar.MINUTE)+" Minuten");
    // Thread starten
    myThread=new Thread(this);
    myThread.start();
  }
  
  public void save (Frame f)
  {
    try
    {
      ObjectOutputStream p = new ObjectOutputStream(new FileOutputStream("MonsterClock.data"));
      p.writeInt (gmt.getSelectedIndex());
      p.writeBoolean (ydreh.getState());
      p.writeBoolean (zdreh.getState());
      p.writeObject (f.getBounds());
      p.writeDouble (Ay);
      p.writeDouble (Az);
      p.flush();
      //ostream.close();
      p.close();
    }
    catch (IOException e)
    {
    }
  }
  
  public void load(Frame f)
  {
    try
    {
      ObjectInputStream p = new ObjectInputStream(new FileInputStream("MonsterClock.data"));
      gmt.select (p.readInt ());
      ydreh.setState(p.readBoolean ());
      zdreh.setState(p.readBoolean ());
      f.setBounds ((Rectangle) p.readObject ());
      Ay = p.readDouble ();
      Az = p.readDouble ();
      //istream.close();
      p.close();
    }
    catch (IOException e)
    {
    }
    catch (ClassNotFoundException e)
    {
    }
    itemStateChanged (null);
  }
  
  public void itemStateChanged(ItemEvent e)
  {
    switch (gmt.getSelectedIndex())
	  {
      case (0):zzchoice=-12;break;
      case (1):zzchoice=-11;break;
      case (2):zzchoice=-10;break;
      case (3):zzchoice=-9;break;
      case (4):zzchoice=-8;break;
      case (5):zzchoice=-7;break;
      case (6):zzchoice=-6;break;
      case (7):zzchoice=-5;break;
      case (8):zzchoice=-4;break;
      case (9):zzchoice=-3;break;
      case (10):zzchoice=-2;break;
      case (11):zzchoice=-1;break;
      case (12):zzchoice=0;break;
      case (13):zzchoice=1;break;
      case (14):zzchoice=2;break;
      case (15):zzchoice=3;break;
      case (16):zzchoice=4;break;
      case (17):zzchoice=5;break;
      case (18):zzchoice=6;break;
      case (19):zzchoice=7;break;
      case (20):zzchoice=8;break;
      case (21):zzchoice=9;break;
      case (22):zzchoice=10;break;
      case (23):zzchoice=11;break;
      case (24):zzchoice=12;break;
    }
  }
  
  public void run()
  {
    while(myThread!=null)
    {
      try 
      { 
        myThread.sleep(50);
      }
      catch ( InterruptedException e) 
      {
      }
      // alle Punkte weiterrechnen
      rotatePoints( x, y, z, xt, yt, zt, pts-3, 0, Ay, Az);
      // die zeitzeigerweiterrechnen
      rotatePtr (x, y, z, xt, yt, zt, secPtr, AxSec, Ay, Az);
      rotatePtr (x, y, z, xt, yt, zt, minPtr, AxMin, Ay, Az);
      rotatePtr (x, y, z, xt, yt, zt, hourPtr, AxHour, Ay, Az);
      Calendar calendar = new GregorianCalendar(); // neuen Calendar erzeugen
      TimeZone tz=TimeZone.getDefault();
      tz.setRawOffset(zzchoice*3600000);
      calendar.setTimeZone(tz);
      calendar.setTime(new Date());  // lokale Zeit in Kalender mit GMT eintragen
      // wenn eine sekunde weiter...
      if (calendar.get(Calendar.SECOND) != secs)
      {
        // wenn eine minute weiter...
        if (secs > calendar.get(Calendar.SECOND))
        {
          AxMin  = minAngle*calendar.get(Calendar.MINUTE);  // neuen Minutenwinkel berechnen
          if (calendar.get(Calendar.MINUTE)==1) // die neue Uhrzeit textuell ausgeben
            System.out.println ("Mit dem Schlag des Quarz wurde es: "+calendar.get(Calendar.HOUR_OF_DAY)+" Uhr und "
            +calendar.get(Calendar.MINUTE)+" Minute");
          else
            System.out.println ("Mit dem Schlag des Quarz wurde es: "+calendar.get(Calendar.HOUR_OF_DAY)+" Uhr und "
            +calendar.get(Calendar.MINUTE)+" Minuten");
        }
        // Startwinkel der Stunde definieren + etwas Übertrag für dei bereits in dieser
        // Stunde vergangene Zeit
        AxHour = hourAngle*calendar.get(Calendar.HOUR)+(calendar.get(Calendar.MINUTE)*0.008726646258);
        secs = calendar.get(Calendar.SECOND);  // Sekundenwert speichern
        AxSec = minAngle*secs;  // Startwinkel der Sekunden definieren
      }
      // die Drehwinkel inkrementieren
      if (zdreh.getState())
        Az+=da;
      if (ydreh.getState())
        Ay+=da*0.3;
      uhrflaeche.repaint();  // zeichnen veranlassen
    }
  }
  
  public void start()
  {
    System.out.println ("passing applet.start()");
    if(myThread==null)
    { 
      myThread=new Thread(this);myThread.start();
    }
  }
  
  public void stop()
  {
    System.out.println ("passing applet.stop()");
    if(myThread!=null)
    { 
      myThread.stop(); myThread=null;
    }
  }
  
  public void update (Graphics g)
  {
    // wenn applet neu malen, dann die uhr zeichnen lassen
    uhrflaeche.repaint();
  }
  
  // rechnet die Rotation für einen Wert
  private void rotatePtr (double xs[],double ys[],double zs[], double xd[],double yd[],double zd[], int ptr,
                          double a,double b,double c)
  {
    double Xa,Ya,Za,Xb,Yb,Zb;
    // Rotation des Punktes in x-y-Fläche
    Xa = xs[ptr]*Math.cos(a)-ys[ptr]*Math.sin(a);
    Ya = xs[ptr]*Math.sin(a)+ys[ptr]*Math.cos(a);
    Za = zs[ptr];
    // Rotation des entstandenen Punktes in y-z-Fläche
    Xb = Xa;
    Yb = Ya*Math.cos(b)-Za*Math.sin(b);
    Zb = Ya*Math.sin(b)+Za*Math.cos(b);
    // Rotation des entstandenen Punktes in z-x-Fläche
    xd[ptr] = Xb*Math.cos(c)+Zb*Math.sin(c);
    yd[ptr] = Yb;
    zd[ptr] = -Xb*Math.sin(c)+Zb*Math.cos(c);
  }
  
  // berechnet Rotation für eine Menge von Punken im Array
  private void rotatePoints (double xs[],double ys[],double zs[],double xd[],double yd[],double zd[],int pts,
                             double a,double b,double c)
  {
    for(int n=0;n<pts;n++)
    {
      double Xa,Ya,Za,Xb,Yb,Zb;
      // Rotation des Punktes in x-y-Fläche
      Xa = xs[n]*Math.cos(a)-ys[n]*Math.sin(a);
      Ya = xs[n]*Math.sin(a)+ys[n]*Math.cos(a);
      Za = zs[n];
      // Rotation des enstandenen Punktes in y-z-Fläche
      Xb = Xa;
      Yb = Ya*Math.cos(b)-Za*Math.sin(b);
      Zb = Ya*Math.sin(b)+Za*Math.cos(b);
      // Rotation des enstandenen Punktes in z-x-Fläche
      xd[n] = Xb*Math.cos(c)+Zb*Math.sin(c);
      yd[n] = Yb;
      zd[n] = -Xb*Math.sin(c)+Zb*Math.cos(c);
    }
  }

  // initialisiert einmalig die Fonts
  private void setFonts ()
  {
    if (fontsSet) return;
    f_times = new Font ("TimesRoman", Font.PLAIN, 12);
    fontsSet = true;
  }

  // innerclass zur Implementation der Malflaeche als Canvas-Ableitung
  public class myclockcan extends Canvas
  {
    private Image clock=null; // Image zum Zeichnen im Hintergrund um Flimmern zu Vermeiden
    private int sizeX, sizeY;
    
    public myclockcan (int sizex, int sizey)
    {
      sizeX = sizex; sizeY = sizey;
      this.setSize (sizeX, sizeY);
    }

    public void initclock ()
    {
      clock = this.createImage(sizeX, sizeY);
    }
  
    public void update (Graphics gc)
    {
      // überschrieben, um flackern zu verhindern
      paint (this.getGraphics());
    }
   
    public void paint (Graphics gc)
    {
      if (clock==null)
      {
        initclock();
        return;  // sonst Fehler auf langsamen Rechner bei Doppelpufferung
      }
      Graphics g = clock.getGraphics();  // Graphics-Kontext für Hintergrundzeichnen holen.
      g.setColor(Color.white);  // Den Hintergrund weiß löschen (normalerweise in Component.update)
      g.fillRect (0,0,150,150);
      setFonts ();  // Fonts initialisieren
      int Xo=clock.getWidth(this)>>1,Yo=clock.getHeight(this)>>1;  // Größen ermitteln und / 2 dividieren
      for(int n=0;n<pts;n++)
      {
        // x und y aus der Tabelle der transformierten Werte holen
        int x=(int)xt[n];
        int y=(int)yt[n];
        if (n == secPtr)  // wenn Indize n auf den Sekundenzeiger zeigt
        {
          g.setColor(Color.red); // Farbe auf Rotsetzen
          g.drawLine(Xo,Yo,Xo+x,Yo+y);  // Sekundenzeiger malen
        }
        else if (n == minPtr)  // Wenn Indize auf Minutenzeiger zeigt
        {
          g.setColor(Color.darkGray);  // Farbe auf Dunkelgrau setzen
          g.drawLine(Xo,Yo,Xo+x,Yo+y);  // Minutenzeiger malen
        }
        else if (n == hourPtr)  // Wenn Indize auf Stundenzeiger zeigt
        {
          g.setColor(Color.darkGray);  // Farbe auf Dunkelgrau setzen
          g.drawLine(Xo,Yo,Xo+x,Yo+y);  // Stundenzeiger malen
        }
        else if (n<24)  // wenn indize auf normalen uhrpukt zeigt
          g.fillOval(Xo+x-1, Yo+y-2,4,4); // uhrpunktgnubbel malen
      }

      // die Beschriftung zeichnen
      g.drawString( "3", Xo+(int)xt[pts-9]+(int)xt[pts-7], Yo+(int)yt[pts-9]+5);
      g.drawString( "6", Xo+(int)xt[pts-4]-1,             Yo+(int)yt[pts-4]+5);
      g.drawString( "9", Xo+(int)xt[pts-8]+(int)xt[pts-7], Yo+(int)yt[pts-8]+5);
      g.drawString( "12",Xo+(int)xt[pts-5]-4,             Yo+(int)yt[pts-5]+5);

      // Uhrstriche im Ziffernblatt malen
      for (int i=24;i<pts-10;i+=2)
        g.drawLine (Xo+(int)xt[i],Yo+(int)yt[i],Xo+(int)xt[i+1],Yo+(int)yt[i+1]);
      
      // Grafikschnickschnack aussen machen
      int xi[]= new int[12], yi[]= new int[12];
      for (int dd=0;dd<12;dd++)
      {
        xi[dd]=Xo+(int)xt[dd];
        yi[dd]=Yo+(int)yt[dd];
      }
      g.drawPolygon (xi, yi, 12);
      for (int dd=0;dd<12;dd++)
      {
        xi[dd]=Xo+(int)xt[dd+12];
        yi[dd]=Yo+(int)yt[dd+12];
      }
      g.drawPolygon (xi, yi, 12);

      g.setColor (Color.gray);
      for (int dd=0;dd<12;dd++)
      {
        xi[0]=Xo+(int)xt[dd];
        yi[0]=Yo+(int)yt[dd];
        xi[2]=Xo+(int)xt[dd+12];
        yi[2]=Yo+(int)yt[dd+12];
        if (dd<11)
        {
          xi[1]=Xo+(int)xt[dd+1];
          yi[1]=Yo+(int)yt[dd+1];
          xi[3]=Xo+(int)xt[dd+13];
          yi[3]=Yo+(int)yt[dd+13];
        }
        else
        {
          xi[1]=Xo+(int)xt[0];
          yi[1]=Yo+(int)yt[0];
          xi[3]=Xo+(int)xt[dd+1];
          yi[3]=Yo+(int)yt[dd+1];
        }
        g.fillPolygon (xi, yi, 4);    
      }
      // Verbindungslinien zw. der vorderen und der hinteren Reihe malen
      for (int dd=0;dd<12;dd++)
      {
        g.drawLine (Xo+(int)xt[dd],Yo+(int)yt[dd],Xo+(int)xt[dd+12],Yo+(int)yt[12+dd]);
      }
      Dimension di = uhrflaeche.getSize ();
      Xo=di.width>>1; Yo=di.height>>1;
      gc.drawImage (clock,Xo-75,Yo-75,this); // das Bild endlich darstellen
    }
  }

  // innerclass zur Eventabfrage beim Aufruf der Uhr as application
  public class ClientWindowListener extends WindowAdapter
  {
    private Frame ff;
    public ClientWindowListener (Frame f)
    {
      ff = f;
    }
   
    public void windowClosing (WindowEvent e) 
    {
      save(ff);
      System.exit(0);  // Programm verlassen
    }
  }

  // Hauptroutine
  public static void main (String argv[])
  {
    Frame f = new Frame ("MonsterClock v0.0.0.1"); // neues Fenster
    f.setBounds(50, 50, 400, 300); // Größe festlegen
    MonsterClock applet = new MonsterClock(); // Uhr erzeugen
    f.add(applet); // Uhr zum Frame hinzufügen
    f.addWindowListener (applet. new ClientWindowListener(f)); // Listener zum Frame hinzufügen
    f.setVisible (true); // Frame zeigen
    applet.init(); // Uhr starten
    applet.start();
    applet.load(f);
    f.setVisible (true); // sorgt für korrekte Darstellung: Bug im AWT !!
  }
}
