/*
 * File: BouncingBall.java
 * -----------------------
 * This program graphically simulates multiple bouncing balls
 * @author Matt Oehrlein
 *
 */
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class BouncingBall extends java.applet.Applet implements Runnable, MouseListener {

	/** Applet window height in pixels	 */
	private static final int APPLET_HEIGHT = 500;
	/** Applet window width in pixels	 */
	private static final int APPLET_WIDTH = 500;
	/** Number of balls to spawn */
	private static final int MAX_NUM_BALLS = 10;
	/** Size (diameter) of the ball */
	private static final int DIAM_BALL = 30;
	/** Amount Y velocity is increased each cycle as a
	 * result of gravity */
	private static final double GRAVITY = .8;
	/** maximum deviation of velocities when spawning a new ball */
	private static final double VEL_DEVIATION = 20;
	/** Amount Y Velocity is reduced when it bounces */
	private static final double BOUNCE_REDUCE = 0.9;
	/** Starting X and Y Velocities */

	/* private instance variable */
	private Ball[] balls;
	private int ballAddCount = 0;
	private Random randVel = new Random();


	//Thread for handling the animation
	private Thread animator;
	private int frame;
	private int delayBetweenFrames;
	
	//Instance variables for handling the backbuffer
    Dimension offDimension;
    Image offImage;
    Graphics offGraphics;



	public void init(){
		/**
		 * Initialize the applet and compute the delay between frames.
		 */
		String str = getParameter("fps");
		int fps = (str != null) ? Integer.parseInt(str) : 10;
		delayBetweenFrames = (fps > 0) ? (1000 / fps) : 100;
		//create mouse event reader
		addMouseListener(this);

	}

	/** start thread
	 * 
	 */
	public void start() {
		animator = new Thread(this);
		setup();
		animator.start();
	}


	/** this is our new run routine which will spawn a thread to
	 * handle animation by calling the repaint method
	 */
	public void run() {
		// Remember the starting time
		long tm = System.currentTimeMillis();
		while (Thread.currentThread() == animator) {
			
			// Display the next frame of animation.
			repaint();
			
			/** This is probably bad idea, but i'm just going to hijack the
			 * animator thread at this point to do pretty much all of the processing.
			 * Ideally, this should be done in a separate thread. oh well!
			 */
			moveBalls();
			checkForCollision();
			
			// Delay depending on how far we are behind.
			try {
				tm += delayBetweenFrames;
				Thread.sleep(Math.max(0, tm - System.currentTimeMillis()));
			} catch (InterruptedException e) {
				break;
			}

			// Advance the frame
			frame++;
		}
	}

	/**
	 * This method is called when the applet is no longer
	 * visible. Set the animator variable to null so that the
	 * thread will exit before displaying the next frame.
	 */
	public void stop() {
		animator = null;
	}

	/**
	 * Paint display a frame if there is one already painted
	 */
	public void paint(Graphics g){
		if (offImage != null) {
			g.drawImage(offImage, 0, 0, null);
		}
	}
	/**
	 * Paint a frame of animation into the buffered frame
	 */
	public void update(Graphics g) {


		Dimension d = getSize();

		// Create the offscreen graphics context
		if ((offGraphics == null)
				|| (d.width != offDimension.width)
				|| (d.height != offDimension.height)) {
			offDimension = d;
			offImage = createImage(d.width, d.height);
			offGraphics = offImage.getGraphics();
		}

		// Erase the previous image
		offGraphics.setColor(getBackground());
		offGraphics.fillRect(0, 0, d.width, d.height);
		offGraphics.setColor(Color.black);

		// Paint the frame into the image
		paintFrame(offGraphics);

		// Paint the image onto the screen
		g.drawImage(offImage, 0, 0, null);
	}



	/**
	 * Paint a frame of animation.
	 */
	public void paintFrame(Graphics g) {
		g.setColor(Color.black);
		g.drawString("Frame " + frame, 0, 30);


		/**
		 * draw in all the balls where they're supposed to be 
		 */
		for (int i = 0; i < balls.length && balls[i] != null; i++){
			g.fillOval((int)balls[i].getX(), (int)balls[i].getY(), DIAM_BALL, DIAM_BALL);
		}
	}


	
	/** Create and place ball. */
	private void setup() {
		setSize(APPLET_WIDTH, APPLET_HEIGHT);
		balls = new Ball[MAX_NUM_BALLS];
	}

	/** Update and move ball */
	private void moveBalls() {
		for (int i = 0; (i < balls.length) && (balls[i] != null); i++){
			// increase yVelocity due to gravity on each cycle
			balls[i].setYVel(GRAVITY + balls[i].getYVel());
			balls[i].move(balls[i].getXVel(), balls[i].getYVel());
		}
	}
	
	/** Determine if collision with floor, update velocities
	 * and location as appropriate. */
	private void checkForCollision() {
		for (int i = 0; (i < balls.length)  && ( balls[i] != null); i++){
			// determine if ball has dropped below the floor
			if (balls[i].getY() > getHeight() - DIAM_BALL) {
				// change ball's Y velocity to now bounce upwards
				balls[i].setYVel(-(balls[i].getYVel()) * BOUNCE_REDUCE);
				// assume bounce will move ball an amount above the
				// floor equal to the amount it would have dropped
				// below the floor.
				double diff = balls[i].getY() - (getHeight() - DIAM_BALL);
				balls[i].move(0, -2 * diff);
			}
			//Determine if ball has hit the right wall
			if (balls[i].getX() > getWidth() - DIAM_BALL) {
				// change ball's X velocity to now bounce the opposite X direction
				balls[i].setXVel(-(balls[i].getXVel()) * BOUNCE_REDUCE);
				// assume bounce will move ball an amount away from the
				// wall equal to the amount it would have moved
				// into the wall.
				double diff = balls[i].getX() - (getWidth() - DIAM_BALL);
				balls[i].move(-2 * diff, 0);
			}
			//Determine if ball has hit the left wall
			if(balls[i].getX() <= 0){
				// change ball's X velocity to now bounce the opposite X direction
				balls[i].setXVel(-(balls[i].getXVel()) * BOUNCE_REDUCE);
				// assume bounce will move ball an amount away from the
				// wall equal to the amount it would have moved
				// into the wall.
				double diff = balls[i].getX();
				balls[i].move(-2 * diff, 0);
			}

			/** for collision detection, just propose that there is a 
			 * collision between every combination of balls on the screen and let the 
			 * handleBallCollision method decide if any action is to be done
			 * (note that this is pretty inefficient as there gets to be more
			 * balls on the screen, but we're limited to MAX_NUM_BALLS anyways.)
			 */
			for(int j = i+1; j< balls.length; j++){
				if ((balls[i] != null) && (balls[j] != null)){
					handleBallCollision(balls[i],balls[j]);
				}
			}
		}

	}

	/** Handles the collision between two balls by adjusting their velocities
	 * and positions. Both balls will reduce their velocities by BOUNCE_REDUCE
	 * @param b1 one ball that is colliding
	 * @param b2 the other ball that is colliding
	 */
	private void handleBallCollision (Ball b1,Ball b2){
		/**due to a poor (read: none) collision detection algorithm, we're going to verify
		 * that the euclidian distance between the centers (and therefore upper
		 * left corner) of each ball is less than twice the radius of each ball. 
		 * This will throw out the "false alarms"
		 */
		double xDiff;
		double yDiff;
		double dist;
		xDiff = (b1.getX() - b2.getX());
		yDiff = (b1.getY() - b2.getY());
		dist = Math.sqrt(Math.pow(xDiff, 2) + Math.pow(yDiff, 2));

		if (dist <= (DIAM_BALL)){
			/** At this point in the code, the balls have been verified
			 * to be (truly) colliding.. proceed with action
			 */

			//displace locations now (so that balls don't lock together)
			b1.move(-b1.getXVel(), -b1.getYVel());
			b2.move(-b2.getXVel(), -b2.getYVel());

			//going to keep this really simple right now.
			//for the time being, the balls just simply swap velocities
			//however, the velocities are reduced by BOUNCE_REDUCE

			double tmpXVel = b1.getXVel();
			double tmpYVel = b1.getYVel();
			b1.setXVel(b2.getXVel() * BOUNCE_REDUCE);
			b1.setYVel(b2.getYVel() * BOUNCE_REDUCE);
			b2.setXVel(tmpXVel * BOUNCE_REDUCE);
			b2.setYVel(tmpYVel * BOUNCE_REDUCE);


		}
	}

	@Override
	public void mouseEntered(MouseEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseExited(MouseEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mousePressed(MouseEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseReleased(MouseEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseClicked(MouseEvent e) {

		//random velocities to be generated by the randVel RandomGenerator
		//These velocities will be in the range of -VEL_DEVIATION to VEL_DEVIATION
		double xVel = (randVel.nextDouble() * VEL_DEVIATION * 2) - VEL_DEVIATION;
		double yVel = (randVel.nextDouble() * VEL_DEVIATION * 2) - VEL_DEVIATION;


		balls[ballAddCount % MAX_NUM_BALLS] = new Ball(e.getX(), e.getY(), DIAM_BALL,
				DIAM_BALL, xVel, yVel);
		
		ballAddCount++;

	}
}

/*
 * Class to implement ball object
 */

public class Ball{
	/** constructor for ball */
	public Ball (double x, double y, int w, int h, double xVel, double yVel){
		setYVel(yVel);
		setXVel(xVel);
		setX(x);
		setY(y);
		setWidth(w);
		setHeight(h);
	}
	public void move (double dx, double dy){
		setX(getX()+dx);
		setY(getY()+dy);
	}
	
	private void setHeight(int h) {
		height = h;
		
	}
	public int getHeight(){
		return height;
	}
	
	public int getWidth(){
		return width;
	}

	private void setWidth(int w) {
		width = w;
		
	}

	public void setYVel(double y){
		yVelocity = y;	
	}
	
	public void setXVel(double x){
		xVelocity = x;
	}
	
	private void setX(double x){
		xPos = x;
	}
	
	public double getX(){
		return xPos;
	}
	
	private void setY(double y){
		yPos = y;
	}
	
	public double getY(){
		return yPos;
	}
	
	public double getYVel(){
		return yVelocity;
	}
	public double getXVel(){
		return xVelocity;
	}
	
	/*Instance variables */
	private double yVelocity = 0;
	private double xVelocity = 0;
	private double xPos = 0;
	private double yPos = 0;
	private int width = 0;
	private int height = 0;
	
	
}


