/**Brian Alson(bda9), Venkat Ganesh (vsg3)
 * Approx. Time: 5 hrs each*/
/** Task 5: static funtion Bricks(int speed, int hits, int time, int n) - Begins a new game with 4 arguments, an integer for the speed 
 * (1- 5 are normally best), an integer for the number of hits required to clear each brick(1-3), an integer for time the program 
 * should run, and an integer for the number of balls (1-5). 25 bricks of 3 colors (red, green, and blue) appear in random arrangement 
 * (the same color does not appear on any adjacent brick). n Balls are of different random colors are created. One paddle is created for each 
 * Ball, the same color as the Ball. The paddle moves with the ball and stops the ball from hitting the bottom surface. As each brick 
 * is hit, it dulls or disappears depending on the number of hits required to clear each brick. The balls deflect when they hit the walls,
 * paddle, bricks or other balls. The game ends when all 25 bricks are cleared or the time runs out. The number of bricks cleared and the 
 * time taken is then printed. */

import java.awt.*;
import java.util.Random;

/** Assignment A4: using a Turtle */
public class Ball extends YourTurtle  {
  private int radius;
  private int vx;
  private int vy;
  
  /** Constructor: a window without anything in it */
  public Ball() {
      clear();
  }
  
  /** = the radius */
  public int getRadius(){
    return radius;
  }
  
  /** = the x velocity */
  public int getVX(){
    return vx; 
  }
  
  /** = the y velocity */
  public int getVY(){
    return vy; 
  }
  
  /** private function to set vx */
  private void setVX(int vx2){
    vx= vx2;
  }
  
  /** private function to set vy */
  private void setVY(int vy2){
    vy= vy2;
  }
  
  
  /** Constructor:  a Ball at position (x, y) with x-velocity vx2, y-velocity vy2, radius r, and Color c.  Note that c must be
   * in the form java.awt.Color.* where * is a valid color in class Color. */
  public Ball(int x, int y, int vx2, int vy2, int r, Color c){
    moveTo(x, y, 0);
    vx= vx2;
    vy= vy2;
    radius= r;
    setColor(c);
    fillCircle(r*2);
  }
  
  /** Constructor:  a Ball at position (x, y) with x-velocity vx2, y-velocity vy2, radius r, and Color c.  Note that c must be
   * an integer value between 1 and 13 (Anything outside this range defaults to black). The colors are as follows:
   * 1 = black, 2 = blue, 3 = cyan, 4 = dark gray, 5 = gray
   * 6 = green, 7 = light gray, 8 = magenta, 9 = orange
   * 10 = pink, 11 = red, 12 = white, 13 = yellow */
  public Ball(int x, int y, int vx2, int vy2, int r, int c){
    moveTo(x, y, 0);
    vx= vx2;
    vy= vy2;
    radius= r;
    setColor(c);
    fillCircle(r*2);
  }
  
  /** Constructor: a Black Ball at center of the window with x-velocity vx2, y-velocity vy2, and radius r. */
  public Ball(int vx2, int vy2, int r){
    this(250, 250, vx2, vy2, r, Color.black);
  }
  
  /** Causes the current Ball to move by erasing the current Ball, then drawing another vx and vy away.
   *  Ball also bounces off of the walls when it encounters them.*/
  public void moveBallOnce() {
    Color save= getColor();
    setColor(Color.white);
    fillCircle(2*radius);
    setColor(save);
    if ((getY() <= radius && vy < 0) || (getY() >= 500-radius && vy > 0)) vy= -vy;
    if ((getX() <= radius && vx <= 0) || (getX() >= 500-radius && vx >= 0)) vx= -vx;
    moveTo(getX()+vx, getY()+vy, 0);
    fillCircle(2*radius);
  }
  /** Causes an infinite loop that causes the current Ball to move after pausing 100 microseconds */
  public void inMotion(){
    // pre: A Ball anywhere on screen
    // inv: Ball continues to move infinitely
    while(1 == 1){
      pause(100);
      moveBallOnce();
    }
    // post: The Ball continues to move until reset.
  }
  
  // Code for Bricks Game
  private int reqHits= 0; // =number of further hits required to clear each brick
  private Color tempSave; // =the starting color of each brick
  
  private static Ball[] arrRect= new Ball[25]; // an array where each rectangle is referenced
  private static Ball[] b= new Ball[5]; // an arrary of Balls to be used
  private static Ball general= new Ball(0, 0, 0);  // a Ball used for non-static functions
  
  private static int counter=0; 
  private static int hitCount= 0; // =the number of bricks hit
  
  private static Random rand1= new Random();  // used for randomizations
  
