import java.awt.GridLayout;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Dimension;
import java.awt.event.WindowEvent;
import java.awt.event.WindowAdapter;

import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;

import java.util.Timer;
import java.util.TimerTask;

import java.util.Random;

public class BallArena extends Component {

    private ArrayList balls;

    public BallArena() {
        balls = new ArrayList();
        balls.add(new BouncingBall(0.3,0.3,0.1, Color.red));
        balls.add(new BouncingBall(0.7,0.1,0.07, Color.green));
        balls.add(new BouncingBall(0.5,0.5,0.05, Color.blue));
        balls.add(new BouncingBall(0.1,0.1,0.15, Color.yellow));
    }

    public List getBalls() {
        return balls;
    }

    /**
     * paint draws the bouncing balls.
     */
    public void paint( Graphics g ) {
        Dimension sz = getSize();
        Iterator it = balls.iterator();
        while ( it.hasNext() ) {
            BouncingBall ball = (BouncingBall)it.next();
            ball.draw( g, sz );
        }
    }

    private static class MoveBalls extends TimerTask {
        private BallArena arena;

        public MoveBalls( BallArena arena ) {
            this.arena = arena;
        }

        public void run() {
            List l = arena.getBalls();
            Iterator it = l.iterator();
            while ( it.hasNext() ) {
                BouncingBall ball = (BouncingBall)it.next();
                ball.update();
            }
            /* causes the paint method to be invoked */
            arena.repaint();
        }
    }

    private static void dumpThreads( String msg ) {
        System.out.println();
        System.out.println( msg );
        Thread[] threads = new Thread[100];
        int num = Thread.enumerate( threads );
        for( int i = 0 ; i < num; i++ ) {
            System.out.println( threads[i] );
        }
    }

    public static void main( String[] args ) {
        BallArena.dumpThreads("start");
        Timer timer = new Timer( true );
        BallArena.dumpThreads("after timer");
        Frame frame = new Frame();
        BallArena.dumpThreads("after frame");
        /* static method inner class */
        frame.addWindowListener (
            new WindowAdapter() {
                public void windowClosing( WindowEvent e ) {
                    System.exit( 0 );
                }
            }
        );

        frame.setTitle( "Bouncing Balls" );
        frame.setLayout( new GridLayout(1,1) );
        BallArena ba = new BallArena();

        frame.add ( ba );
        frame.setBackground( Color.white );
        frame.pack();
        frame.setSize( 400, 400 );
        frame.show();
        timer.schedule( new MoveBalls( ba ), 100, 100 );
        BallArena.dumpThreads("main end");
    }
}

/**
 * Describes and controls the behaviour of a bouncing ball.
 */
class BouncingBall {
    private static Random rand = new Random();
    private double x, y, r;
    private double dx, dy;
    private Color colour;

    private final double SCALE = 8.0;

    public BouncingBall( double x, double y, double r, Color c ) {
        this.x = x;
        this.y = y;
        this.r = r;
        this.colour = c;
        this.dx = (rand.nextDouble()-0.5) / SCALE;
        this.dy = (rand.nextDouble()-0.5) / SCALE;
    }

    /* invoked by awt thread */
    public synchronized void draw( Graphics g, Dimension sz ) {
        int w = sz.width;
        int h = sz.height;
        int m = ( w < h ) ? w : h;
        int ix = (int)(w*x);
        int iy = (int)(h*y);
        int ir = (int)(m*r);
        int ir2 = ir/2;
        g.setColor( colour );
        g.fillOval( ix-ir2, iy-ir2, ir, ir );
    }

    /* invoked by timer thread  */
    public synchronized void update() {
        double tx = x + dx;
        if ( tx < 0.0 ) {
            dx = rand.nextDouble() / (2.0*SCALE);
            x += dx;
        }
        else if ( tx > 1.0 ) {
            dx = -rand.nextDouble() / (2.0*SCALE);
            x += dx;
        }
        else {
            x = tx;
        }
        double ty = y + dy;
        if ( ty < 0.0 ) {
            dy = rand.nextDouble() / (2.0*SCALE);
            y += dy;
        }
        else if ( ty > 1.0 ) {
            dy = -rand.nextDouble() / (2.0*SCALE);
            y += dy;
        }
        else {
            y = ty;
        }
    }
}
