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

//***** Class TwentyFourHourClock **********************************************
public class TwentyFourHourClock extends BufferedApplet
{
	//Base sprocket coordinates.  This will eventually be transformed to form
	//the rest of the clock
	static final int NUM_POINTS_SPROCKET = 48;
	static final int NUM_POINTS_HAND = 4;
	static final int COLUMN_X = 0;
	static final int COLUMN_Y = 1;
	static final int TIME_ADJUST = 1;
		//TIME_ADJUST of 27 seems to give the best simulation of real time although
		//it is very imprecise
	
	static final int SPROCKET_HOURS_X = 25;
	static final int SPROCKET_HOURS_Y = 25;
	static final int SPROCKET_MINUTES_X = -60;
	static final int SPROCKET_MINUTES_Y = -50;
	static final int SPROCKET_SECONDS_X = 65;
	static final int SPROCKET_SECONDS_Y = -90;
		//Constants for the positions of the sprockets in the screen
	
	int WIDTH = 0;
	int HEIGHT = 0;
	boolean bIsInited = false;
	
	static double SprocketBase[][] = { {124, 0, 1}, {150, 0, 1}, {153, 28, 1}, 
		{176, 35, 1}, {193, 12, 1}, {216, 25, 1}, {205, 50, 1}, {222, 69, 1}, 
		{248, 58, 1}, {260, 80, 1}, {239, 96, 1}, {244, 120, 1}, {273, 123, 1}, 
		{273, 150, 1}, {244, 153, 1}, {239, 178, 1}, {260, 192, 1}, {248, 216, 1}, 
		{222, 205, 1}, {205, 222, 1}, {216, 247, 1}, {193, 260, 1}, {176, 238, 1}, 
		{153, 245, 1}, {150, 273, 1}, {124, 273, 1}, {120, 245, 1}, {96, 238, 1}, 
		{79, 260, 1}, {56, 247, 1}, {68, 222, 1}, {52, 205, 1}, {26, 216, 1}, 
		{11, 192, 1}, {35, 178, 1},	{29, 153, 1}, {0, 150, 1}, {0, 123, 1}, 
		{27, 120, 1}, {34, 96, 1},{13, 80, 1}, {25, 58, 1}, {50, 69, 1}, 
		{69, 50, 1}, {58, 25, 1}, {80, 12, 1}, {97, 35, 1}, {120, 28, 1} };
		//Base coordinates for the sprockets, all of them start at these coordinates
		//they are then scaled and translated

	static double HandBase[][] = { {0, -175, 1}, {25, -25, 1},
		{0, 0, 1}, {-25, -25, 1} };

	Matrix2D Matrix2D = new Matrix2D();	
	static int iTickCount = -1;
	
	static double MatrixTransform[][] = new double[3][3];
	static double MatrixTemp[][] = new double[3][3];
	
	static double SprocketHours[][] = new double[NUM_POINTS_SPROCKET][3];
	static double SprocketMinutes[][] = new double[NUM_POINTS_SPROCKET][3];
	static double SprocketSeconds[][] = new double[NUM_POINTS_SPROCKET][3];
	static double SprocketCenter[][] = new double[NUM_POINTS_SPROCKET][3];
	
	static double HandHours[][] = new double[NUM_POINTS_HAND][3];
	static double HandMinutes[][] = new double[NUM_POINTS_HAND][3];
	static double HandSeconds[][] = new double[NUM_POINTS_HAND][3];
	
	static double NumberPositions[][] = new double[24][3];
	
	static Color clrHours = new Color(60,60,60, 220);
	static Color clrSeconds = new Color(100, 100, 100, 220);
	static Color clrMinutes = new Color( 140, 140, 140, 220);
	static Color clrCenter = new Color(200,200,200,128);
		//different colors for each of the sprockets and hands
		//depending on which part of the time they tell
		//they also have some transparency, it looks cooler
							
