import javax.swing.JApplet;
import java.awt.*;
import java.awt.image.*;
import javax.imageio.ImageIO;
import java.io.File;

public class Worlds extends JApplet implements Runnable {
	Canvas C;
	Dimension CD;
	BufferedImage cout = null;
	Label L,Title;
	int Nbodies;
    Thread T;
    double G = 0.001;
    double dtime = 0.0001;
	double Vtangential;
	double Vradial;
    World W, WEnd=null;

	int timestep = -1;



   public void init() {

	   this.getContentPane().setLayout(new BorderLayout());

	   C = new Canvas();
	   this.getContentPane().add("Center",C);
	   L = new Label("Starting ...");
	   this.getContentPane().add("South",L);
       Title = new Label("Gravitational Collapse for the manhole cover");
	   this.getContentPane().add("North",Title);
		setVisible(true);

   }

   public void start() {

       Nbodies = 0;

	   // central body

	   WEnd = new World(0.5,0.5,0.,0.,1.);
       WEnd.setprevious(null);
	   World Wlast = WEnd;

		Nbodies = 1000;
	   for(int ir = 0;ir<Nbodies;ir++) {
			double theta = 2.*Math.PI*Math.random();
			double r = 0.3*Math.random();
            double X = 0.5 + r * Math.cos(theta);
            double Y = 0.5 + r * Math.sin(theta);
			Vtangential = 1.;
			Vradial = 0.0;
			double Mass = 1.0; 
            double Vx = -Vtangential* Math.sin(theta) + Vradial * Math.cos(theta);
			double Vy = Vtangential * Math.cos(theta) + Vradial * Math.sin(theta);
            W = new World(X,Y,Vx,Vy,Mass);
            W.setnext(Wlast);
            Wlast.setprevious(W);
            Wlast = W;
	   }
	   Title.setText("Gravitational Spin of "+Nbodies+" bodies");

	  T = new Thread(this);
	  T.setPriority(Thread.MIN_PRIORITY);
	  T.start();

   }

   public void run() {
	      timestep = 0;
          World Wthat;
		  World Wthis;
          World Wscratch;
		  long timestart;
		  double accelx,accely;

          while(true) {
             Wthis = WEnd.previous;
			 double dmin = 0.0001;
			 timestep++;
 	         timestart = System.currentTimeMillis();

			 while(Wthis!=null) {
                 accelx = 0.;
                 accely = 0.;
                 Wthat = WEnd;
                 while(Wthat != null) {
                     if(Wthis != Wthat) {
                        double r2 = dsquared(Wthis.Xpos,Wthis.Ypos,Wthat.Xpos,Wthat.Ypos);
						if(r2 < dmin) r2 = dmin;
                        double r = Math.sqrt(r2);
                         double GMr = G*Wthat.Mass/r2;
						 double daccelx = (Wthat.Xpos-Wthis.Xpos)*GMr/r;
						 double daccely = (Wthat.Ypos-Wthis.Ypos)*GMr/r;
	                     accelx += daccelx;
		                 accely += daccely;
                     }
                     Wthat = Wthat.previous;
                 }

                 Wthis.update(accelx,accely,dtime);
		        Wthis = Wthis.previous;

             }
			 long elapsed = System.currentTimeMillis() - timestart;
             repaint();

			 
		   if(timestep > 0 && timestep % 100 == 1 && cout != null) {
				try {
					String filename = "galaxy"+timestep+".gif";
					File imageFile = new File(filename);
					ImageIO.write(cout,"gif",imageFile);
					System.out.println("Wrote file "+imageFile);
				} catch (Exception e) {
					System.err.println("Error "+e);
				}
			}
			

			 System.out.println("Epoch "+timestep+", "+elapsed+" msecs per Epoch");
			 Title.setText(Nbodies+" bodies, Epoch "+timestep+", "+elapsed+" msecs per Epoch");
          }
   }

   double dsquared(double X, double Y, double X1, double Y1) {
	   return (X-X1)*(X-X1) + (Y-Y1)*(Y-Y1);
   }


	int width = 0;
	int height = 0;

	public void paint(Graphics G) {
	   int xpos,ypos;

		Dimension D = this.size();
		if(cout == null || width != D.width || height != D.height) {
			cout = new BufferedImage(D.width,D.height,BufferedImage.TYPE_INT_RGB);
		}
		width = D.width;
		height = D.height;

		Graphics2D g2 = cout.createGraphics();

		g2.setColor(Color.black);
		g2.fillRect(0,0,width,height);

		g2.setColor(Color.white);

	   World W = WEnd;
	   while(W!=null) {
		   xpos = (int) (width*W.Xpos);
		   ypos = (int) (height*W.Ypos);
	       g2.fillOval(xpos,ypos,4,4);
		   W = W.previous;
	   }

	   Graphics g = C.getGraphics();
	   g.drawImage(cout,0,0,this);

	   g2.dispose();
   }



   public String getAppletInfo () {
      return "Worlds by J.J.B.";
   }


}




class World {

	double Xpos,oldXpos;
	double Ypos,oldYpos;
    double Vx;
    double Vy;
    double Mass;
	World next = null;
	World previous = null;

	World (double X, double Y, double Vx0, double Vy0, double M) {
		Xpos = oldXpos = X;
		Ypos = oldYpos = Y;
        Vx = Vx0;
		Vy = Vy0;
        Mass = M;
		next = null;
		previous = null;
	}

    void update(double accelx, double accely, double dtime) {
		oldXpos = Xpos;
		oldYpos = Ypos;
		double dVx = accelx*dtime;
		double dVy = accely*dtime;
        Xpos += (Vx*dtime) + (0.5f*dVx*dtime);
        Ypos += (Vy*dtime) + (0.5f*dVy*dtime);
        Vx += dVx;
        Vy += dVy;

		// the next two lines implement "dark matter" that tends to pull 
		// everything towards the centre of the cluster
		Vx += -0.01*(0.5-Xpos) ;
		Vy += -0.01*(0.5-Ypos);


    }

	void setnext(World W) {
		next = W;
	}
	void setprevious(World W) {
		previous = W;
	}


}








