import java.util.concurrent.ThreadLocalRandom;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;

/**
 * Simulates the random motion of idealized particles colliding within a rectangular
 * container. The user can modify the simulation using mouse buttons as follows:
 * 
 * Left button: decrease radius of particles.
 * Right button: increase radius of particles.
 * Middle button: toggles run/pause state of animation.
 *
 * @author Drue Coles
 */
public class CollisionSimulator extends Application {

    @Override
    public void start(Stage primaryStage) {
        Pane root = new Pane();
        final int width = 500;
        final int height = 300;
        root.setPrefSize(width, height);
        Scene scene = new Scene(root);

        String style = "-fx-background-color: ALICEBLUE; "
                + "-fx-border-style: solid; -fx-border-width: 4; "
                + "-fx-border-insets: 1; -fx-border-color: DARKSLATEGRAY";
        root.setStyle(style);

        final int numParticles = 20;
        Particle[] particles = new Particle[numParticles];

        // Instantiate the particles and add them to the root node
        ThreadLocalRandom rand = ThreadLocalRandom.current();
        int delay = 50; // milliseconds
        for (int i = 0; i < numParticles; i++) {

            // calculate initial position of particle at random, at least two
            // diameters away from the boundaries of the viewing area.
            final int radius = 8;
            int x = rand.nextInt(4 * radius, width - 4 * radius);
            int y = rand.nextInt(4 * radius, height - 4 * radius);

            // create particle and add it to root container
            Color c = randomDarkColor();

            particles[i] = new Particle(x, y, radius, c, width, height, delay);
            root.getChildren().add(particles[i]);
        }

        // Check for collisions among each pair of balls. When two balls collide,
        // each bounces away in a random direction.
        KeyFrame kf = new KeyFrame(Duration.millis(delay), e -> {
            for (int i = 0; i < numParticles; i++) {
                for (int j = i + 1; j < numParticles; j++) {
                    if (particles[i].intersects(particles[j].getBoundsInLocal())) {
                        particles[i].meander();
                        particles[j].meander();
                    }
                }
            }
        });
        Timeline timeline = new Timeline(kf);
        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.play();

        // The user can manipulate particle size and pause/play the animation
        // using mouse clicks.
        int minRadius = 5;
        int maxRadius = 50;
        root.setOnMouseClicked(e -> {
            double currentRadius = particles[0].getRadius();
            int radiusChange = 1;
            for (Particle particle : particles) {
                switch (e.getButton()) {
                    case PRIMARY:
                        if (currentRadius - radiusChange >= minRadius) {
                            particle.setRadius(currentRadius - radiusChange);
                        }
                        break;
                    case SECONDARY:
                        if (currentRadius + radiusChange <= maxRadius) {
                            particle.setRadius(currentRadius + radiusChange);
                        }
                        break;
                    default:
                        particle.toggleRunState();
                }
            }
        });

        primaryStage.setTitle("Collision Simulator");
        primaryStage.setScene(scene);
        primaryStage.show();
        primaryStage.setAlwaysOnTop(true);
    }

    /**
     * Returns a color with randomly chosen RGB intensities between zero and
     * half of the maximum possible intensity. 
     */
    private Color randomDarkColor() {
        ThreadLocalRandom rand = ThreadLocalRandom.current();
        int max = 128; // max intensity for dark color
        int r = rand.nextInt(max);
        int g = rand.nextInt(max);
        int b = rand.nextInt(max);
        return Color.rgb(r, g, b);
    }

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