import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * A grid of colored cells updated over time to reflect the states of a predator-prey
 * simulation. The user can advance the simulation by one unit of time by clicking the
 * primary mouse button. The secondary mouse button toggles auto-update mode for animating
 * the simulated passage of time; pressing the space bar has the same effect.
 * 
 * @author Drue Coles
 */
public class WorldViewer extends Application {

    // constants for drawing
    private static final Color PREDATOR_COLOR = Color.DARKRED;
    private static final Color PREY_COLOR = Color.DARKGREEN;
    private static final Color EMPTY_COLOR = Color.ALICEBLUE;
    private static final Color CELL_BORDER_COLOR = Color.LIGHTGRAY;
    private static final int CELL_SIZE = 8;
    private static final int PADDING = 4;

    private static final int SIZE = 100; // size of simulated world
    private static final int DELAY = 75; // milliseconds between auto-updates

    private final World world = new World(SIZE);
    private final Rectangle[][] cells = new Rectangle[SIZE][SIZE];
    private Timeline timeline; // for auto-updates

    @Override
    public void start(Stage primaryStage) {

        // Size of the scene is determined by the size of the simulated world and a fixed
        // size for the colored squares depicting creatures of the simulated world. 
        final int n = 2 * PADDING + SIZE * CELL_SIZE;
        Pane root = new Pane();
        Scene scene = new Scene(root, n, n);

        // Initialize 2D array of rectangles to be filled at each step of the simulation
        // with a color signifying the creature at the corresponding grid location. 
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                int x = PADDING + i * CELL_SIZE;
                int y = PADDING + j * CELL_SIZE;
                cells[i][j] = new Rectangle(x, y, CELL_SIZE, CELL_SIZE);
                cells[i][j].setStroke(CELL_BORDER_COLOR);
                root.getChildren().add(cells[i][j]);
            }
        }
        showWorld(); // Draw the simulated world in its starting state.

        // Create animation for updates to the simulation.
        KeyFrame keyframe = new KeyFrame(Duration.millis(DELAY), e -> {
            world.tick();
            showWorld();
        });
        timeline = new Timeline(keyframe);
        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.pause();

        // Mouse click handler for updating the simulation.
        root.setOnMouseClicked(e -> {
            if (e.getButton() == MouseButton.PRIMARY) {
                timeline.pause();
                world.tick();
                showWorld();
            } else {
                toggleRunState();
            }
        });

        // Space bar to update the simulation.
        scene.setOnKeyPressed(e -> { 
            if (e.getCode() == KeyCode.SPACE) {
               toggleRunState();
            }
        });

        primaryStage.setTitle("Predator-Prey Simulation");
        primaryStage.setScene(scene);
        primaryStage.show();
        showInfo();
    }

    /**
     * Toggles the animation timeline between running and paused states. 
     */
    private void toggleRunState() { 
        if (timeline.getStatus() == Timeline.Status.RUNNING) {
            timeline.pause();
        } else if (!world.isDead()) {
            timeline.play();
        }
    }

    /**
     * Fills each rectangle in the 2D cells array with a color depending on the creature
     * at the corresponding position in the simulation.
     */
    private void showWorld() {
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                Creature creature = world.get(i, j);
                if (creature == null) {
                    cells[i][j].setFill(EMPTY_COLOR);
                } else if (creature instanceof Predator) {
                    cells[i][j].setFill(PREDATOR_COLOR);
                } else if (creature instanceof Prey) {
                    cells[i][j].setFill(PREY_COLOR);
                }
            }
        }
    }

    /**
     * Produces an alert box with basic info about the simulation.
     */
    private static void showInfo() {
        String info
                = "Primary mouse button: advance one unit of time.\n"
                + "Right mouse button: toggle auto-update mode.";

        Alert alert = new Alert(AlertType.INFORMATION);
        alert.setTitle("Predator-Prey Simulation");
        alert.setHeaderText("RED = PREDATOR\nGREEN = PREY");
        alert.setContentText(info);
        alert.showAndWait();
    }

    public static void main(String[] args) {
        launch(args);
    }
}