  private static Color db1= new Color(0, 0, 255, 150);  // a medium blue used for multiple hits.
  private static Color db2= new Color(0, 0, 255, 75);   // a light blue used for multiple hits.
  private static Color dg1= new Color(0, 255, 0, 150);  // a medium green used for multiple hits.
  private static Color dg2= new Color(0, 255, 0, 75);   // a light green used for multiple hits.
  private static Color dr1= new Color(255, 0, 0, 150);  // a medium red used for multiple hits.
  private static Color dr2= new Color(255, 0, 0, 75);   // a light red used for multiple hits.
  
  /** A private function that returns 0 if red, 1 if blue, or 2 if green. Used while creating rectangles */  
  private static int rgbColor(Color c){
    if (c == Color.red) return 0;
    if (c == Color.blue) return 1;
    if (c == Color.green) return 2;
    else return 5;
  }
  
  /** Clears the screen and creates 25 rectangles in random arrangement with the condition that the same color does 
   * not appear in adjacent rectangles and stores each one of them in an array of class Balls */
  private static void createRects(int noOfHits) {
    general.clear();
    int  w= 50, h= 10, color= 0;
    //inv: every row before w-1 has 5 rectangles each
    while (w < 501) {
      //inv: h-1 columns in row w have bricks added and each brick is stored in the array
      while (h < 101) { 
        arrRect[counter]= new Ball(0, 0, 0);
        color= rand1.nextInt(3);
        if (counter >= 1) {
          //inv: color is the same as the color of the previous block 
          while (color == rgbColor(arrRect[counter - 1].getColor())){
            color= rand1.nextInt(3);
          }
        }
        if (counter >= 5) {
          //inv: color is the same as the color of the block to the immediate left or above
          while (color == rgbColor(arrRect[counter - 1].getColor()) || color == rgbColor(arrRect[counter - 5].getColor())){
            color= rand1.nextInt(3);
          }
        }
        if (color==1) arrRect[counter].setColor(2);
        else if (color==2) arrRect[counter].setColor(6);
        else if (color==0)  arrRect[counter].setColor(11);
        arrRect[counter].moveTo(w, h, 0); 
        arrRect[counter].fillRectangle(100, 20); 
        arrRect[counter].reqHits= noOfHits; 
        arrRect[counter].tempSave= arrRect[counter].getColor();
        counter++;
        h= h + 20;
      }
      w= w + 100;
      h= 10;
    }
  }
  
  /** Checks for the reqHits number and determines which color the new Rectangle must be drawn*/
  private static void hitCheck() {
    if (arrRect[counter].reqHits== 3)  {
      arrRect[counter].reqHits--;
      arrRect[counter].setColor(12);
      arrRect[counter].fillRectangle(100, 20);
      if (arrRect[counter].tempSave.equals(Color.blue))arrRect[counter].setColor(db1);
      else if (arrRect[counter].tempSave.equals(Color.green)) arrRect[counter].setColor(dg1);
      else if (arrRect[counter].tempSave.equals(Color.red)) arrRect[counter].setColor(dr1);
    }
    else if (arrRect[counter].reqHits==2) { 
      arrRect[counter].reqHits--;   
      arrRect[counter].setColor(12);
      arrRect[counter].fillRectangle(100, 20);
      if (arrRect[counter].tempSave.equals(Color.blue)) arrRect[counter].setColor(db2);
      else if (arrRect[counter].tempSave.equals(Color.green)) arrRect[counter].setColor(dg2);
      else if (arrRect[counter].tempSave.equals(Color.red)) arrRect[counter].setColor(dr2);
    }
    else if (arrRect[counter].reqHits==1) { 
      arrRect[counter].setColor(Color.white);
    }
  }
  
