import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.awt.event.*;
import java.applet.Applet;
import java.util.*;
import javax.swing.*;


public class Galaxy extends JApplet implements Runnable {
    static int topy = 500;
    static int topx = 500;
   
    public Galaxy(){
        init_galaxy();
                startover();
    }

    static Random rnd = new Random(); // used in Ball

     static int fps = 30;     
    int frameNumber = -1;
    int delay;
    boolean frozen = false;
    Thread animatorThread;
    private BufferedImage bimg;

// taken from gal.c
    static double FLOATRAND () {
        return    rnd.nextFloat();
    }

static boolean WRAP = true;        /* Warp around edges */
static boolean BOUNCE = true;        /* Bounce from borders */

static int MINSIZE= 1;
static int MINGALAXIES  = 1;
static int MAX_STARS    = 300;
static int MAX_IDELTAT    =50;
/* These come originally from the Cluster-version */
static int DEFAULT_GALAXIES  =5;
static int DEFAULT_STARS    =1000;
static int DEFAULT_HITITERATIONS  =7500;
static int DEFAULT_IDELTAT    =200;    /* 0.02 */
static double EPSILON =0.00000001;

static double sqrt_EPSILON =0.0001;

static double DELTAT =  MAX_IDELTAT * 0.0001;

static double GALAXYRANGESIZE  =0.1;
static double GALAXYMINSIZE  =0.1;
static double QCONS    =0.001;
static int COLORBASE = 312234;
static int NUMCOLORS = 256*256*256;
static int COLORSTEP = (int)NUMCOLORS / COLORBASE;