	public void Initialize()
	//need some kind of init method that will get everything into place
	//also set some colors, 
	{
		WIDTH = bounds().width;
		HEIGHT = bounds().height;
		
		//translate the base sprocket to the origin
		Matrix2D.Translate(MatrixTransform, -137, -137);
		for (int i=0; i<NUM_POINTS_SPROCKET; i++)
			Matrix2D.Transform(MatrixTransform, SprocketBase[i]);
		
		for (int i=0; i<NUM_POINTS_SPROCKET;i++)
		//Duplicates three instances of the sprocket of the clock
		{
			for (int j=0; j<3;j++)
			{
				SprocketHours[i][j] = SprocketBase[i][j];
				SprocketMinutes[i][j] = SprocketBase[i][j];
				SprocketSeconds[i][j] = SprocketBase[i][j];
				SprocketCenter[i][j] = SprocketBase[i][j];
			}
		}
			
		for (int i=0; i<NUM_POINTS_HAND;i++)
		//Duplicates three instances of the hand of the clock
		{
			for (int j=0; j<3;j++)
			{
				HandHours[i][j] = HandBase[i][j];
				HandMinutes[i][j] = HandBase[i][j];
				HandSeconds[i][j] = HandBase[i][j];
			}
		}
			
		//scale hour hand, 3/4 the height of the minute hand
		Matrix2D.Scale(MatrixTransform, 0.75, 0.75);
		for (int i=0; i<NUM_POINTS_HAND; i++)
			Matrix2D.Transform(MatrixTransform, HandHours[i]);
		
		//scale second hand, half the width of the minute hand
		Matrix2D.Scale(MatrixTransform, 0.2, 1);
		for (int i=0; i<NUM_POINTS_HAND; i++)
			Matrix2D.Transform(MatrixTransform, HandSeconds[i]);
		
		//scale hour sprocket
		Matrix2D.Scale(MatrixTransform, 1.2, 1.2);
		for (int i=0; i<NUM_POINTS_SPROCKET; i++)
			Matrix2D.Transform(MatrixTransform, SprocketHours[i]);
		
		//scale minute sprocket
		Matrix2D.Scale(MatrixTransform, 0.9, 0.9);
		for (int i=0; i<NUM_POINTS_SPROCKET; i++)
			Matrix2D.Transform(MatrixTransform, SprocketMinutes[i]);

		//scale second sprocket
		Matrix2D.Scale(MatrixTransform, 0.6, 0.6);
		for (int i=0; i<NUM_POINTS_SPROCKET; i++)
			Matrix2D.Transform(MatrixTransform, SprocketSeconds[i]);

		//scale center sprocket
		Matrix2D.Scale(MatrixTransform, 0.3, 0.3);
		for (int i=0; i<NUM_POINTS_SPROCKET; i++)
			Matrix2D.Transform(MatrixTransform, SprocketCenter[i]);
		
		//translate hour sprocket
		Matrix2D.Translate(MatrixTransform, SPROCKET_HOURS_X, SPROCKET_HOURS_Y);
		for (int i=0; i<NUM_POINTS_SPROCKET; i++)
			Matrix2D.Transform(MatrixTransform, SprocketHours[i]);
		
		//translate minute sprocket
		Matrix2D.Translate(MatrixTransform, SPROCKET_MINUTES_X, SPROCKET_MINUTES_Y);
		for (int i=0; i<NUM_POINTS_SPROCKET; i++)
			Matrix2D.Transform(MatrixTransform, SprocketMinutes[i]);
		
		//translate second sprocket
		Matrix2D.Translate(MatrixTransform, SPROCKET_SECONDS_X, SPROCKET_SECONDS_Y);
		for (int i=0; i<NUM_POINTS_SPROCKET; i++)
			Matrix2D.Transform(MatrixTransform, SprocketSeconds[i]);
		
		NumberPositions[0][0] = -5;
		NumberPositions[0][1] = -180;
		for (int i=1; i<24; i++)
			//sets up an array of coordinates
			//will be used later to put the numbers on the face of the clock
		{
			NumberPositions[i][0] = NumberPositions[i-1][0];
			NumberPositions[i][1] = NumberPositions[i-1][1];
			Matrix2D.Translate(MatrixTransform, WIDTH/2, HEIGHT/2);
			Matrix2D.Rotate(MatrixTemp, Math.PI/12);
			Matrix2D.MultiplyMatrix(MatrixTemp, MatrixTransform);
			Matrix2D.Translate(MatrixTransform, -WIDTH/2, -HEIGHT/2);
			Matrix2D.MultiplyMatrix(MatrixTransform,MatrixTemp);
			Matrix2D.Transform(MatrixTransform, NumberPositions[i]);
		}
	}
	
