import java.awt.*;
import java.applet.*;

public class Galaxy2 extends Applet implements Runnable
{
  Thread runThread = null;
  Image im = null;
  Graphics img = null;

  int imw, imh;

  //---- Galaxy data

  static int gw, gh;

  double[][] m0;  //  Mass double buffer 0
  double[][] m1;  //  Mass double buffer 1
  double[][] dx0, dx1;
  double[][] dy0, dy1;

  double[][] dist_lookup;
  double[][] ood2_lookup;
  double[][] dirx_lookup;
  double[][] diry_lookup;

  int frame = 0;
  int time = 0;

  double[][] potx, poty;

  Color[] greyscale;



  public void init()
  {
    int i, j;

    imw = size().width;
    imh = size().height;
    gw = imw/2;
    gh = imh/2;

    m0 = new double[gh][gw];   //  mass
    dx0 = new double[gh][gw];  //  x speed
    dy0 = new double[gh][gw];  //  y speed
    
    m1 = new double[gh][gw];
    dx1 = new double[gh][gw];
    dy1 = new double[gh][gw];

    //  Initial empty mass and speed buffers

    for(j=0; j<gh; j++) for(i=0; i<gw; i++)
    {
      m0[j][i] = 0;
      dx0[j][i] = 0;
      dy0[j][i] = 0;

      m1[j][i] = 0;
      dx1[j][i] = 0;
      dy1[j][i] = 0;
    }
    
    //- Gravity precalc

	  int x, y;
	
	  //  calculate the distance from the centre point (SIZE,SIZE) in (SIZE*2,SIZE*2) array
	
	  double dx, dy, d2, d;
	  
	  dist_lookup = new double[gh*2][gw*2];
	  ood2_lookup = new double[gh*2][gw*2];
	  dirx_lookup = new double[gh*2][gw*2];
	  diry_lookup = new double[gh*2][gw*2];
	
	  for( y=0; y<gh*2; y++ )
	  {
	    for( x=0; x<gw*2; x++ )
	    {
	      if( x != gw && y != gh )
	      {
	        dx = (x-gw);
	        dy = (y-gh);
	        
	        d2 = dx*dx + dy*dy;
	
	        dist_lookup[ y ][ x ] = (d = Math.sqrt( d2 ));  //  distance
	        ood2_lookup[ y ][ x ] = 1.f / d2;   //  one over distance squared, for gravitational force
	        dirx_lookup[ y ][ x ] = dx / d;     //  Unit direction vector
	        diry_lookup[ y ][ x ] = dy / d;
	      }
	      else  //  Special case for centre point
	      {
	        dist_lookup[ y ][ x ] = 0;
	        ood2_lookup[ y ][ x ] = 0;
	        dirx_lookup[ y ][ x ] = 0;
	        diry_lookup[ y ][ x ] = 0;
	      }
	    }
	  }
    
    //- Set up colour palette

    greyscale = new Color[256];
    for(i=0; i<256; i++) greyscale[i] = new Color(i, i, i);
  }



  //---- paint

	public void paint(Graphics g)
	{
    if(img != null)
    {
      //- Draw image

      int mc;

	    double[][] current_m, new_m;  //  Current and new mass
	    double[][] current_dx, new_dx;  //  Current and new x speed
	    double[][] current_dy, new_dy;  //  Current and new y speed
	
	    current_dx = dx0; new_dx = dx1;
	    current_dy = dy0; new_dy = dy1;
	
	    if(frame == 0)
	    {
	      current_m  =  m0; new_m  =  m1;
	      current_dx = dx0; new_dx = dx1;
	      current_dy = dy0; new_dy = dy1;
	    }
	    else
	    {
	      current_m  =  m1; new_m  =  m0;
	      current_dx = dx1; new_dx = dx0;
	      current_dy = dy1; new_dy = dy0;
	    }
    
      for(int j=0; j<gh; j++) for(int i=0; i<gw; i++)
      {
        mc = (int)(current_m[j][i]*32.0); if(mc > 255) mc = 255; if( mc < 0 ) mc = 0;
        img.setColor(greyscale[mc]);
        img.fillRect(i, j, 1, 1);

        mc = (int)(128.0+current_dx[j][i]*256.0); if(mc > 255) mc = 255; if(mc < 0) mc = 0;
        img.setColor(greyscale[mc]);
        img.fillRect(i, j+gh, 1, 1);

        mc = (int)(128.0+current_dy[j][i]*256.0); if(mc > 255) mc = 255; if(mc < 0) mc = 0;
        img.setColor(greyscale[mc]);
        img.fillRect(i+gw, j+gh, 1, 1);
      }

      g.drawImage(im, 0, 0, this);
    }
    else
    {
      g.drawString("Loading...", 5, 15);
    }
	}





