import java.applet.*;
import java.awt.*; 
import java.util.*; 
//////////////////////////////////////////////////////////////////////
// class pushNode : a pushable node in the world, reacts to forces
//////////////////////////////////////////////////////////////////////
class pushNode
{
  public static final double CROSSLEN = 0.025;
  public static final double DEFAULT_MASS = 1.0;
  public static final boolean MOVEABLE=false,FIXED=true;
  public static final int X=0,Y=1;
  private static Random rand = null;
  static {
    Date d = new Date();
    rand = new Random();
    rand.setSeed(d.getSeconds());
  }
  
  public pushNode(Graphics g, boolean fixedFlag, double fixedX, double fixedY){
    itsVel[X] = 0.0;
    itsVel[Y] = 0.0;
    itsMass = DEFAULT_MASS;
    itsFixedFlag = fixedFlag;
    graphicsContext = g;
    graphicsContext.setXORMode(Color.white);
    
    if( itsFixedFlag ){
      itsPos[X] = fixedX;
      itsPos[Y] = fixedY;
      itsMass *= 1.5;
    } else {
      itsPos[X] = rand.nextDouble();
      itsPos[Y] = rand.nextDouble();
    }    
  }
  public void reset(){
    this.render(); // erase it in XOR mode
    itsPos[X] = rand.nextDouble();
    itsPos[Y] = rand.nextDouble();
    this.render(); // draw the new pos
  }
  public double get_pos(int dim) {
    return itsPos[dim];
  }
  public double get_mass() { 
    return itsMass;
  }
  public void set_mass(double m) { 
    itsMass = m;
  }
  public void react(double forceX, double forceY, double friction, double tick) {
    // s = vt + 1/2 at^2
    double accX, accY;
    double velX, velY;
    double posX, posY;
    
    this.render(); // erase it [in XOR mode]
    
    accX = forceX/(friction*itsMass);
    accY = forceY/(friction*itsMass);
    
    // V'=    V      +   A     T
    velX = itsVel[X] + accX * tick - friction*itsVel[X];
    velY = itsVel[Y] + accY * tick - friction*itsVel[Y];
    
    // S'=     S     +     V        T   + 1/2     A       T^2
    posX = itsPos[X] + itsVel[X] * tick + 0.5 * accX * tick*tick;
    posY = itsPos[Y] + itsVel[Y] * tick + 0.5 * accY * tick*tick;
    
    if( posX > 1.0 || posX < 0.0 ){
      velX *= -0.5;
      itsVel[X] = velX;
    } else if( posY > 1.0 || posY < 0.0 ){
      velY *= -0.5;
      itsVel[Y] = velY;
    } else {
      itsVel[X] = velX;
      itsVel[Y] = velY;
      
      itsPos[X] = posX;
      itsPos[Y] = posY;
    }
    this.render(); // draw it in XOR mode
  }
  public void render() {
    int x1, x2, y1, y2;
    x1 = (int)((itsPos[X] + CROSSLEN) * winSize);
    x2 = (int)((itsPos[X] - CROSSLEN) * winSize);
    y1 = (int)(itsPos[Y] * winSize);
    y2 = (int)(itsPos[Y] * winSize);
    graphicsContext.drawLine(x1,y1,x2,y2);
    x1 = (int)(itsPos[X] * winSize);
    x2 = (int)(itsPos[X] * winSize);
    y1 = (int)((itsPos[Y] + CROSSLEN) * winSize);
    y2 = (int)((itsPos[Y] - CROSSLEN) * winSize);
    graphicsContext.drawLine(x1,y1,x2,y2);
  }
  
  public boolean fixed() { 
    return itsFixedFlag; 
  }
  private Graphics graphicsContext = null;
  private double   itsMass = 1.0;
  private double   itsPos[] = new double[2];
  private double   itsVel[] = new double[2];
  private boolean  itsFixedFlag = false;
  private int      winSize = 500;
}
//////////////////////////////////////////////////////////////////////
// class pushWorld : creates and maintains a group of pushNodes
//////////////////////////////////////////////////////////////////////
class pushWorld {
  // class globals and constants
  public static final int numNodes = 100;
  private pushNode nodeList[] = new pushNode[numNodes];
  private double gravity = 0.001; 
  private double friction = 0.8;
  private double tick = 0.2;
  // constructor - allocate all the pushNodes
  public pushWorld(Graphics g){
    for(int i = 0; i < numNodes; i++)
      nodeList[i] = new pushNode(g, pushNode.MOVEABLE,0.0,0.0);
  }
 
