import java.awt.*;
import java.awt.event.*;

/**
Simulation external clock. It runs in cycle of <B>delay</B> miliseconds
and waiting loop checks each (delay/10) milis if cycle ended
*/
class Clock implements Runnable {
	int md;
	int delay;
	boolean is_time;
	
	/**
	Constructs clock
	@param mini_delay  1/10 of wanted delay
	*/
	public Clock( int mini_delay ) {
		md = mini_delay;
		delay = 10*md;
		is_time = false;
	}
	
	/**
	Implements Runnable interface
	*/
	public void run(){
		while( true ) {
			try { Thread.sleep( delay ); } 
			catch( InterruptedException e ) {}
			is_time = false;
		}
	}
	
	/**
	Called for waiting. When called, waits for beging of new clock cycle
	and then returns control to calling process
	*/
	public void waiting() {
		is_time = true;
		while( is_time ){
			try { Thread.sleep( md ); }
			catch( InterruptedException e ) {}
		}
	}
}

/**
Enhanced scrollbar consists of Scrollbar, Title text and text displaying actual value.
It is fully automatic. Stores user correction koeficient and flag if it has changed from last check
*/
class EnhancedScroll extends Panel implements AdjustmentListener {
	Scrollbar s;
	Label l;
	String text;
	double value;
	double correction;
	boolean changed;
	
	/**
	New Enhanced scroll
	@param start  minimum of range
	@param end  maximum of range
	@param pos  current position
	@param text1  value prefix
	@param text2  title of scrollbar
	@param corr  corection koeficient
	*/
	public EnhancedScroll( int start, int end, int pos, String text1, String text2, double corr ) {
		super( new GridLayout( 0, 1 ) );
		correction = corr;
		s = new Scrollbar( Scrollbar.HORIZONTAL, pos, 1, start, end );
		text = text1 + " = ";
		value = pos * correction;
		l = new Label( text + value, Label.CENTER );
		add( new Label( text2 ) );
		add( s );
		add( l );
		s.addAdjustmentListener( this );
		changed = true;
	}

	/**
	Implemets AdjustmentListener Interface. Updates <B>value</B> and <B>changed</B> flag
	*/
	public void adjustmentValueChanged( AdjustmentEvent e ) {
		value = s.getValue() * correction;
		l.setText( text + value );
		changed = true;
	}

	/**
	Returns value
	@return  current value
	*/
	public double getValue() {
		return value;	
	}  
}

/**
All Buttons of Control Panel
*/
class ButtonField extends Panel implements ActionListener {
	Button pause_b, kill_body, clear_b;
	boolean paused, clear_all, kill_b;
		
	/**
	Add Pause/Resume, Kill Body, Clear Screen
	*/
	public ButtonField() {
		super( new GridLayout(0,1) );
		paused = false;
		kill_b = false;
		clear_all = false;
		pause_b = new Button("Pause...");
		kill_body = new Button("Kill Body");
		clear_b = new Button("Clear screen");
		add( pause_b );
		pause_b.addActionListener( this );
		add( kill_body );
		kill_body.addActionListener( this );
		add( clear_b );
		clear_b.addActionListener( this );
	}

	/**
	Implements ActionListener Interface. Watches for clicks on button
	*/
	public void actionPerformed( ActionEvent e ) {
		String act_id = e.getActionCommand();
		
		if( act_id == "Pause..." ) {
			paused = true;
			pause_b.setLabel("Resume...");
		}
		if( act_id == "Resume..." ) {
			paused = false;
			pause_b.setLabel("Pause...");
		}
		if( act_id == "Kill Body" ) {
			kill_b = true;
		}	
		if( act_id == "Clear screen" ) {
			clear_all = true;
		}	
	}
}

/**
CheckBox Field of Control Panel
*/
class ChBoxField extends Panel {
	Checkbox tracking, relativistic;
	
	public ChBoxField() {
		super( new GridLayout(0,1) );
		tracking = new Checkbox("Tracking", true );
		relativistic = new Checkbox("Relativistic", true );
		add( tracking );
		add( relativistic );
	}

}

/**
Control Panel <BR>
It stores all control elemets and values of simulator.
*/
class ControlPanel extends Panel {
	EnhancedScroll light, mass, zoom, speed;
	ButtonField bf;
	ChBoxField chbf;
	
