import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.layout.Pane;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;

/**
 * This class demos a Canvas that is resized when the Pane that
 * contains it is resized.  (The Pane changes size when the user
 * changes the window size.)  This is done by binding the
 * width property of the Canvas to the width property of the
 * Pane and the height property of the Canvas to the height
 * property of the Pane.
 * 
 * The program shows an animation of red disks bouncing around 
 * inside the canvas.  When the canvas size increases, the balls 
 * spread out into the new space.  When the canvas size decreases, 
 * the balls become trapped in the smaller space.  This shows
 * that the canvas really does change size.
 */
public class CanvasResizeDemo extends Application {

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

    private Canvas canvas;
    private GraphicsContext g;
    private BouncingBall[] balls;

    public void start(Stage stage) {
        
        canvas = new Canvas(640,480);
        g = canvas.getGraphicsContext2D();
        
        balls = new BouncingBall[50];
        for (int i = 0; i < 50; i++)
            balls[i] = new BouncingBall();
        
        /* When the user clicks or drags the mouse on the canvas,
         * the velocities of all of the balls are changed so that
         * they head towards the mouse position. */
        
        canvas.setOnMousePressed( e -> {
            double x = e.getX();
            double y = e.getY();
            for (BouncingBall b : balls)
                b.headTowards(x,y);
        });
        canvas.setOnMouseDragged( e -> {
            double x = e.getX();
            double y = e.getY();
            for (BouncingBall b : balls)
                b.headTowards(x,y);
        });
        
        Pane root = new Pane(canvas);
        stage.setScene( new Scene(root) );
        stage.setTitle("Resizable Canvas Demo");
        stage.setMinWidth(60);
        stage.setMinHeight(100);
        stage.show();
        
        
        /* Set up binding to keep canvas the same size as the Pane
         * that contains it.  Note that this must be done after the 
         * stage is shown, or canvas size will be set to zero. */
        
        canvas.widthProperty().bind(root.widthProperty()); 
        canvas.heightProperty().bind(root.heightProperty());
        
        
        /* Start a timer that will continually update the positions
         * of the balls and redraw the canvas.  (There is no need
         * to redraw the canvas when it changes size, since it
         * will be redrawn by the timer in any case.) */
        
        AnimationTimer timer = new AnimationTimer() {
            long previousTime = 0;
            public void handle(long time) {
                    // Move all the balls, except no motion the
                    // first time that handle() is called.
                if (previousTime > 0) {
                    double width = canvas.getWidth();
                    double height = canvas.getHeight();
                    for (BouncingBall b: balls) {
                        b.move(width,height,(time - previousTime)/1.0e9);
                    }
                }
                redraw();
                previousTime = time;
            }
        };
        timer.start();

    }
    
    /**
     * Draw the canvas with the balls in their current positions.
     */
    private void redraw() { 
        double width = canvas.getWidth();
        double height = canvas.getHeight();
        g.setFill(Color.WHITE);
        g.fillRect(0,0,width,height);
        g.setFill(Color.RED);
        for (BouncingBall b: balls) {
            g.fillOval(b.x-b.radius, b.y-b.radius, 2*b.radius, 2*b.radius);
        }
    }

    /**
     * Represents a red disk that bounces around in the canvas.  The constructor
     * makes a disk with radius 10 and center at the center of the canvas.  Note
     * that the canvas must exist before the constructor is called.  The
     * new disk is given a random velocity in the range 100 to 400 pixels per second.
     */
    private class BouncingBall {
        double x,y;      // center of the disk
        double radius;   // radius of the disk
        double dx,dy;    // velocity in pixels per second
        BouncingBall() {
            x = canvas.getWidth()/2;
            y = canvas.getHeight()/2;
            this.radius = 10;
            double velocity = 100 + 300*Math.random();
            double angle = 2 * Math.PI * Math.random();
            dx = velocity*Math.cos(angle);
            dy = velocity*Math.sin(angle);
        }
        void move(double canvasWidth, double canvasHeight, double elapsedTimeInSeconds) {
                // Move the ball by an amount equal to its velocity,
                // multiplied by elapsed time since the previous frame.
                // If it crosses an edge of the canvas, it is moved
                // back into the canvas and its velocity is reversed.
                // If it is outside the canvas because the canvas has
                // shrunk, rather than because it crosses an edge,
                // then it merely heads back in the direction of
                // the canvas.
            double w = canvasWidth;
            double h = canvasHeight;
            x += dx * elapsedTimeInSeconds;
            y += dy * elapsedTimeInSeconds;
            if (x < radius) { 
                    // bounce off left edge
                x = 2*radius - x;
                dx = Math.abs(dx);
            }
            else if (x - dx < w - radius && x > w - radius) { 
                    // bounce off right edge
                x = 2 * (w - radius) - x;
                dx = -Math.abs(dx);
            }
            else if (x > w - radius) { 
                    // Disk is outside the right edge but didn't move there.
                    // Presumably this happened because canvas got smaller.
                dx = -Math.abs(dx);  // head back towards canvas.
            }
            if (y < radius) { 
                y = 2*radius - y;
                dy = Math.abs(dy);
            }
            else if (y - dy < h - radius && y > h - radius) { 
                y = 2 * (h - radius) - y;
                dy = -Math.abs(dy);
            }
            else if (y > h - radius) { 
                dy = -Math.abs(dy);
            }
        }
        void headTowards( double a, double b ) {
                // Reset the direction in which the ball is moving so
                // that its new velocity vector points in the direction
                // from its current location to (a,b).  The speed is
                // not changed, only the direction,
            if (Math.abs(a-x) < 1e-6 && Math.abs(b-y) < 1e-6)
                return;
            double velocity = Math.sqrt(dx*dx + dy*dy);
            double vecx = a - x;
            double vecy = b - y;
            double length = Math.sqrt(vecx*vecx + vecy*vecy);
            double dirx = vecx/length; // unit vector in direction from (x,y) to (a,b)
            double diry = vecy/length;
            dx = velocity * dirx;
            dy = velocity * diry;
        }
    }
    
} // end class CanvasResizeDemo