  // render the world
  public void render() {
    pushNode thisNode = null, otherNode=null;
    int   target;
    int   other;
    double d_s;
    double x,y,otherX,otherY;
    double thisMass,otherMass;
    double dist;
    double distVX, distVY;
    double thisVectorX,thisVectorY;
    double forceVectorX,forceVectorY;
    double f;
    
    // for all objects in the world...
    for( target = 0; target < numNodes; target++ ){
    
      thisNode = nodeList[target];
      if( thisNode.fixed() )
	continue;
      
      forceVectorX = forceVectorY = 0.0; 
      x = thisNode.get_pos(pushNode.X);
      y = thisNode.get_pos(pushNode.Y);
      thisMass = thisNode.get_mass();
      
      // ... determine influence of all other objects
      for( other = 0; other < numNodes; other++ ){
	
	if( other == target )
	  continue;
	
	otherNode = nodeList[other];
	
	otherX = otherNode.get_pos(pushNode.X);
	otherY = otherNode.get_pos(pushNode.Y);
	
	// distance squared
	d_s = 0.0;			
	
	// make a vector from the other point towards this point
	distVX = x - otherX;
	distVY = y - otherY;
	
	// calculate the square of the distance to the other point
	d_s = distVX * distVX + distVY * distVY;
	
	if(d_s < 1.0e-6 )
	  continue;
	
	// kludgy hack, ignore 'far' objects
	if( d_s > 0.2 )
	  continue;
	
	dist = Math.sqrt(d_s);
	
	distVX /= dist;
	distVY /= dist;
	
	// the force is the inverse of the square of the distance
	otherMass = otherNode.get_mass();
	f = gravity * (thisMass * otherMass) / d_s;
	
	thisVectorX = f * distVX;
	thisVectorY = f * distVY;
	
	forceVectorX += thisVectorX;
	forceVectorY += thisVectorY;
      }
      // react to the sum force, this will render the node
      thisNode.react(forceVectorX,forceVectorY,friction,tick);
    }
  }
  public void renderNodes() {
    for(int i = 0 ; i < numNodes ; i++ ) {
      pushNode p = nodeList[i];
      p.render();
    }
  } 
  public void reset() {
    for(int i = 0 ; i < numNodes ; i++ ) {
      pushNode p = nodeList[i];
      p.reset();
    }
  }
}
//////////////////////////////////////////////////////////////////////
// class myApp : my first real java applet
//////////////////////////////////////////////////////////////////////
public class repelDemo extends Applet implements Runnable {
  private pushWorld theWorld = null;
  private Thread runner = null;
  private double curTime = 0.0;
  private Graphics graphicsContext = null;
  // init the applet, set up the UI
  // allocate a pushWorld
  public void init() {    
    this.resize(550,550);
    setLayout(new BorderLayout());
    Panel p = new Panel();
    p.setLayout( new FlowLayout() );
    p.add(new Button("Run"));
    p.add(new Button("Stop"));
    p.add(new Button("Reset"));
    Panel cp = new Panel();
    cp.setLayout( new FlowLayout() );
    Canvas canvas = new Canvas();
    canvas.resize(502,502);
    cp.add(canvas);
    add("North", p );
    add("South", cp );
    graphicsContext = canvas.getGraphics();
    theWorld = new pushWorld(graphicsContext);
  }
  public void run(){
    boolean done = false;
    while( done == false ) {
      curTime += 0.1;
      if( curTime > 100.0 ){
	done = true;
	curTime = 0.0;
	runner.suspend();
      } else {
	update(this.getGraphics());
      }
      try {
	runner.sleep(30);
      }
      catch( Throwable e) {}
      runner.yield(); // be nice
    }
  }
  
  void startRunner(){
    if( runner != null ) {
      if( runner.isAlive() )
	runner.resume();
    } else {
      runner = new Thread(this);
      runner.setPriority(Thread.MIN_PRIORITY);
      runner.start();
    }
    drawWorld();
  }
  public void start() {
    System.out.println("start()");
    drawWorld();
  }
  
  public void stop() {
    System.out.println("stop()");
    if( runner != null && runner.isAlive() ){
      drawWorld();
      runner.suspend();
    }
  }
  public void destroy() {
    System.out.println("destroy()");
    runner.suspend();
    drawWorld();
  }
  public void reset() {
    theWorld.reset();
    drawWorld();
  }
  void drawWorld() {
    graphicsContext.clearRect(1,1,500,500); 
    graphicsContext.drawRect(0,0,501,501);    
    theWorld.renderNodes();
  }
  public void paint(Graphics g){
    System.out.println("paint()");   
    theWorld.render();    
  }
  public boolean handleEvent(Event e){
    if( e.id == Event.WINDOW_DESTROY )
      System.exit(0);
    return( super.handleEvent(e) );
  }
  public boolean action(Event e, Object arg){
    System.out.println(arg);
    
    if( arg.equals("Run") ){
      startRunner();
      drawWorld();
    }
    
    if( arg.equals("Stop") )
      stop();
    if( arg.equals("Reset") ){
      stop();
      reset();
    }
    return( super.action(e,arg) );
  }
}