import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

/**
 * @see https://stackoverflow.com/a/44056730/230513
 */
public class CanvasTaskTest extends Application {

	private static final int PANEL_WIDTH =600, PANEL_HEIGHT = 600;
	private static final int TRI_WIDTH= 500, TRI_HEIGHT= 500;
	private static final int SIDE_GAP = (PANEL_WIDTH - TRI_WIDTH)/2;
	private static final int TOP_GAP = (PANEL_HEIGHT - TRI_HEIGHT)/2;
	private Canvas canvas;

	private long startTime;
	private int countTriangles;
	private static int numberOfLevels = 13;

	@Override
	public void start(Stage stage) {

		stage.setTitle("CanvasTaskTest");
		StackPane root = new StackPane();
		canvas = new Canvas(PANEL_WIDTH, PANEL_HEIGHT);
		root.getChildren().add(canvas);
		Scene scene = new Scene(root);
		stage.setScene(scene);
		stage.show();
		CanvasTask task = new CanvasTask();
		Thread thread = new Thread(task);
		thread.setDaemon(true);
		thread.start();
	}

	private void updateGraphics(boolean success, Canvas backgroundCanvas){

		if(success) {

			copyCanvas(backgroundCanvas);

			GraphicsContext gc = canvas.getGraphicsContext2D();
			gc.fillText("Number of triangles: "+ countTriangles,5,15);
			gc.fillText("Time: "+ (System.currentTimeMillis()- startTime )+ " mili seconds", 5,35);
			gc.fillText("Levels: "+ numberOfLevels,5,55);
		}else {

			GraphicsContext gc = canvas.getGraphicsContext2D();
			gc.clearRect(0, 0, PANEL_WIDTH, PANEL_HEIGHT);
			gc.fillText("Failed ",5,15);
		}
	}

	private void copyCanvas(Canvas backgroundCanvas) {

		WritableImage image = backgroundCanvas.snapshot(null, null);
		canvas.getGraphicsContext2D().drawImage(image, 0, 0);
	}

	private class CanvasTask extends Task<Void> {

		private Point2D top, left, right;
		private Canvas backgroundCanvas;

		private GraphicsContext gc;

		@Override
		protected Void call() throws Exception {

			backgroundCanvas = new Canvas(PANEL_WIDTH, PANEL_HEIGHT);
			gc = backgroundCanvas.getGraphicsContext2D();
			countTriangles = 0;
			setStartPoints();
			startTime = System.currentTimeMillis();

			Platform.runLater(new Runnable() {

				@Override
				public void run() {
					drawTriangle(numberOfLevels, top, left, right);
				}
			});

			return null;
		}

		@Override
		protected void succeeded() {

			updateGraphics(true, backgroundCanvas);
			super.succeeded();
		}

		@Override
		protected void failed() {

			updateGraphics(false, null);
		}

		private void drawTriangle( int levels,
				Point2D top, Point2D left, Point2D right) {

			if(levels < 0) {//add stop criteria
				return ;
			}

			gc.strokePolygon( //implementing with strokeLine did not make much difference
					new double[]{
							top.getX(),left.getX(),right.getX()
					},
					new double[]{
							top.getY(),left.getY(), right.getY()
					},
					3);

			countTriangles++;

			//Get the midpoint on each edge in the triangle
			Point2D p12 = midpoint(top, left);
			Point2D p23 = midpoint(left, right);
			Point2D p31 = midpoint(right, top);

			// recurse on 3 triangular areas
			drawTriangle(levels - 1, top, p12, p31);
			drawTriangle(levels - 1, p12, left, p23);
			drawTriangle(levels - 1, p31, p23, right);
		}

		private void setStartPoints() {

			top = new Point2D(PANEL_WIDTH/2, TOP_GAP);
			left = new Point2D(SIDE_GAP, TOP_GAP + TRI_HEIGHT);
			right = new Point2D(SIDE_GAP + TRI_WIDTH, TOP_GAP + TRI_WIDTH);
		}

		private Point2D midpoint(Point2D p1, Point2D p2) {

			return new Point2D((p1.getX() + p2.getX()) /
					2, (p1.getY() + p2.getY()) / 2);
		}
	}

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