package game;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Group;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.image.ImageView;
import javafx.scene.image.ImageViewBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.util.Duration;

public class BallGameController implements Initializable {
	// UI ELEMENTS
	@FXML
	private Group area;

	@FXML
	private Circle ball;

	@FXML
	private Rectangle borderTop;

	@FXML
	private Rectangle borderBottom;

	@FXML
	private Rectangle borderLeft;

	@FXML
	private Rectangle borderRight;

	@FXML
	private Rectangle paddle;

	@FXML
	private Text gameOverText;

	@FXML
	private Text winnerText;

	@FXML
	private Button startButton;

	@FXML
	private Button quitButton;

	@FXML
	private ProgressBar progressBar;

	@FXML
	private Label remainingBlocksLabel;

	// GAME MODEL
	private final GameModel model = new GameModel();

	// GAME HEARTBEAT
	private final EventHandler<ActionEvent> pulseEvent = new EventHandler<ActionEvent>() {
		@Override
		public void handle(final ActionEvent evt) {
			checkWin();
			checkCollisions();
			updateBallPosition();
		}
	};

	// THE TIMELINE, RUNS EVERY 10MS
	private final Timeline heartbeat = TimelineBuilder.create()
			.keyFrames(new KeyFrame(new Duration(10.0), pulseEvent))
			.cycleCount(Timeline.INDEFINITE).build();

	/*
	 * (non-Javadoc)
	 * 
	 * @see javafx.fxml.Initializable#initialize(java.net.URL,
	 * java.util.ResourceBundle)
	 */
	@Override
	public void initialize(final URL url, final ResourceBundle bundle) {
		bindPaddleMouseEvents();
		bindStartButtonEvents();
		bindQuitButtonEvents();
		bindElementsToModel();
		initializeBoxes();
		initializeGame();
		area.requestFocus();
	}

	/**
	 * binds ui elements to model state
	 */
	private void bindElementsToModel() {
		startButton.disableProperty().bind(model.getGameStopped().not());
		ball.centerXProperty().bind(model.getBallX());
		ball.centerYProperty().bind(model.getBallY());
		paddle.xProperty().bind(model.getPaddleX());
		gameOverText.visibleProperty().bind(model.getGameLost());
		winnerText.visibleProperty().bind(model.getGameWon());
		progressBar.progressProperty().bind(
				model.getBoxesLeft().subtract(model.getInitialAmountBlocks())
						.multiply(-1).divide(model.getInitialAmountBlocks()));
		remainingBlocksLabel.textProperty().bind(
				Bindings.format("%.0f boxes left", model.getBoxesLeft()));
	}

	/**
	 * initializes the game, is called for every new game
	 */
	private void initializeGame() {
		model.reset();
	}

	/**
	 * initializes the boxes.
	 */
	private void initializeBoxes() {
		int startX = 15;
		int startY = 30;
		for (int v = 1; v <= model.getInitialBlocksVertical(); v++) {
			for (int h = 1; h <= model.getInitialBlocksHorizontal(); h++) {
				int x = startX + (h * 40);
				int y = startY + (v * 40);
				ImageView imageView = ImageViewBuilder.create()
						.image(BallGame.ICON).layoutX(x).layoutY(y).build();
				model.getBoxes().add(imageView);
			}
		}
		area.getChildren().addAll(model.getBoxes());
	}

	/**
	 * creates event handler for the quit button. pressing it immediatly quits
	 * the application.
	 */
	private void bindQuitButtonEvents() {
		quitButton.setOnAction(new EventHandler<ActionEvent>() {
			@Override
			public void handle(final ActionEvent evt) {
				Platform.exit();
			}
		});
	}

	/**
	 * binds events to the start button. by pressing the start button, the game
	 * is initialized and the timeline execution is started.
	 */
	private void bindStartButtonEvents() {
		startButton.setOnAction(new EventHandler<ActionEvent>() {
			@Override
			public void handle(final ActionEvent evt) {
				initializeGame();
				model.getGameStopped().set(false);
				heartbeat.playFromStart();
			}
		});
	}

	/**
	 * binds events to drag the paddle using the mouse.
	 */
	private void bindPaddleMouseEvents() {
		paddle.setOnMousePressed(new EventHandler<MouseEvent>() {
			@Override
			public void handle(final MouseEvent evt) {
				model.setPaddleTranslateX(model.getPaddleTranslateX() + 150);
				model.setPaddleDragX(evt.getSceneX());
			}
		});
		paddle.setOnMouseDragged(new EventHandler<MouseEvent>() {
			@Override
			public void handle(final MouseEvent evt) {
				if (!model.getGameStopped().get()) {
					double x = model.getPaddleTranslateX() + evt.getSceneX()
							- model.getPaddleDragX();
					model.getPaddleX().setValue(x);
				}
			}
		});
	}

	/**
	 * checks if the game is won.
	 */
	private void checkWin() {
		if (0 == model.getBoxesLeft().get()) {
			model.getGameWon().set(true);
			model.getGameStopped().set(true);
			heartbeat.stop();
		}
	}

	/**
	 * checks if the ball has collisions with the walls or the paddle.
	 */
	private void checkCollisions() {
		checkBoxCollisions();
		if (ball.intersects(paddle.getBoundsInLocal())) {
			model.incrementSpeed();
			model.setMovingDown(false);
		}
		if (ball.intersects(borderTop.getBoundsInLocal())) {
			model.incrementSpeed();
			model.setMovingDown(true);
		}
		if (ball.intersects(borderBottom.getBoundsInLocal())) {
			model.getGameStopped().set(true);
			model.getGameLost().set(true);
			heartbeat.stop();
		}
		if (ball.intersects(borderLeft.getBoundsInLocal())) {
			model.incrementSpeed();
			model.setMovingRight(true);
		}
		if (ball.intersects(borderRight.getBoundsInLocal())) {
			model.incrementSpeed();
			model.setMovingRight(false);
		}
		if (paddle.intersects(borderRight.getBoundsInLocal())) {
			model.getPaddleX().set(350);
		}
		if (paddle.intersects(borderLeft.getBoundsInLocal())) {
			model.getPaddleX().set(0);
		}
	}

	/**
	 * checks if the ball collides with one or more of the boxes. if there's a
	 * collision, the box is removed.
	 */
	private void checkBoxCollisions() {
		for (ImageView r : model.getBoxes()) {
			if (r.isVisible() && ball.intersects(r.getBoundsInParent())) {
				model.getBoxesLeft().set(model.getBoxesLeft().get() - 1);
				r.setVisible(false);
			}
		}
	}

	/**
	 * updates the ball position by calculating the ball's speed, position and
	 * direction.
	 */
	private void updateBallPosition() {
		double x = model.isMovingRight() ? model.getMovingSpeed() : -model
				.getMovingSpeed();
		double y = model.isMovingDown() ? model.getMovingSpeed() : -model
				.getMovingSpeed();
		model.getBallX().set(model.getBallX().get() + x);
		model.getBallY().set(model.getBallY().get() + y);
	}
}