import java.util.concurrent.TimeUnit;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.binding.NumberBinding;
import javafx.beans.value.ObservableBooleanValue;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;

public class MultiWindowPerformanceDrop extends Application
{
   @Override
   public void start(Stage primaryStage) throws Exception
   {
	   final String version = String.format("Java: %s, JavaFX: %s", System.getProperty("java.runtime.version"),
				System.getProperty("javafx.version"));
		primaryStage.setTitle(version);
      primaryStage.setScene(new Scene(newAnimationPane(), 720, 480));
      primaryStage.show();
   }

   public static void main(String[] args)
   {
      launch();
   }

   /**
    * Creates a new Stage with all the controls populated.
    */
   public static void newStage()
   {
      Stage stage = new Stage();
	  final String version = String.format("Java: %s, JavaFX: %s", System.getProperty("java.runtime.version"),
				System.getProperty("javafx.version"));
		stage.setTitle(version);
      Pane animationPane = newAnimationPane();
      stage.setScene(new Scene(animationPane, 720, 480));
      stage.setOnCloseRequest(e -> animationPane.getChildren().clear());
      stage.show();
   }

   /**
    * Creates a new root pane with controls and animations used for the test.
    */
   public static Pane newAnimationPane()
   {
      FlowPane pane = new FlowPane();

      // Button to add an animate-able widget to the pane
      Button add = new Button("Add gizmo");
      // Button to remove the last widget added.
      Button remove = new Button("Remove gizmo");
      // Toggle the animation state for all the widgets contained in the pane.
      ToggleButton animate = new ToggleButton("Animate");
      // Button for creating another stage with a separate animation pane.
      Button createStage = new Button("Create Stage");

      pane.getChildren().addAll(add, remove, animate, new FPSDisplay(), createStage);

      add.setOnAction(e ->
      {
         pane.getChildren().add(new AnimatedGizmo(animate.selectedProperty()));
      });

      remove.setOnAction(e ->
      {
         int lastChildIndex = pane.getChildren().size() - 1;
         Node lastNode = pane.getChildren().get(lastChildIndex);

         if (lastNode instanceof AnimatedGizmo)
            pane.getChildren().remove(lastChildIndex);
      });

      createStage.setOnAction(e ->
      {
         newStage();
      });

      return pane;
   }

   /**
    * A little widget for keeping track of the current frame rate.
    */
   private static class FPSDisplay extends HBox
   {
      private final Label fpsLabel = new Label();

      /** For filtering the frame rate update to 2Hz. */
      private long timeIntervalBetweenUpdates = TimeUnit.MILLISECONDS.toNanos(500);

      /** To keep track of the last time the frame rate was computed. */
      private long timeLast = -1;
      /** Number of rendered frames since last time we computed frame rate. */
      private int frameCounter = 0;

      /** Timer used to compute the frame rate. */
      private final AnimationTimer timer = new AnimationTimer()
      {
         @Override
         public void handle(long timeNow)
         {
            if (timeLast == -1)
            { // First call ever, initialize.
               timeLast = timeNow;
               frameCounter = 0;
            }
            else
            {
               // Increment number of frame rendered since timeLast.
               frameCounter++;

               if (timeNow - timeLast >= timeIntervalBetweenUpdates)
               { // It is time to compute our new averaged frame rate.
                 // Duration since the last time we computed the frame rate.
                  double timeElapsedInSeconds = (timeNow - timeLast) * 1.0e-9;
                  // Our averaged frame rate.
                  double framesPerSecond = frameCounter / timeElapsedInSeconds;
                  fpsLabel.setText(String.format("%6.2f FPS", framesPerSecond));

                  timeLast = timeNow;
                  frameCounter = 0;
               }
            }
         }
      };

      public FPSDisplay()
      {
         setPrefSize(50, 50);
         setAlignment(Pos.CENTER);
         fpsLabel.setMinWidth(70);
         getChildren().add(fpsLabel);

         parentProperty().addListener((o, oldValue, newValue) ->
         {
            if (newValue != null)
               timer.start();
            else
               timer.stop();
         });
      }
   }

   /**
    * A little widget to simulate some graphic animation happening.
    */
   private static class AnimatedGizmo extends AnchorPane
   {
      /** We use a triangle that will rotate to simulate an animation. */
      private final Polygon triangle = new Polygon();
      /** The transform used to rotate the triangle. */
      private final Rotate rotate = new Rotate();
      /** The animation. */
      private final AnimationTimer timer = new AnimationTimer()
      {
         @Override
         public void handle(long now)
         {
            rotate.setAngle(rotate.getAngle() + 1.0);
         }
      };

      public AnimatedGizmo(ObservableBooleanValue animate)
      {
         setPrefSize(50, 50);
         double radius = 0.5;
         double cos60 = Math.sqrt(3) / 2.0;
         double sin60 = 0.5;
         // Drawing a triangle
         triangle.getPoints().addAll(0.0, -radius, cos60 * radius, sin60 * radius, -cos60 * radius, sin60 * radius);
         triangle.setStroke(null);
         triangle.setFill(Color.BLUE);
         // Position the triangle in the center of the pane
         DoubleBinding halfWidth = widthProperty().divide(2.0);
         DoubleBinding halfHeight = heightProperty().divide(2.0);
         Translate translate = new Translate();
         translate.xProperty().bind(halfWidth);
         translate.yProperty().bind(halfHeight);
         // Scale the triangle to fill the pane
         Scale scale = new Scale();
         NumberBinding scaleFactor = Bindings.min(halfWidth, halfHeight).divide(radius);
         scale.xProperty().bind(scaleFactor);
         scale.yProperty().bind(scaleFactor);

         triangle.getTransforms().addAll(translate, scale, rotate);

         animate.addListener((o, oldValue, newValue) ->
         {
            rotate.setAngle(0.0);
            if (newValue && getParent() != null)
               timer.start();
            else
               timer.stop();
         });

         parentProperty().addListener((o, oldValue, newValue) ->
         {
            rotate.setAngle(0.0);
            if (newValue != null && animate.get())
               timer.start();
            else
               timer.stop();
         });

         getChildren().add(triangle);
      }
   }
} 