import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import javax.swing.JApplet;

class Node
{	
	public double radius;
	public double mass;
	
	// current position
	public double x, y;
	
	// current velocity
	public double vx, vy;
	
	// composition of forces
	public double fx, fy;
	
	public Node( double x, double y )
	{
		this.x = x;
		this.y = y;
		
		radius = 5;
		mass = 	1e22;
		
		vx = vy = 0;
		fx = fy = 0;
	}
	
	public double distance( Node n )
	{
		double dx = n.x - x, dy = n.y - y;
		return Math.sqrt( dx * dx + dy * dy );
	}
	
	public Point getPoint()
	{
		return new Point( (int)x, (int)y );
	}
}

public class Nbody extends JApplet implements KeyListener, MouseListener, MouseMotionListener
{
	private static final long serialVersionUID = 1662161030431704552L;
	
	private static final double G = 6.67428e-11;

	private double pscale = 1e6;
	private double vscale = 1e4;

	private double time = 0;
	private double time_slice = 1;
	
	private Vector<Node> nodes;
	private int selected;
	
	private enum Mode { running, stop };
	private Mode mode;
	
	private boolean showVelocity;

	private Timer timer;
	
	public void init()
	{
		nodes = new Vector<Node>();
		selected = -1;
		mode = Mode.stop;
		showVelocity = false;
		
		addKeyListener( this );
		addMouseListener( this );
		addMouseMotionListener( this );
	}
	
	public void paint( Graphics g )
	{
		int x, y, vx, vy, radius;
		Node n;
		String s;
		
		// clear screen
		g.clearRect( 0, 0, getWidth(), getHeight() );
		
		g.setColor( Color.black );

		s = String.format( "time: %.3e", time );
		g.drawString( s, 10, 13 );

		s = String.format( "time slice: %.3e", time_slice );
		g.drawString( s, 10, 26 );

		s = String.format( "position scale: %.3e", pscale );
		g.drawString( s, 10, 39 );

		s = String.format( "velocity scale: %.3e", vscale );
		g.drawString( s, 10, 52 );

		for(int i = 0; i < nodes.size(); i++) {
			n = nodes.elementAt( i );
			
			// draw node
			if( i == selected ) {
				g.setColor( Color.green );
			} else {
				g.setColor( Color.red );
			}

			x = (int)(n.x / pscale);
			y = (int)(n.y / pscale);
			vx = (int)(n.vx / vscale);
			vy = (int)(n.vy / vscale);
			radius = (int)n.radius;

			g.fillArc( x - radius, y - radius, 2 * radius, 2 * radius, 0, 360);

			g.setColor( Color.black );

			if( mode == Mode.stop || showVelocity ) {
				// draw velocity
				g.drawLine( x, y, x + vx, y + vy);
			}
			
			if( i == selected ) {
				s = String.format( "radius: %.3e", n.radius );
				g.drawString( s, 400, 13 );
				
				s = String.format( "mass: %.3e", n.mass );
				g.drawString( s, 400, 26 );
				
				s = String.format( "location: (%.3e, %.3e)", n.x, n.y );
				g.drawString( s, 400, 39 );
				
				s = String.format( "velocity: (%.3e, %.3e)", n.vx, n.vy );
				g.drawString( s, 400, 52 );
			}
		}
	}
	
	private void step( double time )
	{
		int i, j;
		double dist, dist2, force;
		double dx, dy, fx, fy;
		
		// mess and velocity
		double m1, m2, v1, v2;
		
		Node n, n1, n2;
		
		for(i = 0; i < nodes.size(); i++) {
			
			n1 = nodes.elementAt( i );
			m1 = n1.mass;
			
			n1.fx = n1.fy = 0;

			for(j = 0; j < nodes.size(); j++) {
				
				if( i == j ) {
					continue;
				}
				
				n2 = nodes.elementAt( j );
				m2 = n2.mass;
				
				dx = n2.x - n1.x;
				dy = n2.y - n1.y;
				dist2 = (dx * dx + dy * dy);
				dist = Math.sqrt( dist2 );
				force = G * m1 * m2 / dist2;
				n1.fx += force * dx / dist;
				n1.fy += force * dy / dist;

				// TODO: collision
			}
			
		}

		for(i = 0; i < nodes.size(); i++) {

			n = nodes.elementAt( i );

			n.vx += n.fx / n.mass * time;
			n.vy += n.fy / n.mass * time;

			n.x += n.vx * time;
			n.y += n.vy * time;

			n.fx = 0;
			n.fy = 0;
		}
	}
	