	public ControlPanel() {
		super(new GridLayout(0,1));
		setBackground( new Color( 200, 255, 200 ) );
		bf = new ButtonField();
		light = new EnhancedScroll( 3, 50, 10, "c", "Light speed", 0.1 );
		mass = new EnhancedScroll( 1, 100, 40, "mass", "Mass of central body", 1 );
		zoom = new EnhancedScroll( 1, 11, 5, "ratio", "Zoom", 0.2 );
		speed = new EnhancedScroll( 1, 11, 4, "speed", "Speed of animation", 1 );
		chbf = new ChBoxField();
		add( bf );
		add( light );
		add( mass );
		add( zoom );
		add( speed );
		add( chbf );
	}
}

/**
Status Line <BR>
Displays information about body position, dynamics and status of simulator
*/
class StatusLine extends Panel {
	Label dist, prad, pangl, status;
	
	public StatusLine() {
		super( new GridLayout( 2, 3 ) );
		setBackground( new Color( 255, 225, 200 ) );
		dist = new Label();		add(dist);
		prad = new Label();		add(prad);
		pangl = new Label();		add(pangl);
		status = new Label();		status.setBackground( new Color( 150, 175, 200 ));
		add(status);
	}
		
	/**
	Updates information in status line
	@param dist  Distance from central body
	@param prad  Radial dynamics
	@param panlg  Angular dynamics
	@param st Status message
	*/
	public void updateInfo( double dist, double prad, double pangl, String st ) {
		this.dist.setText("Distance = " + dist);
		this.prad.setText("Rad. Dynam. = " + prad);
		this.pangl.setText("Angle Dynam. = " + pangl );
		this.status.setText( "Status: " + st );
	}
	
}

/**
Simulation Canvas <BR>
Main work class of simulator
*/
class Simulation extends Canvas implements Runnable, MouseListener, MouseMotionListener {
	Clock c;														//reference to simulation clock
	ControlPanel p;											//reference to variables in Control panel
	StatusLine s;												//reference to Status line
	Universe uni;												//current universe
	CentrBody c_body;										//current central body
	DependBody d_body;										//current dependent body
	double cor_x, cor_y, area_x, area_y;		//size of virtual space and transform koeficients
	int size_x, size_y;									//size of canvas
	boolean clear_all;										//clear_all falg
	boolean no_body;										//there is no dependend body flag
	boolean updating;										//flag for user input line drawing
	boolean relativistic;								//whether relativistic mode is set
	boolean some_change;									// flag of changed inputs
	int start_x, start_y, old_x, old_y, cur_x, cur_y;		//coordinates of mouse clicks and drags
	Image off_screen;										//off screen buffer
	Graphics ig;												//off screen graphics
	
	/**
	Scans Control panel for needed values and updates own values and flags
	*/
	private void getParams() {
		
		if(p.light.changed) {													//light speed
			uni.c = p.light.getValue()*10;
			p.light.changed = false;
			some_change = true;
		}
		if(p.mass.changed) {													//central mass
			uni.cb.mass = p.mass.getValue();
			p.mass.changed = false;
			some_change = true;
		}
		if(p.speed.changed) {													//animation speed
			c.delay = (int) (500 / p.speed.getValue());
			c.md = c.delay / 10;
			p.speed.changed = !p.speed.changed;
			some_change = true;
		}
		if(p.zoom.changed) {													//zoom
			area_x = 100 * p.zoom.getValue();
			area_y = 100 * p.zoom.getValue();
			cor_x = size_x / area_x;
			cor_y = size_y / area_y;
			clear_all = true;
			p.zoom.changed = !p.zoom.changed;
			some_change = true;
		}
		if( p.bf.clear_all ) {												//clear all button
			clear_all = true;
			p.bf.clear_all = false;
			some_change = true;
		}
		if( p.bf.kill_b ) {														//kill body button
			no_body = true;
			start_x = 0; start_y =0; cur_x = 0; cur_y = 0; old_x = 0; old_y = 0;
			p.bf.kill_b = false;
		}
		relativistic = p.chbf.relativistic.getState(); //relativistic
		
		//if we do not repaint periodicaly but something has changed, we must repaint
		if( some_change && no_body )
			repaint();
	}
	
	/**
	Transforms coordinates from canvas coordinates to simulation coordinates by default zoom ratio
	@param x  x in canvas space 
	@param y  y in canvas space 
  @return  tranformed vector in simulation space
	*/
	private Vector coordTransform( int x, int y ) {
		Vector v = new Vector( (x - size_x) / cor_x, (y - size_y) / cor_y );
		return v;
	}
	