	public void render(Graphics g)
		//called about every 27 seconds or so
	{
		if (bIsInited == false)
		{
			Initialize();
			bIsInited = true;
		}

		iTickCount++;
		Update();
		Draw(g);
	}
	
	public void Update()
	//this method will update the coordinates of each of the objects in the clock
	//the constant TIME_ADJUST was put in so I could adjust the program enough 
	//to match a real watch ticking off seconds
	//its much more interesting to watch it zoom by and the movement is more evident
	{
		if (((iTickCount%TIME_ADJUST) == 0) && ((iTickCount) > TIME_ADJUST))
		{
			//update the position of the second hand
			Matrix2D.Rotate(MatrixTransform, Math.PI/30);
			for (int i=0; i<NUM_POINTS_HAND; i++)
				Matrix2D.Transform(MatrixTransform, HandSeconds[i]);
			
			//update the position of the seconds sprocket
			Matrix2D.Translate(MatrixTransform, -SPROCKET_SECONDS_X, -SPROCKET_SECONDS_Y);
			Matrix2D.Rotate(MatrixTemp, -Math.PI/30);
			Matrix2D.MultiplyMatrix(MatrixTemp, MatrixTransform);
			Matrix2D.Translate(MatrixTransform, SPROCKET_SECONDS_X, SPROCKET_SECONDS_Y);
			Matrix2D.MultiplyMatrix(MatrixTransform,MatrixTemp);
			for (int i=0; i<NUM_POINTS_SPROCKET; i++)
				Matrix2D.Transform(MatrixTransform, SprocketSeconds[i]);
		}
			
		if (((iTickCount%(60*TIME_ADJUST)) == 1) && ((iTickCount) > (60*TIME_ADJUST)))
		{
			//update the position of the minute hand
			Matrix2D.Rotate(MatrixTransform, Math.PI/30);
			for (int i=0; i<NUM_POINTS_HAND; i++)
				Matrix2D.Transform(MatrixTransform, HandMinutes[i]);
			
			//update the position of the minutes sprocket
			Matrix2D.Translate(MatrixTransform, -SPROCKET_MINUTES_X, -SPROCKET_MINUTES_Y);
			Matrix2D.Rotate(MatrixTemp, -Math.PI/30);
			Matrix2D.MultiplyMatrix(MatrixTemp, MatrixTransform);
			Matrix2D.Translate(MatrixTransform, SPROCKET_MINUTES_X, SPROCKET_MINUTES_Y);
			Matrix2D.MultiplyMatrix(MatrixTransform,MatrixTemp);
			for (int i=0; i<NUM_POINTS_SPROCKET; i++)
				Matrix2D.Transform(MatrixTransform, SprocketMinutes[i]);
		}

		if (((iTickCount%(720*TIME_ADJUST)) == 1) && ((iTickCount) > (720*TIME_ADJUST)))
		{
			//update the position of the hour hand
			Matrix2D.Rotate(MatrixTransform, Math.PI/60);
			for (int i=0; i<NUM_POINTS_HAND; i++)
				Matrix2D.Transform(MatrixTransform, HandHours[i]);
			
			//update the position of the hours sprocket
			Matrix2D.Translate(MatrixTransform, -SPROCKET_HOURS_X, -SPROCKET_HOURS_Y);
			Matrix2D.Rotate(MatrixTemp, -Math.PI/60);
			Matrix2D.MultiplyMatrix(MatrixTemp, MatrixTransform);
			Matrix2D.Translate(MatrixTransform, SPROCKET_HOURS_X, SPROCKET_HOURS_Y);
			Matrix2D.MultiplyMatrix(MatrixTransform,MatrixTemp);
			for (int i=0; i<NUM_POINTS_SPROCKET; i++)
				Matrix2D.Transform(MatrixTransform, SprocketHours[i]);
			iTickCount = 0;
		}		
	}
	
