/*
 * File: Breakout.java
 * -------------------
 * Name: Luka Gogmachadze
 * Section Leader: Mamuka Sakhelahsvili
 * 
 * This file will eventually implement the game of Breakout.
 */

import acm.graphics.*;
import acm.program.*;
import acm.util.*;

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

public class Breakout extends GraphicsProgram {
	public static void main(String[] args) {
		Breakout breakout = new Breakout();
		breakout.start(args);
	}

	/** Width and height of application window in pixels */
	public static final int APPLICATION_WIDTH = 400;
	public static final int APPLICATION_HEIGHT = 500;

	/** Dimensions of game board (usually the same) */
	private static final int WIDTH = APPLICATION_WIDTH;
	private static final int HEIGHT = APPLICATION_HEIGHT;

	/** Dimensions of the paddle */
	private static final int PADDLE_WIDTH = 60;
	private static final int PADDLE_HEIGHT = 10;

	/** Offset of the paddle up from the bottom */
	private static final int PADDLE_Y_OFFSET = 30;

	/** Number of bricks per row */
	private static final int NBRICKS_PER_ROW = 10;

	/** Number of rows of bricks */
	private static final int NBRICK_ROWS = 10;

	/** Separation between bricks */
	private static final int BRICK_SEP = 4;

	/** Width of a brick */
	private static final int BRICK_WIDTH = (WIDTH - (NBRICKS_PER_ROW - 1) * BRICK_SEP) / NBRICKS_PER_ROW;

	/** Height of a brick */
	private static final int BRICK_HEIGHT = 8;

	/** Radius of the ball in pixels */
	private static final int BALL_RADIUS = 30;

	/** Offset of the top brick row from the top */
	private static final int BRICK_Y_OFFSET = 70;

	/** Number of turns */
	private static final int NTURNS = 3;

	/** Declaring brick as instant variable */
	private GRect brick;

	/** Declaring paddle as instant variable */
	private GRect paddle;

	/** Declaring Ball instance */
	private GOval ball;

	/** Ball coordinate tracking */
	private double ballX, ballY;

	/** Ball directional velocities */
	private double vx, vy;

	/** RandomGenerator instance for ball speed velocity variation */
	private RandomGenerator rspeed = RandomGenerator.getInstance();

	/** Collision counter for velocity increase */
	private int collisionCounts = 0;

	public void run() {
		initializeTheGame();
		gameProcess();	

	}

	private void initializeTheGame() {
		drawBricks();
		drawPaddle();
		drawBall();
	}

	private void gameProcess() {
		addMouseListeners();
		ballMovement();
	}

	/*
	 * Draws a grid of bricks by calculating and placing each brick in its correct position.
	 * The grid starts at a fixed vertical position (`startYPos`) and is built row by row.
	 * The outer loop controls the rows; for each row, the starting Y-coordinate 
	 * (`yPos`) is calculated by adding the row index multiplied by the brick height 
	 * and vertical separation (`BRICK_HEIGHT + BRICK_SEP`) to `startYPos`.
	 * The inner loop controls the columns; for each column, the X-coordinate (`xPos`) 
	 * is calculated by multiplying the column index by the brick width and horizontal 
	 * separation (`BRICK_WIDTH + BRICK_SEP`).
	 * For every brick, a `GRect` is created using the calculated `xPos`, `yPos`, width, 
	 * and height. The brick is then filled, colored based on its row via `fillBrickColor`, 
	 * and added to the canvas at its calculated position.
	 */ 

	private void drawBricks() {
		for (int rows = 0; rows < NBRICK_ROWS; rows++) {
			for (int cols = 0; cols < NBRICKS_PER_ROW; cols++) {
				int xPos = cols * (BRICK_WIDTH + BRICK_SEP);
				int yPos = BRICK_Y_OFFSET + rows * (BRICK_HEIGHT + BRICK_SEP);

				brick = new GRect(xPos, yPos, BRICK_WIDTH, BRICK_HEIGHT);
				brick.setFilled(true);
				brick.setFillColor(fillBrickColor(rows));
				add(brick);
			}
		}
	}

	/*
	 * Simple color filling pattern method. For every two rows we have fill
	 * the same colors alternating between assignment required colors.
	 */
	private Color fillBrickColor(int row) {
		if (row == 0 || row == 1) {
			return Color.RED;
		} else if (row == 2 || row == 3) {
			return Color.ORANGE;
		} else if (row == 4 || row == 5) {
			return Color.YELLOW;
		} else if (row == 6 || row == 7) {
			return Color.GREEN;
		} else if (row == 8 || row == 9) {
			return Color.CYAN;
		}
		return Color.GRAY;
	}
	