    void drawStar(int x,int y,int size, Graphics2D g) {
         g.fill(new Ellipse2D.Double((double)x, y, size, size));
    }

    
static Universe universes = new Universe();

static void
startover()
{
    Universe gp = universes;
    int         size = 1;
    int         i, j;    /* more tmp */
    double      w1, w2;    /* more tmp */
    double      d, v, w, h;    /* yet more tmp */

    gp.step = 0;

    //if (MI_BATCHCOUNT(mi) < -MINGALAXIES)
    //    free_galaxies(gp);
//    gp.ngalaxies = MI_BATCHCOUNT(mi);
    gp.ngalaxies = 5;
    if (gp.ngalaxies < -MINGALAXIES)
        gp.ngalaxies = rnd.nextInt(-gp.ngalaxies - MINGALAXIES + 1) + MINGALAXIES;
    else if (gp.ngalaxies < MINGALAXIES)
        gp.ngalaxies = MINGALAXIES;

    for (i = 0; i < gp.ngalaxies; ++i) {
        GalaxyObject    gt = new GalaxyObject();
        double      sinw1, sinw2, cosw1, cosw2;

        gt.galcol = new Color(rnd.nextInt(COLORBASE - 2));
        if (gt.galcol.getRGB() > 1)
            gt.galcol = new Color(gt.galcol.getRGB() + 2);    /* Mult 8; 16..31 no green stars */
        /* Galaxies still may have some green stars but are not all green. */

        if(gt.stars.size() > 0) {
            gt.stars = new Vector();
        }
        gt.nstars = 10; //(rnd.nextInt(MAX_STARS / 2)) + MAX_STARS / 2;
        w1 = 2.0 * Math.PI * FLOATRAND();
        w2 = 2.0 * Math.PI * FLOATRAND();
        sinw1 = Math.sin(w1);
        sinw2 = Math.sin(w2);
        cosw1 = Math.cos(w1);
        cosw2 = Math.cos(w2);

        gp.mat[0][0] = cosw2;
        gp.mat[0][1] = -sinw1 * sinw2;
        gp.mat[0][2] = cosw1 * sinw2;
        gp.mat[1][0] = 0.0;
        gp.mat[1][1] = cosw1;
        gp.mat[1][2] = sinw1;
        gp.mat[2][0] = -sinw2;
        gp.mat[2][1] = -sinw1 * cosw2;
        gp.mat[2][2] = cosw1 * cosw2;

        gt.vel[0] = FLOATRAND() * 2.0 - 1.0;
        gt.vel[1] = FLOATRAND()* 2.0 - 1.0;
        gt.vel[2] = FLOATRAND()* 2.0 - 1.0;
        gt.pos[0] = -gt.vel[0] * DELTAT *
            gp.f_hititerations + FLOATRAND()- 0.5;
        gt.pos[1] = -gt.vel[1] * DELTAT *
            gp.f_hititerations + FLOATRAND()- 0.5;
        gt.pos[2] = -gt.vel[2] * DELTAT *
            gp.f_hititerations + FLOATRAND()- 0.5;

        gt.mass = (int) (FLOATRAND()* 1000.0) + 1;

        gp.size = GALAXYRANGESIZE * FLOATRAND()+ GALAXYMINSIZE;

        for (j = 0; j < gt.nstars; ++j) {
            Star       st = new Star();
            double      sinw, cosw;

            
            w = 2.0 * Math.PI * FLOATRAND();
            sinw = Math.sin(w);
            cosw = Math.cos(w);
            d = FLOATRAND()* gp.size;
            h = FLOATRAND()* Math.exp(-2.0 * (d / gp.size)) / 5.0 * gp.size;
            if (FLOATRAND()< 0.5)
                h = -h;
            st.pos[0] = gp.mat[0][0] * d * cosw + gp.mat[1][0] * d * sinw +
                gp.mat[2][0] * h + gt.pos[0];
            st.pos[1] = gp.mat[0][1] * d * cosw + gp.mat[1][1] * d * sinw +
                gp.mat[2][1] * h + gt.pos[1];
            st.pos[2] = gp.mat[0][2] * d * cosw + gp.mat[1][2] * d * sinw +
                gp.mat[2][2] * h + gt.pos[2];

            v = Math.sqrt(gt.mass * QCONS / Math.sqrt(d * d + h * h));
            st.vel[0] = -gp.mat[0][0] * v * sinw + gp.mat[1][0] * v * cosw +
                gt.vel[0];
            st.vel[1] = -gp.mat[0][1] * v * sinw + gp.mat[1][1] * v * cosw +
                gt.vel[1];
            st.vel[2] = -gp.mat[0][2] * v * sinw + gp.mat[1][2] * v * cosw +
                gt.vel[2];

            st.vel[0] *= DELTAT;
            st.vel[1] *= DELTAT;
            st.vel[2] *= DELTAT;

            st.px = 0;
            st.py = 0;

            if (size < -MINSIZE)
                st.size = rnd.nextInt((-size - MINSIZE + 1) + MINSIZE);
            else if (size < MINSIZE)
                st.size = MINSIZE;
            else
                st.size = size;

            gt.stars.insertElementAt(st, j);
        }
        gp.galaxies.insertElementAt(gt, i);

    }

}
public static void init_galaxy()
{


        if (universes == null) {
                universes = new Universe();     
        }
        
        universes.f_hititerations = 100;

        universes.clip.left = 0;
        universes.clip.top = 0;
        universes.clip.right = topx;
        universes.clip.bottom = topy;

        universes.scale = (double) (universes.clip.right +
universes.clip.bottom) / 8.0;
        universes.midx = universes.clip.right / 2;
        universes.midy = universes.clip.bottom / 2;
        
}


void
draw_galaxy(Graphics2D g2)
{
    Universe  gp = universes;
    double      d;        /* tmp */
    int         i, j, k;    /* more tmp */

    for (i = 0; i < gp.ngalaxies; ++i) {
        GalaxyObject     gt = (GalaxyObject)(gp.galaxies.elementAt(i));

        for (j = 0; j < ((GalaxyObject)gp.galaxies.elementAt(i)).nstars; ++j) {
            Star       st = (Star)gt.stars.elementAt(j);
            double      v0 = st.vel[0];
            double      v1 = st.vel[1];
            double      v2 = st.vel[2];

            for (k = 0; k < gp.ngalaxies; ++k) {
                GalaxyObject     gtk = (GalaxyObject)gp.galaxies.elementAt(k);
                double      d0 = gtk.pos[0] - st.pos[0];
                double      d1 = gtk.pos[1] - st.pos[1];
                double      d2 = gtk.pos[2] - st.pos[2];

                d = d0 * d0 + d1 * d1 + d2 * d2;
                if (d > EPSILON)
                    d = gt.mass / (d * Math.sqrt(d)) * DELTAT * DELTAT * QCONS;
                else
                    d = gt.mass / (EPSILON * sqrt_EPSILON) * DELTAT * DELTAT * QCONS;
                v0 += d0 * d;
                v1 += d1 * d;
                v2 += d2 * d;
            }

            st.vel[0] = v0;
            st.vel[1] = v1;
            st.vel[2] = v2;

            d = (v0 * v0 + v1 * v1 + v2 * v2) / (3.0 * DELTAT * DELTAT);
            if (d > (double) COLORSTEP)
                st.color = new Color(gt.galcol.getRGB() + COLORSTEP - 1);
            else
                st.color = new Color(gt.galcol.getRGB() + ((int) d) % COLORSTEP);

            st.pos[0] += v0;
            st.pos[1] += v1;
            st.pos[2] += v2;

            if (st.px >= gp.clip.left &&
                st.px <= gp.clip.right - st.size &&
                st.py >= gp.clip.top &&
                st.py <= gp.clip.bottom - st.size) {
                //XSetForeground(display, gc, MI_WIN_BLACK_PIXEL(mi));
                g2.setColor(Color.black);
                drawStar(st.px, st.py, st.size, g2);
            }
            st.px = (int) (st.pos[0] * gp.scale) + gp.midx;
            st.py = (int) (st.pos[1] * gp.scale) + gp.midy;



            if (st.px >= gp.clip.left &&
                st.px <= gp.clip.right - st.size &&
            st.py >= gp.clip.top &&
            st.py <= gp.clip.bottom - st.size) {
                
            //    XSetForeground(display, gc, MI_PIXEL(mi, st.color));
                
                g2.setColor(st.color);
                drawStar(st.px, st.py, st.size, g2);
            }
        }

        for (k = i + 1; k < gp.ngalaxies; ++k) {
            GalaxyObject     gtk = (GalaxyObject)gp.galaxies.elementAt(k);
            double      d0 = gtk.pos[0] - gt.pos[0];
            double      d1 = gtk.pos[1] - gt.pos[1];
            double      d2 = gtk.pos[2] - gt.pos[2];

            d = d0 * d0 + d1 * d1 + d2 * d2;
            if (d > EPSILON)
                d = gt.mass * gt.mass / (d * Math.sqrt(d)) * DELTAT * QCONS;
            else
                d = gt.mass * gt.mass / (EPSILON * sqrt_EPSILON) * DELTAT * QCONS;
            d0 *= d;
            d1 *= d;
            d2 *= d;
            gt.vel[0] += d0 / gt.mass;
            gt.vel[1] += d1 / gt.mass;
            gt.vel[2] += d2 / gt.mass;
            gtk.vel[0] -= d0 / gtk.mass;
            gtk.vel[1] -= d1 / gtk.mass;
            gtk.vel[2] -= d2 / gtk.mass;
        }
        gt.pos[0] += gt.vel[0] * DELTAT;
        gt.pos[1] += gt.vel[1] * DELTAT;
        gt.pos[2] += gt.vel[2] * DELTAT;
    }

    gp.step++;
    if (gp.step > gp.f_hititerations * 4)
        startover();
}

void
release_galaxy()
{
    if (universes != null) {
        universes = null;
    }
}

void
refresh_galaxy()
{
    /* Do nothing, it will refresh by itself */
}










