import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.font.*;
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;
import javax.swing.*;

/* Todo:
Add twiglight
Add moon phases
Save window position
Customize colors?
Sourcecode documentation
Shadow for time and hand moves?
*/
public class YesClock extends JComponent implements Runnable {
	private int _minimum_dimension;
	private double _sunrise_angle;
	private double _sunset_angle;
	private HashMap _hints;
	private Color 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 Color _day_color = new Color( 150, 150, 255 ),
		_night_color = Color.gray.darker(),
		_background_color = Color.blue;
	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, _long_tick;
	private boolean _standard_time = true;
	private float _latitude, _longitude;
	private int _timezone;
	private boolean _show_digital_time=true, _show_markers=true, _show_jewels=true;
	
	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 );
		_long_tick = new Rectangle2D.Float( -1f, -110f, 2f, 10f );
		
		
		_date_font = new Font( "SansSerif", Font.BOLD, 12 );
		_time_font = new Font( "SansSerif", Font.BOLD, 14 );
		_yes_font = new Font( "SansSerif", 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 void setDay( Color c ) {
		_day_color = c;
	}

	public void setNight( Color c ) {
		_night_color = c;
	}

	public void setBackground( Color c ) {
		_background_color = c;
	}

	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 )
			if (_img_buffer != null) _img_buffer.flush();
			//_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 );
		if (_show_digital_time) drawTime( g2, current_date );
		if (_show_markers) drawMarkers( g2, current_date );
		if (_show_jewels) drawJewels( 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( _day_color );
		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( _night_color );
		_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 TextLayout _date_layout, _yes_layout;
	private String _date_string;
	private Rectangle2D _date_bounds, _yes_bounds;
	private void drawTime( Graphics2D g2, Calendar date ) {
		try {
			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 );
			if (_date_string != day) {
				_date_string = day;
				_date_layout = new TextLayout( day, _date_font, context );
				_date_bounds = _date_layout.getBounds();
			}
			_date_layout.draw( g2, (int)(-_date_bounds.getWidth()/2)+1, -74 );

			TextLayout layout = new TextLayout( time, _time_font, context );
			Rectangle2D r = layout.getBounds( );
			layout.draw( g2, (int)(-r.getWidth()/2)+1, -54 );
		
			g2.setPaint( TIME_COLOR );
			layout.draw( g2, (int)(-r.getWidth()/2), -55 );
			_date_layout.draw( g2, (int)(-_date_bounds.getWidth()/2), -75 );


			if (_yes_bounds == null) {
				_yes_layout = new TextLayout( "YES", _yes_font, context );
				_yes_bounds = _yes_layout.getBounds();
			}
			g2.setPaint( YES_COLOR );
			_yes_layout.draw( g2, (int)(-_yes_bounds.getWidth()/2), 50 );
		} catch (NullPointerException npe) {
			// This is just to avoid a bug in JDK 1.4
			System.out.println( "If you see this, complain to Sun.  Their JDK1.4 is buggy.");
		}
	}

	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;
		int mod;
		if (_standard_time) {
			incr = HOUR_INCR;
			max = 24;
			mod = 3;
		} else {
			g2.rotate( _timezone*TTR );
			incr = TICK_INCR;
			max = 10;
			mod = 5;
		}
		g2.rotate( -incr );
		g2.setStroke( TICK_STROKE );
		int half_max = (int)(max/2);
		Shape tick;
		for (int i=0; i<max; i++) {
			tick = _tick;
			if (i % mod == 0) tick = _long_tick;
			else tick = _tick;
			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 );
	}

	private void drawJewels( Graphics2D g2, Calendar c) {
		AffineTransform t = g2.getTransform();
		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( _background_color );
		g2.fillRect( 0,0, getSize().width, getSize().height);
	}

	public void showDigitalTime( boolean tf ) {
		_show_digital_time = tf;
	}
	public void showMarkers( boolean tf ) {
		_show_markers = tf;
	}
	public void showJewels( boolean tf ) {
		_show_jewels = tf;
	}
}
