import acm.graphics.*;
import acm.program.*;
import java.awt.event.*;
import java.awt.*;
import acm.util.RandomGenerator;

public class Breakout extends GraphicsProgram {
   
   // private instance variables
   private GOval ball;
   private boolean isInPlay;
   private GRect paddle;
   public double dx = 2.0;
   public double dy = 2.0;
   // random number generator ala acm.jar
   private RandomGenerator rgen = RandomGenerator.getInstance();
   
   // public named constants
   public static final int BALL_SIZE = 20;
   public static final int PADDLE_HEIGHT = 10;
   public static final int PADDLE_WIDTH = 100;
   public static final double PAUSE_TIME = 10.0;  
   public static final int APPLICATION_HEIGHT = 600;
   public static final int APPLICATION_WIDTH = 400;
   
   /**
    * Invoking the start method in the main method
    * cause execution to continue from here.
    */
   public void run () {
      setUpGame();
      runGame();
   }
   
   /**
    * Creates the unmoving paddle and adds MouseListener
    */
   private void setUpGame() {  
      paddle = new GRect(getWidth()/2 - PADDLE_WIDTH/2,
                         getHeight() - PADDLE_HEIGHT/2,
                         PADDLE_WIDTH, PADDLE_HEIGHT);
      paddle.setFilled(true);
      add(paddle);
      addMouseListeners();
   }
   
   /**
    * Returns the ball that will bounce around the window
    */
   private GOval createBall() {
      int x = rgen.nextInt(0,getWidth()-BALL_SIZE);
      GOval tempBall =  new GOval(x,0,BALL_SIZE,BALL_SIZE);
      tempBall.setFilled(true);
      return tempBall;
   } 
   
   /**
    * Gives the initial instructions and adds the ball to the
    * canvas.  Calls the method to start moving the ball.
    */
   private void runGame() {
      GLabel message = 
         new GLabel("Click to start!",PADDLE_WIDTH,PADDLE_WIDTH);
      message.setFont("SansSerif-36");
      add(message);
      waitForClick();
      remove(message);
      ball = createBall();
      ball.setFillColor(Color.RED);
      add(ball);     
      playBall();
   }
   
   /**
    * Moves the ball dx pixels in the x direction
    * and dy pixels in the y direction, pauses so 
    * the ball can be redrawn and then calls
    * methods to check for wall and paddle collisions.
    */
   private void playBall() {
      while (isInPlay) {
         ball.move(dx,dy);
         pause(PAUSE_TIME);
         checkForWallBounces();
         checkForObjectBounces();
      }
   }
   
   /**
    * If ball is in collision with one of the 
    * walls, change its direction of 
    * movement by either reversing dx or dy
    */
   private void checkForWallBounces() {
      double x = ball.getX();
      double y = ball.getY();
      if (x < 0 || x + BALL_SIZE > getWidth()) dx = -dx; 
      if (y < 0 || y + BALL_SIZE > getHeight()) dy = -dy;
   }
   
   /**
    * If ball is in collision with the paddle,
    * this method changes the direction of ball movement
    * by reversing either dx or dy, depending on which
    * side of the paddle the ball contacts.
    */
   private void checkForObjectBounces() {
      GObject obj = getCollidingObject();
      if (obj == null) return;
      if (obj == paddle) {
         dy = -dy;       
      }
   }
   
   /**
    * Returns the GObject that the ball is currently
    * touching by checking all 4 corners of the ball
    * for contact in sequence.
    */
   private GObject getCollidingObject() {
      double x = ball.getX();
      double y = ball.getY();
      double d = BALL_SIZE;
      GObject obj = getElementAt(x,y);  // upper left corner
      if (obj == null) obj = getElementAt(x+d,y); // upper right
      if (obj == null) obj = getElementAt(x,y+d); // lower left
      if (obj == null) obj = getElementAt(x+d,y+d);// lower right
      return obj;
   }
   
   /**
    * Side effect is setting the boolean to start the
    * ball moving to true
    */
   public void mouseClicked(MouseEvent e) {
      isInPlay = true;
   }
   
   /**
    * Calls start method of Program class that in turn contains
    * a hidden call to the run method in this class
    */
   public static void main(String[] args) {
      new Breakout().start();
   }
}