    public void init() {
        delay = (fps > 500) ? (500 / fps) : 100;
 
    }

    public void start() {
        if (frozen) {
            //Do nothing.  The user has requested that we
            //stop changing the image.
        } else {
            //Start animating!
            if (animatorThread == null) {
                animatorThread = new Thread(this);
            }
            animatorThread.start();
        }
    }

    public void stop() {
        //Stop the animating thread.
        animatorThread = null;
    }

    public void reset(int x, int y) {
        topx = x;
        topy = y;
        
        universes.clip.left = 0;
        universes.clip.right = topx;
        universes.clip.top = 0;
        universes.clip.bottom = topy;

    }

    public boolean mouseDown(Event e, int x, int y) {
        if (frozen) {
            frozen = false;
            start();
        } else {
            frozen = true;
            stop();
        }
        return true;
    }

    public void run() {
        //Just to be nice, lower this thread's priority
        //so it can't interfere with other processing going on.
        //Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

        //Remember the starting time.
        long startTime = System.currentTimeMillis();

        //This is the animation loop.
        while (Thread.currentThread() == animatorThread) {
            //Advance the animation frame.
            frameNumber++;

            //Display it.
            repaint();

            //Delay depending on how far we are behind.
            try {
                startTime += delay;
                Thread.sleep(Math.max(0, 
                        startTime-System.currentTimeMillis()));
            } catch (InterruptedException e) {
                break;
            }
        }
    }

   
   
    
    public Graphics2D createGraphics2D(int topx, int topy)
    {
        Graphics2D g2 = null;
        if (bimg == null || bimg.getWidth() != topx || bimg.getHeight() != topy) {
            bimg = (BufferedImage) createImage(topx, topy);
            reset(topx, topy);
        } 
        g2 = bimg.createGraphics();
        g2.setBackground(Color.black);
        g2.clearRect(0, 0, topx, topy);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);
        return g2;
    }


    public void paint(Graphics g) {
        Dimension d = getSize();
        Graphics2D g2 = createGraphics2D(d.width, d.height);
//        update(topx, topy, g2);
        draw_galaxy(g2);
        g2.dispose();
        g.drawImage(bimg, 0, 0, this);
    }




    public static void main(String argv[]) {
      
        final Galaxy galaxy = new Galaxy();
        JFrame f = new JFrame("Galaxy");
        f.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {System.exit(0);}
            public void windowDeiconified(WindowEvent e) { galaxy.start(); }
            public void windowIconified(WindowEvent e) { galaxy.stop(); }
        });
        f.getContentPane().add("Center", galaxy);
        f.pack();
        f.setSize(new Dimension(topx,topy));
        f.show();
        galaxy.start();
    }
}