  double total_energy, last_total_energy;



  public void recalc()
  {
    int i, j;

    int ii, jj;
    double newi, newj;
    double mm;
    double px, py;
    double  dx, dy;
    
    //- Decide which buffer is primary and which is secondary

    double[][] current_m, new_m;  //  Current and new mass
    double[][] current_dx, new_dx;  //  Current and new x speed
    double[][] current_dy, new_dy;  //  Current and new y speed

    if(frame == 0)
    {
      current_m  =  m0; new_m  =  m1;
      current_dx = dx0; new_dx = dx1;
      current_dy = dy0; new_dy = dy1;
    }
    else
    {
      current_m  =  m1; new_m  =  m0;
      current_dx = dx1; new_dx = dx0;
      current_dy = dy1; new_dy = dy0;
    }

    //- Add mouse generated matter

    if( mouse_drag )
    {
      int x, y;
      x = mouse_x;
      y = mouse_y;
      for(j=y-2; j<=y+2; j++) for(i=x-2; i<=x+2; i++)
        if(i >= 0 && i < gw && j >= 0 && j < gh)
        {
          current_m[j][i] += 10;
          current_dx[j][i] = mouse_dx * 0.145 + (j-y)*0.0121;
          current_dy[j][i] = mouse_dy * 0.145 + (x-i)*0.0121;
        }
    }
    
    //- Calculate new speeds
    
    int x, y;

    last_total_energy = total_energy;
    total_energy = 0;  //  Total energy ( mass * speed * speed ) // is it squared or not?

    for(j=0; j<gh; j++) for(i=0; i<gw; i++)
    {
	    px=0;
	    py=0;
	
      for( y=0; y<gh; y++ )
      {
        for( x=0; x<gw; x++ )
        {
          if( i != x && j != y )
          {
            double oodist2 = ood2_lookup[gh+y-j][gw+x-i];  //  one over the distance squared from (i,j) -> (x,y)

            px += dirx_lookup[gh+y-j][gw+x-i] * (current_m[y][x]+current_m[j][i]) * oodist2;
            py += diry_lookup[gh+y-j][gw+x-i] * (current_m[y][x]+current_m[j][i]) * oodist2;
          }
          else
          {
            //  TODO: Does it affect itself in any way?
          }
        }
      }
      
      px /= gw*gh;
      py /= gw*gh;

/*
      for(jj = -rad; jj <= rad; jj++) for(ii = -rad; ii <= rad; ii++)
      {
        px += potx[jj+rad][ii+rad] * b0[(j+jj+gh)%gh][(i+ii+gw)%gw];
        px += poty[jj+rad][ii+rad] * b0[(j+jj+gh)%gh][(i+ii+gw)%gw];
      }
*/

      if( current_m[j][i] > 0 )
      {
        current_dx[j][i] += px * current_m[j][i] / (1.d+current_m[j][i]);
        current_dy[j][i] += py * current_m[j][i] / (1.d+current_m[j][i]);
//        current_dy[j][i] *= 0.95;
  //      current_dx[j][i] *= 0.95;
      }
      
/*
      if(current_dx[j][i] < -2) current_dx[j][i] = -2;
      if(current_dx[j][i] >  2) current_dx[j][i] =  2;
      if(current_dy[j][i] < -2) current_dy[j][i] = -2;
      if(current_dy[j][i] >  2) current_dy[j][i] =  2;
*/
      total_energy += current_dx[j][i]*current_dx[j][i] + current_dy[j][i] * current_dy[j][i];
    }
    
    //- Move matter and velocities

    for(j=0; j<gh; j++) for(i=0; i<gw; i++)
    {
      new_m[j][i] = 0;
      new_dx[j][i] = 0;
      new_dy[j][i] = 0;
    }

   
  //  forward movement

    double a;

    for(j=0; j<gh; j++) for(i=0; i<gw; i++)
    {
      mm = current_m [j][i];  //  mass of this pixel last time
      
      if( mm == 0 ) continue;

      dx = current_dx[j][i];// / (mm+1.d);  //  mass of this pixel last time
      dy = current_dy[j][i];// / (mm+1.d);  //  mass of this pixel last time
      
      newi = (double)(i) + current_dx[j][i];//why?? + (double)(gw);
      newj = (double)(j) + current_dy[j][i];//why?? + (double)(gh);
      
      if( newi < 0 ) continue;
      if( newj < 0 ) continue;
      if( newi > gw-1 ) continue;
      if( newj > gh-1 ) continue;

//System.out.print( newj+" "+gh+"  " );
      
      ii = (int)newi; jj = (int)newj;
      if( ii < 0 ) ii = gw - ((-ii) % gw); else ii %= gw;
      if( jj < 0 ) jj = gh - ((-jj) % gh); else jj %= gh;
      
      px = newi - (int)newi;  //  Fractional parts of position
      py = newj - (int)newj;
      if( px < 0 ) px += 1;
      if( py < 0 ) py += 1;

      //  do friction from what it's hitting
/*      
      a =
          current_m [ (jj  )%gh ][ (ii  )%gw ] += mm * (1-px) * (1-py);
          current_m [ (jj  )%gh ][ (ii+1)%gw ] += mm * (  px) * (1-py);
          current_m [ (jj+1)%gh ][ (ii  )%gw ] += mm * (1-px) * (  py);
          current_m [ (jj+1)%gh ][ (ii+1)%gw ] += mm * (  px) * (  py);
          
      dx *= 1.d / (1.d+a+mm);  //  when a = 0, f = 1, as a -> oo, f -> 0
      dy *= 1.d / (1.d+a+mm);
*/
      //  Move mass
            
      new_m [ (jj  )%gh ][ (ii  )%gw ] += mm * (1-px) * (1-py);
      new_m [ (jj  )%gh ][ (ii+1)%gw ] += mm * (  px) * (1-py);
      new_m [ (jj+1)%gh ][ (ii  )%gw ] += mm * (1-px) * (  py);
      new_m [ (jj+1)%gh ][ (ii+1)%gw ] += mm * (  px) * (  py);
      
      //  Move speed

      new_dx[ (jj  )%gh ][ (ii  )%gw ] += dx * (1-px) * (1-py);
      new_dx[ (jj  )%gh ][ (ii+1)%gw ] += dx * (  px) * (1-py);
      new_dx[ (jj+1)%gh ][ (ii  )%gw ] += dx * (1-px) * (  py);
      new_dx[ (jj+1)%gh ][ (ii+1)%gw ] += dx * (  px) * (  py);
      
      new_dy[ (jj  )%gh ][ (ii  )%gw ] += dy * (1-px) * (1-py);
      new_dy[ (jj  )%gh ][ (ii+1)%gw ] += dy * (  px) * (1-py);
      new_dy[ (jj+1)%gh ][ (ii  )%gw ] += dy * (1-px) * (  py);
      new_dy[ (jj+1)%gh ][ (ii+1)%gw ] += dy * (  px) * (  py);
    }

/*
//  reverse motion (not good)

    for(j=0; j<gh; j++) for(i=0; i<gw; i++)
    {
      mm = current_m [j][i];  //  mass of this pixel last time
      dx = current_dx[j][i];  //  mass of this pixel last time
      dy = current_dy[j][i];  //  mass of this pixel last time

      newi = (double)(i) - 30*current_dx[j][i];
      newj = (double)(j) - 30*current_dy[j][i];
      
      if( newi < 0 ) continue;
      if( newj < 0 ) continue;
      if( newi > gw-1 ) continue;
      if( newj > gh-1 ) continue;

//System.out.print( newj+" "+gh+"  " );
      
      ii = (int)newi; jj = (int)newj;
      if( ii < 0 ) ii = gw - ((-ii) % gw); else ii %= gw;
      if( jj < 0 ) jj = gh - ((-jj) % gh); else jj %= gh;
      
      px = newi - (int)newi;  //  Fractional parts of position
      py = newj - (int)newj;
      if( px < 0 ) px += 1;
      if( py < 0 ) py += 1;

      //  Move mass
      
      new_m [j][i] =
                     current_m [ (jj  )%gh ][ (ii  )%gw ] * (1-px) * (1-py) +
                     current_m [ (jj  )%gh ][ (ii+1)%gw ] * (  px) * (1-py) +
                     current_m [ (jj+1)%gh ][ (ii  )%gw ] * (1-px) * (  py) +
                     current_m [ (jj+1)%gh ][ (ii+1)%gw ] * (  px) * (  py);

//      System.out.print( new_m[j][i]+","+current_m[j][i] );
            
      //  Move speed

      new_dx[j][i] =
                     current_m [ (jj  )%gh ][ (ii  )%gw ] * current_dx[ (jj  )%gh ][ (ii  )%gw ] * (1-px) * (1-py) +
                     current_m [ (jj  )%gh ][ (ii+1)%gw ] * current_dx[ (jj  )%gh ][ (ii+1)%gw ] * (  px) * (1-py) +
                     current_m [ (jj+1)%gh ][ (ii  )%gw ] * current_dx[ (jj+1)%gh ][ (ii  )%gw ] * (1-px) * (  py) +
                     current_m [ (jj+1)%gh ][ (ii+1)%gw ] * current_dx[ (jj+1)%gh ][ (ii+1)%gw ] * (  px) * (  py);
            
      new_dy[j][i] =
                     current_m [ (jj  )%gh ][ (ii  )%gw ] * current_dy[ (jj  )%gh ][ (ii  )%gw ] * (1-px) * (1-py) +
                     current_m [ (jj  )%gh ][ (ii+1)%gw ] * current_dy[ (jj  )%gh ][ (ii+1)%gw ] * (  px) * (1-py) +
                     current_m [ (jj+1)%gh ][ (ii  )%gw ] * current_dy[ (jj+1)%gh ][ (ii  )%gw ] * (1-px) * (  py) +
                     current_m [ (jj+1)%gh ][ (ii+1)%gw ] * current_dy[ (jj+1)%gh ][ (ii+1)%gw ] * (  px) * (  py);
            
    }
*/
    
    //- Move on

    frame ^= 1;
    time ++;
  }



