import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import gs.edt.EDTDate;
import gs.edt.EDTFormat;
import java.util.*;
import java.awt.font.FontRenderContext;
import java.applet.Applet;
import java.text.DateFormat;

/* Todo:
Add dump to file and size options 
Add different hands
Add twiglight
Add moon phases
Save window position
Customize colors?
Sourcecode documentation
Shadow for time and hand moves?
Add additional time zones
*/
public class YesClock extends Canvas implements Runnable {
	private int _minimum_dimension;
	private double _sunrise_angle;
	private double _sunset_angle;
	private HashMap _hints;
	private Color BLUE = new Color( 150, 150, 255 ),
		GRAY = Color.gray.darker(),
		GOLD = new Color( 243, 255, 21 ),
		AZURE = new Color( 0, 14, 142 ),
		RUBY = new Color( 200, 0, 100 ),
		BRIGHT_RUBY = new Color( 255, 0, 200 ),
		DARK_RUBY = new Color( 145, 0, 100 ),
		YES_COLOR = new Color( 30, 150, 40 ),
		HAND_FILL = new Color( 0, 205, 100 ),
		HAND_LINE = Color.black, //new Color( 0, 155, 55 ),
		TIME_COLOR = new Color(200,200,255),
		TIME_SHADOW = Color.black,
		SILVER = new Color( 90, 127, 115 );
	private AffineTransform _transform = new AffineTransform();
	private int _center_x, _center_y;
	private double _scale;
	// Hand stuff
	private Shape _hand;
	private Stroke _hand_stroke;
	private Arc2D _night;
	private Ellipse2D _noon, _midnight;
	private Image _img_buffer;
	private Font _time_font, _date_font, _yes_font;
	private Rectangle2D _tick;
	private boolean _standard_time = true;
	private float _latitude, _longitude;
	private int _timezone;
	