	public Simulation( Clock c, ControlPanel p, StatusLine s ) {
		super();
		this.c = c;
		this.p = p;
		this.s = s;
		size_x = 235;								//size of canvas
		size_y = 200;
		setSize(470, 400);					// BORDEL !!!!!!
		System.out.println( size_x + " " + size_y);
		area_x = 100;								//initial size of simulation space
		area_y = 100;
		setBackground( new Color( 0, 0, 0) );
		c_body = new CentrBody( 40, 5 );		//creation of cental body
		uni = new Universe( c_body );				//creation of universe
		uni.set_c( 5.0 );										//light speed
		d_body = new DependBody( uni );			//creation of dependent body
		d_body.initPos( new Vector( 90 , 0 ) );			//start conditions of first body
		d_body.initDynam( new Vector( 90, 40 ) );
		cor_x = size_x / area_x;						//correction factors for zoom 1.0
		cor_y = size_y / area_y;
		updating = false;
		addMouseListener(this);
		addMouseMotionListener(this);
		no_body = false;
		some_change = false;
	}

	/**
	Redefined Canvas.paint. Does nothing, only allocates offscreen image
	*/
	public void paint( Graphics g ) {
		if( off_screen == null )
			off_screen = createImage( 2 * size_x, 2 * size_y );
		if( ig == null )
			ig = off_screen.getGraphics();
	}
	
	/**
	Redefined Canvas.update <BR>
	Draws objects on canvas
	*/
	public void update( Graphics g ) {
		Vector cart_pos;
		int x, y;
		int cor_size, schw_r;
		
		if(!p.chbf.tracking.getState() || clear_all ) {	
			ig.clearRect(0,0,2*size_x, 2*size_y);			//clear screen
			clear_all = !clear_all;
		}
		//draw central body
		cor_size =  (int) (uni.cb.size * cor_x);
		schw_r = (int) ( uni.getSchwRadius() * cor_x );
		ig.setColor( Color.red );
		ig.fillOval( size_x - cor_size, size_y - cor_size, 2 * cor_size, 2 * cor_size );
		//if relativistic, draw Schwartzield radius
		if( relativistic ) {
			ig.setColor( Color.blue );
			ig.fillOval( size_x - schw_r, size_y - schw_r, 2 * schw_r, 2 * schw_r );
		}
		if( !no_body && !p.bf.paused ) {				//normal animation state
			ig.setColor( Color.white );
			cart_pos = Vector.pol_2_cart( d_body.move( 1, relativistic ) );
			//DEBUG System.out.println("x = " + cart_pos.x1 + " y = " + cart_pos.x2);
			if( relativistic ) {
				if( (d_body.pos.x1 - uni.getSchwRadius() ) > 0 && (d_body.pos.x1 - uni.cb.size) > 0) {
					x = size_x + (int) (cart_pos.x1 * cor_x);
					y = size_y + (int) (cart_pos.x2 * cor_y);
					ig.fillOval( x - 2, y - 2, 4, 4 );
					s.updateInfo( d_body.pos.x1, d_body.dynam.x1, d_body.dynam.x2, "Running");
				} else {														//animation ends
					ig.setFont( new Font( "SansSerif", Font.BOLD, 20 ));
					if( (d_body.pos.x1 - uni.getSchwRadius() ) > 0 )
						ig.drawString("<<Object has crashed into central Body>>", size_x - 200, size_y - 50 );
					else
						ig.drawString("<<Fall behind Schwarzschild's radius>>", size_x - 170, size_y - 50 );
					s.updateInfo( d_body.pos.x1, d_body.dynam.x1, d_body.dynam.x2, "User Input");
					no_body = true;
					updating = false;
				}
			} else {															//nonrelativistic
				if( (d_body.pos.x1 - uni.cb.size) > 0) {
					x = size_x + (int) (cart_pos.x1 * cor_x);
					y = size_y + (int) (cart_pos.x2 * cor_y);
					ig.fillOval( x - 2, y - 2, 4, 4 );
					s.updateInfo( d_body.pos.x1, d_body.dynam.x1, d_body.dynam.x2, "Running");
				} else {														//animation ends
					ig.setFont( new Font( "SansSerif", Font.BOLD, 20 ));
					ig.drawString("<<Object has crashed into central Body>>", size_x - 200, size_y - 50 );
					s.updateInfo( d_body.pos.x1, d_body.dynam.x1, d_body.dynam.x2, "User Input");
					no_body = true;
					updating = false;
				}
			}
			g.drawImage(off_screen,0,0,null);			//flush offcreen image onto canvas
		} else {																//user input mode
			if( some_change ) {										//some parameters changed
				g.drawImage(off_screen,0,0,null);
				some_change = false;
			}																			//user input of new starting conditions
			updating = true;
			g.setXORMode( Color.white );
			g.setColor( Color.red );
			g.drawLine( start_x, start_y, old_x, old_y );		
			g.drawLine( start_x, start_y, cur_x, cur_y);
			g.setPaintMode();
			s.updateInfo( d_body.pos.x1, d_body.dynam.x1, d_body.dynam.x2, "User Input");
			updating = false;
		}
	}
	