  /** Causes the current Ball to move by erasing the current Ball, then drawing another vx and vy away.
   *  Ball also bounces off of the walls, bricks or other balls when it encounters them. The bricks that are hit are erased*/
  private void GameOnce() {
    
    Color save= getColor();
    setColor(Color.white);
    fillCircle(2*radius);
    setColor(save);
    if ((getY() <= radius && vy < 0) || (getY() >= 500-radius && vy > 0)) vy= -vy;
    if ((getX() <= radius && vx <= 0) || (getX() >= 500-radius && vx >= 0)) vx= -vx;
    
    counter=0;
    //inv: checks if the ball has hit any bricks in arrRect[0...counter-1]
    while (counter != 25) {
      if (!arrRect[counter].getColor().equals(Color.white)) {
        // Checks collisions on the tops and bottoms of the Balls with the corresponding tops and bottoms of the bricks.
        if ((getX() <= arrRect[counter].getX() + 50 && getX() >= arrRect[counter].getX() - 50 && 
             getY() <= arrRect[counter].getY() + 10 + radius && getY() >= arrRect[counter].getY() + radius) || 
            (getX() <= arrRect[counter].getX() + 50 && getX() >= arrRect[counter].getX() - 50 && 
             getY() <= arrRect[counter].getY() + 10 - radius && getY() >= arrRect[counter].getY() - radius)) {
          hitCheck();
          arrRect[counter].fillRectangle(100, 20);
          vy= -vy;
        }
        // Checks collisions on the sides of the Balls with the corresponding sides of the bricks.
        else if ((getY() <= arrRect[counter].getY() + 10 && getY() >= arrRect[counter].getY() - 10 && 
                  getX() <= arrRect[counter].getX() + 50 + radius && getX() >= arrRect[counter].getX() + radius) || 
                 (getY() <= arrRect[counter].getY() + 10 && getY() >= arrRect[counter].getY() - 10 && 
                  getX() <= arrRect[counter].getX() - radius && getX() >= arrRect[counter].getX() - 50 - radius)) {
          hitCheck();
          arrRect[counter].fillRectangle(100, 20);
          vx= -vx;
        }
        // Checks collisions on the diagonals of the Balls with the corresponding quadrants of the bricks.
        else if ((getX() - radius*(0.707106781) > arrRect[counter].getX() && getX() - radius*(0.707106781) < arrRect[counter].getX() + 50 &&
                  getY() - radius*(0.707106781) > arrRect[counter].getY() && getY() - radius*(0.707106781) < arrRect[counter].getY() + 10) ||
                 (getX() + radius*(0.707106781) < arrRect[counter].getX() && getX() + radius*(0.707106781) > arrRect[counter].getX() - 50 &&
                  getY() - radius*(0.707106781) > arrRect[counter].getY() && getY() - radius*(0.707106781) < arrRect[counter].getY() + 10) ||
                 (getX() + radius*(0.707106781) < arrRect[counter].getX() && getX() + radius*(0.707106781) > arrRect[counter].getX() - 50 &&
                  getY() + radius*(0.707106781) < arrRect[counter].getY() && getY() + radius*(0.707106781) > arrRect[counter].getY() - 10) ||
                 (getX() - radius*(0.707106781) > arrRect[counter].getX() && getX() - radius*(0.707106781) < arrRect[counter].getX() + 50 &&
                  getY() + radius*(0.707106781) < arrRect[counter].getY() && getY() + radius*(0.707106781) > arrRect[counter].getY() - 10)) {
          hitCheck();
          arrRect[counter].fillRectangle(100, 20);
          vx= -vx;
          vy= -vy;
        }
      }
      counter++;
    }
    
    //inv: checks to see if b[0...counter-1] has collided with this ball and, if so, changes their directions.
    for (counter= 0; counter < 5; counter++) {
      if (!(b[counter] == null) && !(this.equals(b[counter]))) {
        if ((getX() >= b[counter].getX() - b[counter].radius && getX() <= b[counter].getX() + b[counter].radius && 
             getY() - radius >= b[counter].getY() && getY() - radius <= b[counter].getY() + b[counter].radius && vy<0) ||
            (getX() >= b[counter].getX() - b[counter].radius && getX() <= b[counter].getX() + b[counter].radius && 
             getY() + radius <= b[counter].getY() && getY() + radius >= b[counter].getY() - b[counter].radius && vy>0)) {
          vy= -vy;
        }
        else if ((getY() >= b[counter].getY() - b[counter].radius && getY() <= b[counter].getY() + b[counter].radius && 
                  getX() - radius >= b[counter].getX() && getX() - radius <= b[counter].getX() + b[counter].radius && vx<0) ||
                 (getY() >= b[counter].getY() - b[counter].radius && getY() <= b[counter].getY() + b[counter].radius && 
                  getX() + radius <= b[counter].getX() && getX() + radius >= b[counter].getX() - b[counter].radius && vx>0))  {
          vx= -vx;
        }
        else if ((getX() - radius*(0.707106781) > b[counter].getX() && getX() - radius*(0.707106781) < b[counter].getX() + b[counter].radius*(0.707106781) &&
                  getY() - radius*(0.707106781) > b[counter].getY() && getY() - radius*(0.707106781) < b[counter].getY() + b[counter].radius*(0.707106781) && vx<0 && vy<0)||
                 (getX() + radius*(0.707106781) < b[counter].getX() && getX() + radius*(0.707106781) > b[counter].getX() - b[counter].radius*(0.707106781) &&
                  getY() - radius*(0.707106781) > b[counter].getY() && getY() - radius*(0.707106781) < b[counter].getY() + b[counter].radius*(0.707106781) && vx>0 && vy<0)||
                 (getX() + radius*(0.707106781) < b[counter].getX() && getX() + radius*(0.707106781) > b[counter].getX() - b[counter].radius*(0.707106781) &&
                  getY() + radius*(0.707106781) < b[counter].getY() && getY() + radius*(0.707106781) > b[counter].getY() - b[counter].radius*(0.707106781) && vx>0 && vy>0)||
                 (getX() - radius*(0.707106781) > b[counter].getX() && getX() - radius*(0.707106781) < b[counter].getX() + b[counter].radius*(0.707106781) &&
                  getY() + radius*(0.707106781) < b[counter].getY() && getY() + radius*(0.707106781) > b[counter].getY() - b[counter].radius*(0.707106781) && vx<0 && vy>0)) {
          vx= -vx;
          vy= -vy;
        }
      }
    }  
    moveTo(getX()+vx, getY()+vy, 0);
    setColor(save);
    fillCircle(2*radius);
  }
  