	public YesClock() {
		_hints = new HashMap();
		_hints.put( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
		
		// Hand stuff
		GeneralPath hand = new GeneralPath();
		hand.moveTo( 0, -5 );
		hand.lineTo( -7, -15 );
		hand.lineTo( -3, -18 );
		hand.lineTo( -5, -20 );
		hand.lineTo( 0, -100 ); // Point
		hand.lineTo( 5, -20 );
		hand.lineTo( 3, -18 );
		hand.lineTo( 7, -15 );
		hand.closePath();
		_hand = hand;
		_hand_stroke = new BasicStroke( 2, BasicStroke.CAP_BUTT
		, BasicStroke.JOIN_ROUND );
		
		_night = new Arc2D.Double( 0d, 0d, 10d, 10d, 90, 180, Arc2D.PIE );
		_noon = new Ellipse2D.Float( -5f, -105f, 10f, 10f );
		_midnight = new Ellipse2D.Float( -5f, 95, 10f, 10f );
		_tick = new Rectangle2D.Float( -1f, -110f, 2f, 5f );

		_date_font = new Font( "Sans", Font.BOLD, 12 );
		_time_font = new Font( "Sans", Font.BOLD, 14 );
		_yes_font = new Font( "Sans", Font.ITALIC, 18 );
		setLongitude( -121.31104f );
		setLatitude( 44.06604f );
		setTZ( (int)(Calendar.getInstance().get(Calendar.ZONE_OFFSET)/3600000) );
	}
	
	public void setHand( Shape s ) {
		_hand = s;
		redraw();
		repaint();
	}

	public void setHandLine( Color c ) {
		HAND_LINE = c;
	}

	public void setHandFill( Color c ) {
		HAND_FILL = c;
	}
	
	public void setLatitude( float l ) {
		_latitude = l;
	}
	
	public void setLongitude( float l ) {
		_longitude = l;
	}
	
	public void setEDT( boolean b ) {
		_standard_time = !b;
	}

	public void setTZ( int tz ) {
		_timezone = tz;
	}

	public boolean getEDT() {
		return !_standard_time;
	}

	public void paint( Graphics g ) {
		if (_img_buffer != null) g.drawImage( _img_buffer, 0, 0, null );
	}
	public void update( Graphics g ) {
		paint(g);
	}
	
	private void checkBufferExists() {
		Dimension size = getSize();
		if (size.width == 0 || size.height == 0) return;
		if (_img_buffer == null ||
			_img_buffer.getWidth(null) != size.width ||
			_img_buffer.getHeight(null) != size.height )
			//_img_buffer = createImage( size.width, size.height );
			_img_buffer = new BufferedImage( size.width, 
					size.height, 
					BufferedImage.TYPE_INT_RGB );
	}
	
	public void run() {
		int timeout=0;
		int mod = 9;
		try {
			while( true ) {
				redraw();
				repaint();
				mod++;
				if (_standard_time) {
					if (mod == 10) {
						mod=0;
						timeout = 60000 -
							(int)(Calendar.getInstance().get(Calendar.SECOND)*1000.0);
					} else timeout = 60000;
				} else {
					if (mod == 10) {
						mod=0;
						EDTDate d = new EDTDate();
						int ival = (int)d.getTicks();
						timeout = 86400 - (int)((d.getTicks()-ival)*1000.0);
					} else timeout = 86400;
				}
				Thread.sleep( timeout );
			}
		} catch (Exception e) {
		}
	}
	
	public void redraw() {
		checkBufferExists();
		if (_img_buffer == null) return;
		Graphics2D g2 = (Graphics2D)_img_buffer.getGraphics();
		
		g2.addRenderingHints( _hints );
		g2.setStroke( _hand_stroke );
		
		Calendar current_date = Calendar.getInstance();
		clearScreen( g2 );
		g2.transform( _transform );
		drawBackground( g2 );
		drawNightArc( g2, current_date );
		drawMoonPhase( g2, current_date );
		drawTime( g2, current_date );
		drawMarkers( g2, current_date );
		drawHand( g2, current_date );
	}

	public void setBounds( int x, int y, int w, int h ) {
		_minimum_dimension = w < h ? w : h;
		_center_x = w/2;
		_center_y = h/2;
		_scale = _minimum_dimension / 222.0;
		super.setBounds( x, y, w, h );
		_transform.setToRotation(0);
		_transform.translate( _center_x, _center_y );
		_transform.scale( _scale, _scale );
		redraw();
		repaint();
	}
	
	private void drawBackground( Graphics2D g2 ) {
		g2.setPaint( Color.black );
		g2.drawOval( -110,-110, 220, 220 );
		g2.setPaint( BLUE );
		g2.fillOval( -110,-110, 220, 220 );
	}

	private void drawNightArc( Graphics2D g2, Calendar c ) {
		// Stupid Java counts months from 0!  The JavaSoft idiots are Morons.
		float[] rise_set = Astro.sunRiseSet( c.get(c.YEAR), c.get(c.MONTH)+1, 
			c.get(c.DAY_OF_MONTH), _longitude, _latitude );
		int dst = c.get(c.DST_OFFSET)/3600000;
		
		/*
		System.out.println(
				"Date: "+c.get(c.YEAR)+" "+c.get(c.MONTH)+" "+c.get(c.DAY_OF_MONTH)+
				"\nLong: "+_longitude+"\t\tLat: "+_latitude+
				"\nSunrise: "+rise_set[0]+"\t\tSunset: "+rise_set[1]+
				"\nRise angle: "+_sunrise_angle+"\t\tSet extent: "+_sunset_angle+
				"\nDST: "+dst+"\t\tZone: "+zone+"\n");
				*/
		rise_set[0] += _timezone+dst;
		rise_set[1] += _timezone+dst;
		_sunrise_angle = timeToAngle( rise_set[0] );
		_sunset_angle = timeToAngle( rise_set[1] );
		_sunset_angle -= _sunrise_angle;
		if (_sunset_angle < 0) _sunset_angle += 360;
		g2.setPaint( GRAY );
		_night.setArc( -110d, -110d, 220d, 220d,
			_sunrise_angle, _sunset_angle, Arc2D.PIE );
		g2.fill( _night );
	}

	private void drawHand( Graphics2D g2, Calendar date ) {
		// Move to center of display
		// Rotate to time
		double rotate = timeToTheta( date );

		AffineTransform t = g2.getTransform();
		g2.rotate( rotate );
		g2.setPaint( HAND_LINE );
		g2.draw( _hand );
		g2.setPaint( HAND_FILL );
		g2.fill( _hand );
		g2.transform(t);
	}
	
	private static final double TWO_PI = Math.PI*2.0;
	private static final double HALF_PI = Math.PI/2.0;
	private static final double TTR = (Math.PI/12.0);
	private static final double MIN = 5.0/300;
	private static final double SEC = 1.0/6000;
	protected static final double timeToTheta( Calendar date ) {
		double d = date.get( date.HOUR_OF_DAY )+date.get(date.MINUTE)*MIN
		+date.get(date.SECOND)*SEC;
		return timeToTheta(d);
	}
	protected static final double timeToTheta( double d ) {
		return (d * TTR) - Math.PI;
	}

	private static final EDTFormat DAY = new EDTFormat( "*-" );
	private static final EDTFormat TIME = new EDTFormat( "+" );
	private static final DateFormat 
		_date_format = DateFormat.getDateInstance( DateFormat.SHORT ),
		_time_format = DateFormat.getTimeInstance( DateFormat.SHORT );
	private void drawTime( Graphics2D g2, Calendar date ) {
		String day, time;
		if (_standard_time) {
			day = _date_format.format( date.getTime() );
			time = _time_format.format( date.getTime() );
		} else {
			EDTDate d = new EDTDate();
			day = DAY.format(d);
			time = TIME.format(d);
		}
		FontRenderContext context = g2.getFontRenderContext();	

		g2.setPaint( TIME_SHADOW );
		g2.setFont( _date_font );
		Rectangle2D r = _date_font.getStringBounds( day, context );
		g2.drawString( day, (int)(-r.getWidth()/2)+1, -74 );
		g2.setFont( _time_font );
		r = _time_font.getStringBounds( time, context );
		g2.drawString( time, (int)(-r.getWidth()/2)+1, -54 );
		
		g2.setPaint( TIME_COLOR );
		g2.setFont( _date_font );
		r = _date_font.getStringBounds( day, context );
		g2.drawString( day, (int)(-r.getWidth()/2), -75 );
		g2.setFont( _time_font );
		r = _time_font.getStringBounds( time, context );
		g2.drawString( time, (int)(-r.getWidth()/2), -55 );

		g2.setFont( _yes_font );
		r = _yes_font.getStringBounds( "YES", context );
		g2.setPaint( YES_COLOR );
		g2.drawString( "YES", (int)(-r.getWidth()/2), 50 );
	}

	private void drawMoonPhase( Graphics2D g2, Calendar date ) {
	}
	
	private static final double TICK_INCR = TWO_PI/10.0;
	private static final double HOUR_INCR = TWO_PI/24.0;
	private static final Stroke TICK_STROKE = new BasicStroke( 1, 
		BasicStroke.CAP_BUTT , BasicStroke.JOIN_BEVEL );
	private void drawMarkers( Graphics2D g2, Calendar c ) {
		// Draw 0-9
		AffineTransform t = g2.getTransform();
		double incr, max;
		if (_standard_time) {
			incr = HOUR_INCR;
			max = 24;
		} else {
			g2.rotate( _timezone*TTR );
			incr = TICK_INCR;
			max = 10;
		}
		g2.rotate( -incr );
		g2.setStroke( TICK_STROKE );
		int half_max = (int)(max/2);
		for (int i=0; i<max; i++) {
			g2.rotate( incr );
			g2.setPaint( SILVER );
			g2.draw( _tick );
			if (i==0) g2.setPaint(BRIGHT_RUBY);
			else if (i==half_max) g2.setPaint(DARK_RUBY);
			else g2.setPaint( RUBY );
			g2.fill( _tick );
		}
		g2.setTransform( t );

		float[] rise_set = Astro.sunRiseSet( c.get(c.YEAR), c.get(c.MONTH), 
			c.get(c.DAY_OF_MONTH), _longitude, _latitude );
		int dst = c.get(c.DST_OFFSET)/3600000;
		float mid_day = (rise_set[1] + rise_set[0])/2;
		g2.rotate( timeToTheta( mid_day+_timezone+dst ) );
		g2.setStroke( _hand_stroke );
		// Draw noon jewel
		g2.setPaint( Color.blue );
		g2.draw( _noon );
		g2.setPaint( GOLD );
		g2.fill( _noon );
		// Draw midnight jewel
		g2.setPaint( Color.blue );
		g2.draw( _midnight );
		g2.setPaint( AZURE );
		g2.fill( _midnight );
		g2.setTransform( t );
	}

	/** 6am = 180.  6pm = 360.  Noon = 90.  Midnight = 270
	* @param d 0 .. 24, decimal
	*/
	protected static final double timeToAngle( float d ) {
		double v = 270-(d * 15.0);
		if (v<0) v = 360+v;
		return v;
	}

	private void clearScreen( Graphics2D g2 ) {
		g2.setPaint( Color.blue );
		g2.fillRect( 0,0, getSize().width, getSize().height);
	}
}
