package game;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.GroupBuilder;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBuilder;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.HyperlinkBuilder;
import javafx.scene.control.Label;
import javafx.scene.control.LabelBuilder;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressBarBuilder;
import javafx.scene.control.ToolBar;
import javafx.scene.control.ToolBarBuilder;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.DropShadowBuilder;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.ImageViewBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.CircleBuilder;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.RectangleBuilder;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextBuilder;
import javafx.stage.Stage;
import javafx.util.Duration;

public class SingleClassNoXmlBallGame extends Application {
	private static final String STYLESHEET_FILE = "/stylesheet/style.css";
	private static final int INITIAL_BLOCKS_HORIZONTAL = 10;
	private static final int INITIAL_BLOCKS_VERTICAL = 5;
	private static final int INITIAL_AMOUNT_BLOCKS = INITIAL_BLOCKS_HORIZONTAL
			* INITIAL_BLOCKS_VERTICAL;

	private final DoubleProperty ballX = new SimpleDoubleProperty();
	private final DoubleProperty ballY = new SimpleDoubleProperty();
	private final DoubleProperty paddleX = new SimpleDoubleProperty();
	private final BooleanProperty gameStopped = new SimpleBooleanProperty();
	private final BooleanProperty gameLost = new SimpleBooleanProperty(false);
	private final BooleanProperty gameWon = new SimpleBooleanProperty(false);
	private final DoubleProperty boxesLeft = new SimpleDoubleProperty(
			INITIAL_AMOUNT_BLOCKS);

	private boolean movingDown = true;
	private boolean movingRight = true;
	private double movingSpeed = 1.0;
	private double paddleDragX = 0.0;
	private double paddleTranslateX = 0.0;

	private static final Image ICON = new Image(
			SingleClassNoXmlBallGame.class
					.getResourceAsStream("/image/head.png"));

	private final DropShadow dropshadowEffect = DropShadowBuilder.create()
			.offsetY(4.0).offsetX(0.5).color(Color.BLACK).build();

	private final ObservableList<ImageView> boxes = FXCollections
			.observableArrayList();

	private final Circle ball = CircleBuilder.create().radius(10.0)
			.fill(Color.BLACK).effect(dropshadowEffect).build();

	private final Rectangle borderTop = RectangleBuilder.create().x(0).y(30)
			.width(500).height(2).effect(dropshadowEffect).build();

	private final Rectangle borderBottom = RectangleBuilder.create().x(0)
			.y(500).width(500).height(2).build();

	private final Rectangle borderLeft = RectangleBuilder.create().x(0).y(0)
			.width(2).height(500).build();

	private final Rectangle borderRight = RectangleBuilder.create().x(498).y(0)
			.width(2).height(500).build();

	private final Rectangle paddle = RectangleBuilder.create().x(200).y(460)
			.width(100).layoutX(20).height(15).effect(dropshadowEffect)
			.fill(Color.BLACK).cursor(Cursor.HAND)
			.onMousePressed(new EventHandler<MouseEvent>() {
				@Override
				public void handle(final MouseEvent evt) {
					paddleTranslateX = paddle.getTranslateX() + 150;
					paddleDragX = evt.getSceneX();
				}
			}).onMouseDragged(new EventHandler<MouseEvent>() {
				@Override
				public void handle(final MouseEvent evt) {
					if (!gameStopped.get()) {
						double x = paddleTranslateX + evt.getSceneX()
								- paddleDragX;
						paddleX.setValue(x);
					}
				}
			}).build();

	private final Text gameOverText = TextBuilder.create().text("Game Over")
			.font(Font.font("Arial", 40.0)).fill(Color.RED).layoutX(150)
			.layoutY(330).effect(dropshadowEffect).build();

	private final Text winnerText = TextBuilder.create().text("You've won!")
			.font(Font.font("Arial", 40.0)).fill(Color.GREEN).layoutX(150)
			.layoutY(330).effect(dropshadowEffect).build();

	private final Button startButton = ButtonBuilder.create().text("Start")
			.onAction(new EventHandler<ActionEvent>() {
				@Override
				public void handle(final ActionEvent evt) {
					initGame();
					gameStopped.set(false);
					heartbeat.playFromStart();
				}
			}).build();

	private final Button quitButton = ButtonBuilder.create().text("Quit")
			.onAction(new EventHandler<ActionEvent>() {
				@Override
				public void handle(final ActionEvent evt) {
					Platform.exit();
				}
			}).build();

	private final Hyperlink link = HyperlinkBuilder.create()
			.text("www.hascode.com").layoutX(360).layoutY(505).build();

	private final ProgressBar progressBar = ProgressBarBuilder.create()
			.progress(100).build();

	private final Label remainingBlocksLabel = LabelBuilder.create().build();

	private final ToolBar toolbar = ToolBarBuilder.create().minWidth(500)
			.items(startButton, quitButton, progressBar, remainingBlocksLabel)
			.build();