  /** Plays a demo game of bricks at speed 'speed' (best range: 1-5), with each brick having to be hit 'hits' (range: 1-3) times to 
   * clear, for 'time' secs with 'n' balls (range: 1-5). 25 bricks of 3 colors (red, green, and blue) appear in random arrangement (the 
   * same color does not appear on any adjacent brick). n Balls of different random colors are created. One paddle is created for each
   * Ball, the same color as the Ball. The paddle moves with the ball and stops the ball from hitting the bottom surface. As each brick 
   * is hit, it dulls or disappears depending on the number of hits required to clear each brick. The balls deflect when they hit the 
   * walls, paddle, bricks or other balls. The game ends when all 25 bricks are cleared or the time runs out. The number of bricks cleared
   * and the time taken is then printed.*/
  public static void Bricks(int speed, int hits, int time, int n){
    
    counter= 0; hitCount= 0;
    
    // Check all parameters to make sure they are within range
    if (n > 5 || n <= 0){
      System.out.println("Number of balls must be within 0-5.");
      return;
    }
    if (speed <= 0){
      System.out.println("Speed must be greater than 0.");
      return;
    }
    if (hits <= 0 || hits > 3){
      System.out.println("Number of required collisions has to be within 1-3.");
      return;
    }
    
    
    Ball[] paddle= new Ball[n]; 
    //inv: create a random color, location, speed for b[0..k-1] of size 7 and a corresponding paddle at the bottom of 
    //the screen of the same color
    for (int k= 0; k < n; k++){
      int color= 1 + rand1.nextInt(11), a= 0;
      //inv: b[a-1] is not the same as the color of b[0..a-2]
      while(a < k){
        Color c= b[a].getColor();
        general.setColor(color);
        if (c.equals(general.getColor())){
          color= 1 + rand1.nextInt(11);
        }
        else a++;
      }
      b[k]= new Ball(rand1.nextInt(500), rand1.nextInt(250) + 200, rand1.nextInt(6) + 5, rand1.nextInt(6) + 5, 7, color);
      paddle[k]= new Ball(500, 500, 0, 0, 0, color);
    }
    
    createRects(hits);
    
    int k=0;
    //inv: run the game for (k -(50/speed)) seconds
    while (k < time*1000) {
      general.pause(50/speed);
      // inv: for every (50/speed) secs, move b[0...game-1] and the corresponding paddle once
      for (int game= 0; game < n; game++){
        b[game].GameOnce();
        
        // paddle code
        Color scolor= paddle[game].getColor();
        paddle[game].setColor(Color.white);
        paddle[game].fillRectangle(50, 10);
        paddle[game].setColor(scolor);
        paddle[game].moveTo((b[game].getX() + b[game].vx), (height - 10), 0);
        paddle[game].fillRectangle(50, 10);
        
        if ((b[game].getY() + b[game].radius) >= (height - 12) && b[game].vy > 0){b[game].setVY(-b[game].vy);}
      }
      
      int s= 0;
      //inv: hitCount= no. of cleared rectangles from arrRect[0...s-1]
      while (s<25) {
        if (arrRect[s].getColor().equals(Color.white))  {hitCount ++;}
        s++;
      }
      if (hitCount >= 25){
        System.out.println("25 Bricks were cleared in " + ((double)k/1000.0) + " seconds!");
        return;
      }
      else {hitCount= 0;}
      k= k + (int)(50/speed);
    }
    
    int s= 0;
    //inv: hitCount= no. of cleared rectangles from arrRect[0...s-1] 
    while (s<25) {
      if (arrRect[s].getColor().equals(Color.white))  {hitCount ++;}
      s++;
    }
    System.out.println("" + hitCount + " Bricks were cleared in " + time + " seconds!");
  }
}