  //---- GRAVITY CALCULATIONS
  
  //---- MOUSE STUFF

  int mouse_x = 0, mouse_y = 0;
  int mouse_oldx = 0, mouse_oldy = 0;
  int mouse_dx = 0, mouse_dy = 0;

  boolean mouse_drag = false;

  public boolean mouseDown(Event evt, int x, int y)
  {
    mouse_oldx = x;
    mouse_oldy = y;
    mouse_x = x;
    mouse_y = y;
    mouse_drag = true;
    
    //.. Mouse button pressed

    return true;
  }
  
  public boolean mouseDrag(Event e, int x, int y)
  {
    mouse_dx = x - mouse_x;
    mouse_dy = y - mouse_y;

    mouse_x = x;
    mouse_y = y;

    //.. Mouse is dragging

    return true;
  }

  public boolean mouseUp(Event evt, int x, int y)
  {
    mouse_drag = false;

    if(mouse_oldx == mouse_x && mouse_oldy == mouse_y)
    {
      //.. The mouse has been released on the same spot (might have moved while dragging tho)
    }
    else
    {
      //.. Mouse released in new position
    }

    return true;
  }
  

  
  //---- THREAD & APPLET GUBBINS
  
  //---- start
  
  public void start()
  {
    if(runThread == null)
    {
      runThread = new Thread(this);
      runThread.start();
    }
  }

  //---- stop
  
  public void stop()
  {
    if(runThread != null)
    {
      runThread.stop();
      runThread = null;
    }
  }

  //---- run
  
  public void run()
  {
    im = createImage(imw, imh);
    img = im.getGraphics();

    while(true)
    {
      recalc();
      repaint();
      try { runThread.sleep(20); } catch (InterruptedException e) { return; }
    }

  }

  //---- update
 
  public void update(Graphics g)
  {
    paint(g);
  }

}	