	public void Draw(Graphics g)
	{
		int tempArrayX[] = new int[48];
		int tempArrayY[] = new int[48];
		//draw everything in the following order:
		g.setColor(Color.black);
		g.fillRect(0,0, WIDTH, HEIGHT);
		
		//set the color for the appropriate sprocket and fill in the polygon
		g.setColor(clrHours);
		g.fillPolygon(Column(SprocketHours, COLUMN_X, NUM_POINTS_SPROCKET), 
					  Column(SprocketHours, COLUMN_Y, NUM_POINTS_SPROCKET), 
					  NUM_POINTS_SPROCKET);
		//give them an outline just so they stand out a bit
		g.setColor(Color.black);
		g.drawPolygon(Column(SprocketHours, COLUMN_X, NUM_POINTS_SPROCKET), 
					  Column(SprocketHours, COLUMN_Y, NUM_POINTS_SPROCKET), 
					  NUM_POINTS_SPROCKET);
							  
		g.setColor(clrSeconds);
		g.fillPolygon(Column(SprocketSeconds, COLUMN_X, NUM_POINTS_SPROCKET), 
					  Column(SprocketSeconds, COLUMN_Y, NUM_POINTS_SPROCKET), 
					  NUM_POINTS_SPROCKET);
		g.setColor(Color.black);
		g.drawPolygon(Column(SprocketSeconds, COLUMN_X, NUM_POINTS_SPROCKET), 
					  Column(SprocketSeconds, COLUMN_Y, NUM_POINTS_SPROCKET), 
					  NUM_POINTS_SPROCKET);

		g.setColor(clrMinutes);
		g.fillPolygon(Column(SprocketMinutes, COLUMN_X, NUM_POINTS_SPROCKET), 
					  Column(SprocketMinutes, COLUMN_Y, NUM_POINTS_SPROCKET), 
					  NUM_POINTS_SPROCKET);
		g.setColor(Color.black);
		g.drawPolygon(Column(SprocketMinutes, COLUMN_X, NUM_POINTS_SPROCKET), 
					  Column(SprocketMinutes, COLUMN_Y, NUM_POINTS_SPROCKET), 
					  NUM_POINTS_SPROCKET);

		g.setColor(clrCenter);
		g.fillPolygon(Column(SprocketCenter, COLUMN_X, NUM_POINTS_SPROCKET), 
					  Column(SprocketCenter, COLUMN_Y, NUM_POINTS_SPROCKET), 
					  NUM_POINTS_SPROCKET);
		g.setColor(Color.black);
		g.drawPolygon(Column(SprocketCenter, COLUMN_X, NUM_POINTS_SPROCKET), 
					  Column(SprocketCenter, COLUMN_Y, NUM_POINTS_SPROCKET), 
					  NUM_POINTS_SPROCKET);
		
		//some superfluous shapes to make it less boring
		g.setColor(Color.white);
		g.fillOval(190,190,20,20);
		
		//the numbers around the face of the clock
		Font ClockNumbers = new Font(g.getFont().getFontName(),Font.BOLD, 20);
		g.setFont(ClockNumbers);
		
		for (int i=0; i<24; i++)
		{
			if (i < 10)
			{
				g.drawString(" " + Integer.toString(i),(int)NumberPositions[i][0] + WIDTH/2 - 10,
						 (int)NumberPositions[i][1] + HEIGHT/2 + 5);
			}
			else
			{
				g.drawString(Integer.toString(i), (int)NumberPositions[i][0] + 
						WIDTH/2 - 10, (int)NumberPositions[i][1] + HEIGHT/2 + 5);
			}
		}
			
			
		//set the color for the appropriate hand and fill in the polygon
		//then outline
		g.setColor(clrMinutes);
		g.fillPolygon(Column(HandMinutes, COLUMN_X, NUM_POINTS_HAND), 
					  Column(HandMinutes, COLUMN_Y, NUM_POINTS_HAND), 
					  NUM_POINTS_HAND);
		g.setColor(Color.white);
		g.drawPolygon(Column(HandMinutes, COLUMN_X, NUM_POINTS_HAND), 
					  Column(HandMinutes, COLUMN_Y, NUM_POINTS_HAND), 
					  NUM_POINTS_HAND);

		g.setColor(clrHours);
		g.fillPolygon(Column(HandHours, COLUMN_X, NUM_POINTS_HAND), 
					  Column(HandHours, COLUMN_Y, NUM_POINTS_HAND), 
					  NUM_POINTS_HAND);
		g.setColor(Color.white);
		g.drawPolygon(Column(HandHours, COLUMN_X, NUM_POINTS_HAND), 
					  Column(HandHours, COLUMN_Y, NUM_POINTS_HAND), 
					  NUM_POINTS_HAND);

		g.setColor(clrSeconds);
		g.fillPolygon(Column(HandSeconds, COLUMN_X, NUM_POINTS_HAND), 
					  Column(HandSeconds, COLUMN_Y, NUM_POINTS_HAND), 
					  NUM_POINTS_HAND);
		g.setColor(Color.white);
		g.drawPolygon(Column(HandSeconds, COLUMN_X, NUM_POINTS_HAND), 
					  Column(HandSeconds, COLUMN_Y, NUM_POINTS_HAND), 
					  NUM_POINTS_HAND);
	}
	
