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, m1, dx, dy;
  int frame = 0;
  int time = 0;

  Color[] greyscale;


  public void init()
  {
    imw = size().width;
    imh = size().height;
    gw = imw/2;
    gh = imh/2;

    m0 = new double[gh][gw];
    m1 = new double[gh][gw];
    dx = new double[gh][gw];
    dy = new double[gh][gw];

    for(int j=0; j<gh; j++) for(int i=0; i<gw; i++)
    {
      m0[j][i] = 0;
      m1[j][i] = 0;
      dx[j][i] = 0.1*Math.sin(-i*3.14159/67 + 0.876) +
                 0.2*Math.sin( i*3.14159/15 - 0.136) +
                 0.3*Math.sin( i*3.14159/32 + 1.178) +
                 0.4*Math.sin( i*3.14159/19 - 0.214);
      dy[j][i] = 0.1*Math.sin( j*3.14159/41 - 1.146) +
                 0.2*Math.sin(-j*3.14159/60 + 1.236) +
                 0.3*Math.sin(-j*3.14159/53 + 0.378) +
                 0.4*Math.sin( j*3.14159/29 + 1.914);
    }

    greyscale = new Color[256];
    for(int i=0; i<256; i++) greyscale[i] = new Color(i, i, i);

    precalc();
  }

  public void start()
  {
    if(runThread == null)
    {
      runThread = new Thread(this);
      runThread.start();
    }
  }

  public void stop()
  {
    if(runThread != null)
    {
      runThread.stop();
      runThread = null;
    }
  }

  public void run()
  {
    im = createImage(imw, imh);
    img = im.getGraphics();

    while(true)
    {
      recalc();
      repaint();
      try { runThread.sleep(20); } catch (InterruptedException e) { return; }
    }

  }

  public void update(Graphics g)
  {
    paint(g);
  }

	public void paint(Graphics g)
	{
    if(img != null)
    {
      //- Draw image

      int mc;

      double[][] b0, b1;
      if(frame == 0) { b0 = m0; b1 = m1; } else { b0 = m1; b1 = m0; }

      for(int j=0; j<gh; j++) for(int i=0; i<gw; i++)
      {
        mc = (int)(b0[j][i]*256.0); if(mc > 255) mc = 255;
        img.setColor(greyscale[mc]);
        img.fillRect(i+i, j+j, 2, 2);
      }

      g.drawImage(im, 0, 0, this);
    }
    else
    {
      g.drawString("Loading...", 5, 15);
    }
	}

  int mouse_x = 0, mouse_y = 0;
  int mouse_oldx = 0, mouse_oldy = 0;
  boolean mouse_drag = true;

  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_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;
  }




double[][] dist_lookup;
double[][] ood2_lookup;
double[][] dirx_lookup;
double[][] diry_lookup;

double[][] grav_x, grav_y;


void precalc()
{
  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 ];

  grav_x = new double[ gh ][ gw ];
  grav_y = new double[ gh ][ gw ];

  int x, y;

  //  calculate the distance from the centre point (SIZE,SIZE) in (SIZE*2,SIZE*2) array

  float dx, dy, d2, d;

  for( y=0; y<gh*2; y++ )
  {
    for( x=0; x<gw*2; x++ )
    {
      if( x != gw && y != gh )
      {
        d2 = (dx = (x-gw)*(x-gw)) + (dy = (y-gh)*(y-gh));

        dist_lookup[ y ][ x ] = (d = 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;
      }
    }
  }
}



void calculate_gravity_field()
{
  int x, y;

  //  for each point in the grid

  for( y=0; y<gh; y++ )
  {
    for( x=0; x<gw; x++ )
    {
      //- Calculate gravity force vector

      int i, j;

      double vx=0, vy=0;

      for( j=0; j<gh; j++ )
      {
        for( i=0; i<gw; i++ )
        {
          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)

            vx += dirx_lookup[gh+y-j][gw+x-i] * mass[j][i] * oodist2;
            vy += diry_lookup[gh+y-j][gw+x-i] * mass[j][i] * oodist2;
          }
          else
          {
            //  TODO: Does it affect itself in any way?
          }
        }
      }

      grav_x[y][x] = vx;
      grav_y[y][x] = vy;
    }
  }

}










  public void recalc()
  {
    int i, j;

    //- Decide which buffer is primary and which is secondary

    double[][] b0, b1;
    if(frame == 0) { b0 = m0; b1 = m1; } else { b0 = m1; b1 = m0; }

    //- Add mouse generated matter

    if(mouse_drag)
    {
      int x, y;
      x = mouse_x/2;
      y = mouse_y/2;
      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) b0[j][i] += 0.5;
    }

    //- Calculate new speeds

    for(j=0; j<gh; j++) for(i=0; i<gw; i++)
    {
      dx[j][i] = 0.1*Math.sin(-i*3.14159/65 + 0.876 + time*0.119) +
                 0.2*Math.sin( i*3.14159/27 - 0.136 + time*0.227) +
                 0.3*Math.sin( i*3.14159/56 + 1.178 - time*0.145) +
                 0.4*Math.sin( i*3.14159/29 - 0.214 - time*0.282);
      dy[j][i] = 0.1*Math.sin( j*3.14159/19 - 1.146 + time*0.274) +
                 0.2*Math.sin(-j*3.14159/59 + 1.236 + time*0.124) +
                 0.3*Math.sin(-j*3.14159/50 + 0.378 - time*0.256) +
                 0.4*Math.sin( j*3.14159/43 + 1.914 + time*0.108);
    }

    //- Move matter

calculate_gravity_field()
    for(j=0; j<gh; j++) for(i=0; i<gw; i++) b1[j][i] = 0;

    int ii, jj;
    double newi, newj;
    double mm;
    double px, py;

    for(j=0; j<gh; j++) for(i=0; i<gw; i++)
    {
      mm = b0[j][i];

      newi = (double)(i) + dx[j][i] + (double)(gw);
      newj = (double)(j) + dy[j][i] + (double)(gh);

      ii = (int)newi; jj = (int)newj;

      px = newi-ii; py = newj-jj;

      b1[(jj  )%gh][(ii  )%gw] += mm*(1-px)*(1-py);
      b1[(jj  )%gh][(ii+1)%gw] += mm*(  px)*(1-py);
      b1[(jj+1)%gh][(ii  )%gw] += mm*(1-px)*(  py);
      b1[(jj+1)%gh][(ii+1)%gw] += mm*(  px)*(  py);
    }

    frame ^= 1;
    time ++;
  }

}