	/**
	Implemets Runnable Interface <BR>
	Loop scans for parameters. If is normal animation mode, then repaints canvas.
	Then calls clock to wait until next clock cycle
	*/
	public void run() {
		while( true ) {
			getParams();
			if( !no_body )
				repaint();
			else	
				if( p.bf.paused )
					s.updateInfo( d_body.pos.x1, d_body.dynam.x1, d_body.dynam.x2, "Paused...");			
			c.waiting();
		}
	}
	/**
	Implemets MouseListener, MouseMotionListener Inteface <BR>
	In user input mode, while dragging, new dynamics is computed each time
	*/
	public void mouseDragged( MouseEvent me ) {
		if( !updating && no_body ) {
			old_x = cur_x; old_y = cur_y;
			cur_x = me.getX();	cur_y = me.getY();
			d_body.initDynam( coordTransform( cur_x, cur_y ) );
			repaint();
		}
		// DEBUG System.out.println("x " + x + " y " + y );	
	}
	
	/**
	Implemets MouseListener, MouseMotionListener Inteface <BR>
	In user input mode sets new starting position
	*/
	public void mousePressed( MouseEvent me ) {
		if( no_body ) {
			start_x = me.getX();		start_y = me.getY();
			cur_x = start_x; 	cur_y = start_y;
			d_body.initPos( coordTransform( start_x, start_y ) );
			d_body.initDynam( coordTransform( cur_x, cur_y ) );
			//DEBUG System.out.println("START: x " + x + " y " + y );		
		}
	}

	/**
	Implemets MouseListener, MouseMotionListener Inteface <BR>
	Switches from user input mode to simulation mode
	*/
	public void mouseReleased( MouseEvent me ) {
		if( no_body ) {
			start_x = 0; start_y = 0; cur_x = 0; cur_y = 0; old_x = 0; old_y = 0;
			clear_all = true;
			p.bf.kill_b = false;
			no_body = false;
		}
	}
	
	// not used mothods from MouseListener & MouseMotionListener:
	/**
	Implemets MouseListener, MouseMotionListener Inteface <BR>
	Not used
	*/
	public void mouseMoved( MouseEvent me ) {}
	/**
	Implemets MouseListener, MouseMotionListener Inteface <BR>
	Not used
	*/
	public void mouseClicked( MouseEvent me ) {}
	/**
	Implemets MouseListener, MouseMotionListener Inteface <BR>
	Not used
	*/
	public void mouseEntered( MouseEvent me ) {}
	/**
	Implemets MouseListener, MouseMotionListener Inteface <BR>
	Not used
	*/
	public void mouseExited( MouseEvent me ) {}

} 

/**
Applet itself <BR>
*/
public class grav extends java.applet.Applet {
	ControlPanel pan;
	StatusLine stl;
	Simulation sim;
	Clock clk;
	Thread clk_thread, sim_thread;
	
	/**
	Puts Control Panel, Status Line and Simulation canvas into applet. Constructs clock.
	*/
	public void init() {
		setLayout( new BorderLayout( 3, 3 ) );
		clk = new Clock( 10 );
		pan = new ControlPanel();
		stl = new StatusLine();
		sim = new Simulation( clk, pan, stl );
		add( sim, BorderLayout.CENTER );
		add( stl, BorderLayout.SOUTH );
		add( pan, BorderLayout.WEST );
		stl.updateInfo(0,0,0,"Running");
	}

	/**
	Starts clock and simulation threads
	*/
	public void start() {
		clk_thread = new Thread( clk );
		clk_thread.start();
		sim_thread = new Thread( sim );
		sim_thread.start();
	}
	
	/**
	Shall stop the threads
	*/
	public void stop() {}
}