	public int[] Column(double Matrix[][], int col, int len)
	//returns a column from a 2D array, in this case, an array of X or Y
	//coordinates. Used to draw polygons
	{
		int ColumnArray[] = new int[len];
		for(int i=0; i<len; i++)
			ColumnArray[i] = (int)Matrix[i][col];
		ColumnArray = ViewPort(ColumnArray, col);
		if (len == 4)
			for (int i=0;i<len;i++);
		return ColumnArray;
	}
	
	public int[] ViewPort(int Array[], int col)
	//converts the X and Y coordinates so that the origin will appear in the
	//center of the applet
	{
		int len = Array.length;
		if (col == COLUMN_X)
			for (int i=0; i<len; i++)
				Array[i] = Array[i] + WIDTH / 2;
		else if (col == COLUMN_Y)
			for (int i=0; i<len; i++)
				Array[i] = Array[i] + HEIGHT / 2;
		return Array;
	}
			
}
//***** End Class **************************************************************

//***** Class BufferedApplet ***************************************************
class BufferedApplet extends java.applet.Applet implements Runnable
{
/*
	THIS CLASS HANDLES DOUBLE BUFFERING FOR YOU, SO THAT THE IMAGE
	DOESN'T FLICKER WHILE YOU'RE RENDERING IT.  YOU DON'T REALLY
	NEED TO WORRY ABOUT THIS TOO MUCH, AND YOU'LL PROBABLY NEVER NEED
	TO CHANGE IT.  IT'S REALLY JUST USEFUL LOW LEVEL PLUMBING.
*/

	public void render(Graphics g) { }   // *you* define how to render
	public boolean damage = false;        // you can force a render
	
	private Image image = null;
	private Graphics buffer = null;
	private Thread t;
	private Rectangle r = new Rectangle(0, 0, 0, 0);
	
	// A BACKGROUND THREAD CHECKS FOR CHANGES ABOUT 30 TIMES PER SECOND

	public void start() { if (t == null) { t = new Thread(this); t.start(); } }
	public void stop()  { if (t != null) { t.stop(); t = null; } }
	public void run()   { try { while (true) { repaint(); t.sleep(30); } }
                             catch(InterruptedException e){}; }
/*
	UPDATE GETS CALLED BY THE SYSTEM.
	IT CALLS YOUR RENDER METHOD, WHICH DRAWS INTO AN OFF-SCREEN IMAGE.
	THEN UPDATE COPIES THE IMAGE YOU'VE RENDERED ONTO THE APPLET WINDOW.
*/

	public void update(Graphics g) 
	{
		if (r.width != bounds().width || r.height != bounds().height) 
		{
			image = createImage(bounds().width, bounds().height);
			buffer = image.getGraphics();
			r = bounds();
		}
		render(buffer);
      
		if (image != null)
		g.drawImage(image,0,0,this);
   }
}
//***** End Class **************************************************************