	public void keyPressed(KeyEvent ke)
	{
		if( ke.getKeyCode() == KeyEvent.VK_ESCAPE ) {
			nodes.clear();
			mode = Mode.stop;
			selected = -1;
			showVelocity = false;
			time = 0;
			time_slice = 1;
			timer.cancel();
		}
	}

	public void keyReleased(KeyEvent arg0) {}

	public void keyTyped(KeyEvent ke)
	{
		char c = ke.getKeyChar();
		Node n;
		
		if( selected != -1 ) {
			n = nodes.elementAt( selected);
			if( c == 'M' ) {
				n.mass *= 1.25;
			} else if( c == 'm' ) {
				n.mass *= 0.8;
			} else if( c == 'R' ) {
				n.radius++;
			} else if( c == 'r' ) {
				n.radius--;
			}
		}
		
		if( c == ' ' ) {
			if( mode == Mode.stop ) {
				mode = Mode.running;
				timer = new Timer();
				timer.schedule(
					new TimerTask(){
						public void run() {
							step(time_slice);
							time += time_slice;
							repaint();
						}
					}, 0, 30);
				
			} else {
				mode = Mode.stop;
				timer.cancel();
			}
		} else if( c == 'v' ) {
			showVelocity = !showVelocity;
		} else if( c == 'T' ) {
			time_slice *= 2;
		} else if( c == 't' ) {
			time_slice /= 2;
		}
		
		repaint();
	}

	private void leftClicked( MouseEvent me )
	{
		int cc = me.getClickCount();
		Node n;
		
		if( cc == 2 ) {
			// create a new node
			n = new Node(me.getX() * pscale, me.getY() * pscale);
			nodes.add( n );
			selected = nodes.indexOf( n );
		}
	}
	
	private void rightClicked( MouseEvent me )
	{
		if( selected != -1 ) {
			// set velocity
			Node n = nodes.elementAt( selected );
			int nx, ny;

			nx = (int)(n.x / pscale);
			ny = (int)(n.y / pscale);

			n.vx = (me.getX() - nx) * vscale;
			n.vy = (me.getY() - ny) * vscale;
		}
	}
	
	public void mouseClicked( MouseEvent me )
	{
		if( me.getButton() == MouseEvent.BUTTON1 ) {
			leftClicked( me );
		} else if( me.getButton() == MouseEvent.BUTTON3 ) {
			rightClicked( me );
		}
		
		repaint();
	}

	public void mouseEntered(MouseEvent arg0) {}

	public void mouseExited(MouseEvent arg0) {}

	public void mousePressed(MouseEvent me)
	{
		Node n;
		int nx, ny, mx, my;
		int dist2;
		
		if( me.getButton() == MouseEvent.BUTTON1 ) {
			// select a node
			selected = -1;
			for( int i = 0; i < nodes.size(); i++ ) {
				n = nodes.elementAt( i );

				nx = (int)(n.x / pscale);
				ny = (int)(n.y / pscale);

				mx = (int)me.getX();
				my = (int)me.getY();

				dist2 = (mx - nx) * (mx - nx) + (my - ny) * (my - ny);
				if(dist2 < n.radius * n.radius) {
					selected = i;
				}
			}
		}
	}

	public void mouseReleased(MouseEvent arg0) {}

	public void mouseDragged(MouseEvent me)
	{
		Node n;
		if( selected != -1 ) {
			n = nodes.elementAt( selected );
			n.x = me.getX() * pscale;
			n.y = me.getY() * pscale;
		}
		repaint();
	}

	public void mouseMoved(MouseEvent arg0) {}

}