	// Method to draw the paddle on the screen
	private void drawPaddle() {
		// Create a new GRect (paddle) with specified width and height
		paddle = new GRect(PADDLE_WIDTH, PADDLE_HEIGHT);

		// Set the paddle to be filled and assign it a black color
		paddle.setFilled(true);
		paddle.setFillColor(Color.BLACK);

		double paddleX = getWidth() / 2 - PADDLE_WIDTH / 2;
		double paddleY = getHeight() - PADDLE_Y_OFFSET;

		// Add the paddle to the screen at the calculated position (x, y)
		add(paddle, paddleX, paddleY);
	}

	private void drawBall() {
		ball = new GOval(BALL_RADIUS, BALL_RADIUS);
		ballX = getWidth() / 2 - BALL_RADIUS / 2;
		ballY = getHeight() / 2 - BALL_RADIUS / 2;
		
		ball.setFilled(true);
		ball.setFillColor(Color.BLACK);

		add(ball);
		ball.setLocation(ballX, ballY);
	}
	
	private void ballMovement() {
		int turns = NTURNS;
		initializeVelocity();

		while (turns > 0) {
			// Check if ball is lost
			if (ballY + BALL_RADIUS >= getHeight()) {
				turns--;
				resetBall();
				initializeVelocity();
				continue;
			}

			handleBoundaryCollision();
			handleCollisions();
			updateBallPos();
		}

	}
	
	private void handleBoundaryCollision() {
		if (ballY <= 0) {
			vy = -vy;
		}

		if (ballX <= 0 || ballX + BALL_RADIUS >= getWidth()) {
			vx = -vx;
		}
	}
	
	// Animating the ball falldown movement.
	private void updateBallPos() {
		ballX += vx;
		ballY += vy;
		ball.setLocation(ballX, ballY);
		pause(20);
			
	}
	
	/*
	 * Once the ball hits bottom of the screen it is reset
	 * to the default middle screen position again
	 */
	private void resetBall() {
		ballX = getWidth() / 2 - BALL_RADIUS / 2;
		ballY = getHeight() / 2 - BALL_RADIUS / 2;
		collisionCounts = 0;
	}
	
	/*
	 * Exactly what the method name says. Initializing ball's 
	 * velocity in both x and y axis. This is the default
	 * state of the ball's movement until a collision happens.
	 */

	private void initializeVelocity() {
		vx = rspeed.nextDouble(1.0, 3.0);
		if (rspeed.nextBoolean(0.2)) {
			vx = -vx;
		}
		vy = 3.0;
	}
	
	/*
	 * This method is used to make the paddle and the mouse
	 * positions on x axis the same
	 */

	public void mouseMoved(MouseEvent e) {
		// Get the x-coordinate of the mouse, adjusted for paddle width (center alignment)
		double xPos = e.getX() - PADDLE_WIDTH / 2;

		// If the paddle goes beyond the left edge, set it to the leftmost position
		if (xPos < 0) {
			xPos = 0;
		}
		// If the paddle goes beyond the right edge, set it to the rightmost position
		else if (xPos > getWidth() - PADDLE_WIDTH) {
			xPos = getWidth() - PADDLE_WIDTH;
		}

		// Update the paddle's position to the calculated x position, at a fixed y-coordinate
		paddle.setLocation(xPos, getHeight() - PADDLE_Y_OFFSET);
	}
	
	private GObject getCollidingObject() {
		double x = ball.getX();
		double y = ball.getY();

		GObject collider = null;

		// Checking collision at top-left corner of the ball
		collider = getElementAt(x, y);
		if (collider != null && collider != ball) {
			return collider;
		}

		// Checking collision at top-right corner of the ball
		collider = getElementAt(x + BALL_RADIUS, y);
		if (collider != null && collider != ball) {
			return collider;
		}

		// Checking collision at bottom-left corner of the ball
		collider = getElementAt(x, y + BALL_RADIUS);
		if (collider != null && collider != ball) {
			return collider;
		}

		// Checking collision at bottom-right corner of the ball
		collider = getElementAt(x + BALL_RADIUS * 2, y + BALL_RADIUS);
		if (collider != null && collider != ball) {
			return collider;
		}

		// If no collision are detected
		return null;
	}

	private void handleCollisions() {
		GObject collider = getCollidingObject();

		if (collider != null) {
			if (collider == paddle) {
				vy = -vy;
			} else if (collider instanceof GRect) {
				vy = -vy;
				remove(collider);

				collisionCounts += 1;
				if (collisionCounts % 7 == 0) {
					vy *= 2;
				}
			}
		}
	}
}