	private final ToolBar footerBar = ToolBarBuilder.create().minWidth(500)
			.items(link).layoutY(500).build();

	private final Group area = GroupBuilder
			.create()
			.focusTraversable(true)
			.children(ball, borderTop, borderBottom, borderLeft, borderRight,
					paddle, gameOverText, winnerText, toolbar, footerBar)
			.build();

	private final EventHandler<ActionEvent> pulseEvent = new EventHandler<ActionEvent>() {
		@Override
		public void handle(final ActionEvent evt) {
			checkWin();
			checkCollisions();
			double x = movingRight ? movingSpeed : -movingSpeed;
			double y = movingDown ? movingSpeed : -movingSpeed;
			ballX.set(ballX.get() + x);
			ballY.set(ballY.get() + y);
		}
	};

	private final Timeline heartbeat = TimelineBuilder.create()
			.keyFrames(new KeyFrame(new Duration(10.0), pulseEvent))
			.cycleCount(Timeline.INDEFINITE).build();

	@Override
	public void start(final Stage stage) throws Exception {
		initGui(stage);
		initGame();
	}

	protected void checkWin() {
		if (0 == boxesLeft.get()) {
			gameWon.set(true);
			gameStopped.set(true);
			heartbeat.stop();
		}
	}

	protected void checkCollisions() {
		checkBoxCollisions();
		if (ball.intersects(paddle.getBoundsInLocal())) {
			incrementSpeed();
			movingDown = false;
		}
		if (ball.intersects(borderTop.getBoundsInLocal())) {
			incrementSpeed();
			movingDown = true;
		}
		if (ball.intersects(borderBottom.getBoundsInLocal())) {
			gameStopped.set(true);
			gameLost.set(true);
			heartbeat.stop();
		}
		if (ball.intersects(borderLeft.getBoundsInLocal())) {
			incrementSpeed();
			movingRight = true;
		}
		if (ball.intersects(borderRight.getBoundsInLocal())) {
			incrementSpeed();
			movingRight = false;
		}
		if (paddle.intersects(borderRight.getBoundsInLocal())) {
			paddleX.set(350);
		}
		if (paddle.intersects(borderLeft.getBoundsInLocal())) {
			paddleX.set(0);
		}
	}

	private void checkBoxCollisions() {
		for (ImageView r : boxes) {
			if (r.isVisible() && ball.intersects(r.getBoundsInParent())) {
				boxesLeft.set(boxesLeft.get() - 1);
				r.setVisible(false);
			}
		}
	}

	private void incrementSpeed() {
		if (movingSpeed <= 6)
			movingSpeed += movingSpeed * 0.5;
	}

	private void initGame() {
		boxesLeft.set(INITIAL_AMOUNT_BLOCKS);
		for (ImageView r : boxes) {
			r.setVisible(true);
		}
		movingSpeed = 1.0;
		movingDown = true;
		movingRight = true;
		ballX.setValue(250);
		ballY.setValue(350);
		paddleX.setValue(175);
		startButton.disableProperty().bind(gameStopped.not());
		ball.centerXProperty().bind(ballX);
		ball.centerYProperty().bind(ballY);
		paddle.xProperty().bind(paddleX);
		gameStopped.set(true);
		gameLost.set(false);
		gameOverText.visibleProperty().bind(gameLost);
		gameWon.set(false);
		winnerText.visibleProperty().bind(gameWon);
		area.requestFocus();
		progressBar.progressProperty().bind(
				boxesLeft.subtract(INITIAL_AMOUNT_BLOCKS).multiply(-1)
						.divide(INITIAL_AMOUNT_BLOCKS));
		remainingBlocksLabel.textProperty().bind(
				Bindings.format("%.0f boxes left", boxesLeft));
	}

	private void initBoxes() {
		int startX = 15;
		int startY = 30;
		for (int v = 1; v <= INITIAL_BLOCKS_VERTICAL; v++) {
			for (int h = 1; h <= INITIAL_BLOCKS_HORIZONTAL; h++) {
				int x = startX + (h * 40);
				int y = startY + (v * 40);
				ImageView imageView = ImageViewBuilder.create().image(ICON)
						.layoutX(x).layoutY(y).build();
				boxes.add(imageView);
			}
		}
		area.getChildren().addAll(boxes);
	}

	private void initGui(final Stage stage) {
		Scene scene = SceneBuilder.create().width(500).height(530)
				.fill(Color.GRAY).root(area).build();
		initBoxes();
		stage.setScene(scene);
		stage.setTitle("hasCode.com - Java FX 2 Ball Game Tutorial");
		stage.getIcons().add(ICON);
		scene.getStylesheets().add(STYLESHEET_FILE);
		stage.show();
	}

	public static void main(final String... args) {
		Application.launch(args);
	}

}