import java.awt.event.*;
import java.awt.*;
import java.applet.*;

public class Firework extends Applet implements Runnable {

    private static Color color() {
        return new Color(
                (float) Math.random(),
                (float) Math.random(),
                (float) Math.random()
                );
    }
    private static Color brightColor() {
        return new Color(
                (float) (0.5+Math.random()/2),
                (float) (0.5+Math.random()/2),
                (float) (0.5+Math.random()/2)
                );
    }
    private static double GRAVITY = 0.25;

    private int fw_count = 0;
    private FW firstFW;

    static abstract class FW {
        double x;
        double y;
        double dx;
        double dy;
        public FW(double x, double y, double dx, double dy) {
            this.x = x;
            this.y = y;
            this.dx = dx;
            this.dy = dy;
        }
        public boolean advance() {
            dy -= GRAVITY;
            x+=dx;
            y+=dy;
            return dy > 0 || x > 0;
        }
        public abstract void draw(Graphics g, int w, int h);
        private FW next = null;
        public FW next() {
            return next;
        }
        public void setNext(FW next) {
            this.next = next;
        }
        public void rotate(double a) {
            double s = Math.sin(a);
            double c = Math.cos(a);
            dx = dx * c + dy * s;
            dy = dx * -s + dy * c;
        }
        public static FW spawn(int w, int h) {
            double x = Math.random() * w;
            double y = Math.sqrt(h/2) - Math.random()*3;
            FW fw;
            double chance = Math.random();
            if(chance < 0.1) {
                fw = new Shell(x,0,0,y);
            } else {
                fw = new Birtha(x,0,0,y);
            }
            fw.rotate(Math.random()*0.5-0.25);
            return fw;
        }
    }

    static class Shell extends FW {
        Color color;
        public Shell(double x,double y,double dx,double dy) {
            super(x,y,dx,dy);
            this.color = brightColor();
        }
        public Shell(double x,double y,double dx,double dy,Color color) {
            super(x,y,dx,dy);
            this.color = color;
        }
        public void draw(Graphics g,int w, int h) {
            g.setColor(color);
            g.fillOval(w-(int)x,h-(int)y,5,5);
        }
    }

    static class Spark extends FW {
        Color color;
        int life;
        public Spark(double x,double y,double dx,double dy,Color color, int life) {
            super(x,y,dx,dy);
            this.color = color;
            this.life = life;
        }
        public boolean advance() {
            dy -= GRAVITY;
            dx *= 0.9;
            dy *= 0.9;
            x+=dx;
            y+=dy;
            if((life % 10) == 0) color = color.brighter();
            return life-- > 0;
        }
        public void draw(Graphics g,int w, int h) {
            g.setColor(color);
            g.drawLine(w-(int)x,h-(int)y,w-(int)(x-dx),h-(int)(y-dy));
        }
    }

    static class Birtha extends FW {
        Color color;
        public Birtha(double x,double y,double dx,double dy) {
            super(x,y,dx,dy);
            color = brightColor();
        }
        private void explode() {
            Color color = color();
            int count = 100 + (int) (Math.random()*50);
            while(count-->0) {
                FW fw = new Spark(x,y,0.0,Math.random()*8.0,color,50+(int)(Math.random() * 50));
                fw.rotate(Math.random()*Math.PI*2);
                fw.setNext(next());
                setNext(fw);
            }
        }
        public boolean advance() {
            super.advance();
            if(dy < 0) {
                explode();
                return false;
            } else {
                return true;
            }
        }
        public void draw(Graphics g,int w, int h) {
            g.setColor(color);
            g.fillOval(w-(int)x,h-(int)y,5,5);
        }
    }

    void spawnFW() {
        Dimension d = getSize();
        int w = d.width;
        int h = d.height;
        FW fw = FW.spawn(w,h);
        fw.setNext(firstFW);
        firstFW = fw;
    }

    private Thread myThread;
    private volatile boolean running;

    public void init() {
        enableEvents(AWTEvent.COMPONENT_EVENT_MASK);
    }
    protected void processComponentEvent(ComponentEvent ce) {
        if(ce.getID() == ComponentEvent.COMPONENT_RESIZED) {
            synchronized(this) {
                if(offscreen != null) {
                    offscreen = null;
                    g.dispose();
                    g = null;
                }
            }
        }
    }
    Image offscreen;
    Graphics g;
    public void start() {
        super.start();
        running = true;
        myThread = new Thread(this);
        myThread.start();
    }
    public void stop() {
        super.stop();
        running = false;
        try {
            synchronized(this) {
                notify();
            }
            myThread.join();
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        }
        offscreen = null;
        g.dispose();
        g = null;
    }
    public void run() {
        long lastSleep = System.currentTimeMillis();
        while(running) {
            if(offscreen != null) {
                /*
                 * Spawn new Fireworks
                 */
                if(Math.random() < 0.1) {
                    spawnFW();
                }
                /*
                 * Physics
                 */
                FW current = firstFW;
                FW prev = null;
                while(current != null) {
                    boolean keep = current.advance();
                    if(!keep) {
                        if(prev == null) {
                            firstFW = current;
                        } else {
                            prev.setNext(current.next());
                        }
                    } else {
                        prev = current;
                    }
                    current = current.next();
                }
                /*
                 * Drawing
                 */
                synchronized(this) {
                    int w = offscreen.getWidth(null);
                    int h = offscreen.getHeight(null);
                    g.setColor(new Color(0,0,0,16));
                    g.fillRect(0,0,w,h);
                    current = firstFW;
                    while(current != null) {
                        current.draw(g,w,h);
                        current = current.next();
                    }
                    repaint();
                    try {
                        wait();
                    } catch (InterruptedException ie) {
                        ie.printStackTrace();
                    }
                }
                myThread.yield();
            } 
        }
    }
    public void update(Graphics g) {
        paint(g);
    }
    public void paint(Graphics g) {
        synchronized(this) {
            Dimension d = getSize();
            int w = d.width;
            int h = d.height;
            if(offscreen == null) {
                offscreen = createImage(w,h);
                this.g = offscreen.getGraphics();
                this.g.setColor(Color.black);
                this.g.fillRect(0,0,w,h);
            }
            g.drawImage(offscreen,0,0,null);
            notify();
        }
    }
}
