/*
 * Copyright (C) 2025 Michael Zucchi
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package au.notzed.demo.proto;

import java.math.BigInteger;
import java.time.Duration;
import javafx.animation.AnimationTimer;
import javafx.animation.Interpolator;
import javafx.animation.RotateTransition;
import javafx.animation.Transition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.Pane;
import javafx.scene.robot.Robot;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class Latency extends Application {
	static double step = 16;
	static double rate = 25;

	@Override
	public void start(Stage stage) throws Exception {
		double yo = -32;
		double xo = -32;
		Group g = new Group(
			new Line(0 + xo, 300 + yo, 0 + xo, -300 + yo),
			new Line(400 + xo, 300 + yo, 400 + xo, -300 + yo),
			new Line(-400 + xo, 300 + yo, -400 + xo, -300 + yo),
			new Line(-200 + xo, 0 + yo, 200 + xo, 0 + yo),
			new Line(0, -10, 0, 10),
			new Line(step, -10, step, 10),
			new Line(step * 2, -10, step * 2, 10)
		);
		int c = 0;
		for (Node n: g.getChildren()) {
			if (c < 4 && n instanceof Line l) {
				l.setStrokeWidth(4);
			}
			c++;
		}

		record rational(long num, long den) {
			public static rational ofDouble(double v) {
				long d = Double.doubleToRawLongBits(v);
				long m = d & 0x000fffffffffffffL;
				long o = 0x0020000000000000L;

				m |= 0x0010000000000000L;

				BigInteger a = BigInteger.valueOf(o);
				BigInteger b = BigInteger.valueOf(m);
				BigInteger gcd = a.gcd(b);

				BigInteger num = a.divide(gcd);
				BigInteger den = b.divide(gcd);

				System.out.printf(" %12.9f = %s/%s\n", v, num, den);

				return null;
			}
		}

		Pane pane = new Pane(g);

		Scene scene = new Scene(pane, 1280, 768, false, SceneAntialiasing.BALANCED);

		//scene.getStylesheets().add(Latency.class.getResource("black.css").toExternalForm());

		stage.setScene(scene);
		stage.show();

		scene.setOnKeyPressed(ev -> {
			if (ev.getCode() == KeyCode.ESCAPE || ev.getCode() == KeyCode.Q)
				Platform.exit();
		});

		pane.setOnMouseMoved(e -> {
			g.setLayoutX(e.getX());
			g.setLayoutY(e.getY());
		});

		Robot robot = new Robot();

		switch (mode) {
		case 0 -> {
			pane.getChildren().add(new Text(20, 20, String.format("Step: %5.2f: Mode: AnimationTimer", step)));
			AnimationTimer at = new AnimationTimer() {
				double t = 0;

				@Override
				public void handle(long now) {
					double x = stage.getX() + stage.getWidth() * 0.5;
					double y = stage.getY() + stage.getHeight() * 0.5;

					double s = t;
					if (s % 1024 < 768)
						x += (s % 1024) - 512;
					else
						x += 256;
					robot.mouseMove(x, y);
					t = (t + step);
				}
			};
			at.start();
		}
		case 2 -> {
			pane.getChildren().add(new Text(20, 20, String.format("Step: %5.2f: Mode: Transition @ %9.5f", step, rate)));
			Transition at = new Transition(rate) {
				double t = 0;

				{
					setInterpolator(Interpolator.LINEAR);
					setCycleCount(INDEFINITE);
					setCycleDuration(javafx.util.Duration.seconds(3600));
				}

				@Override
				protected void interpolate(double frac) {
					double x = stage.getX() + stage.getWidth() * 0.5;
					double y = stage.getY() + stage.getHeight() * 0.5;

					double s = t;
					if (s % 1024 < 768)
						x += (s % 1024) - 512;
					else
						x += 256;
					robot.mouseMove(x, y);
					t = (t + step);
				}
			};
			at.play();
		}
		case 1 -> {
			// t = f * rn / rd;
			// f = t * rd / rn;
			pane.getChildren().add(new Text(20, 20, String.format("Step: %5.2f: Mode: Thread @ %9.5f", step, rate)));
			// How to get the true frame rate?
			Thread.ofPlatform().daemon().start(() -> {
				try {
					double t = 0;
					long start = System.nanoTime();
					double rn = 1000000000.0;
					double rd = rate;
					//long rn = 1000000000L;
					long target = start + (long)(1000000000L / rate);

					while (true) {
						long time = System.nanoTime() - start;

						if (false) {
							long now = System.nanoTime();
							long diff = (target - now);

							if (diff > 0) {
								Thread.sleep(Duration.ofNanos(diff));
								target += (long)(1000000000L / rate);
							} else {
								System.out.printf("Dropped frames: %d\n", -diff / (1000000000L / 25));
								Thread.sleep(Duration.ofMillis(1));
							}
						} else {
							double frame = time * rd / rn;
							double next = (int)frame + 1;
							long wait = (long)(next * rn / rd) - time;

							//System.out.printf(" frame %f next %f wait %d\n", frame, next, wait);
							//if (wait < 1000)
							//	wait = 10000000;
							Thread.sleep(Duration.ofNanos(wait));
						}

						//double s = t % (Math.PI * 2);
						double s = t;

						Platform.runLater(() -> {
							//double x = Math.cos(s) * 200 + stage.getX() + stage.getWidth() * 0.5;
							//double y = Math.sin(s*3) * 100 + stage.getY() + stage.getHeight() * 0.5;

							double x = stage.getX() + stage.getWidth() * 0.5;
							double y = stage.getY() + stage.getHeight() * 0.5;

							if (s % 1024 < 768)
								x += (s % 1024) - 512;
							else
								x += 256;
							robot.mouseMove(x, y);
							/*
						Event.fireEvent(pane, new MouseEvent(MouseEvent.MOUSE_MOVED,
							x + 1280/2 - 50, 768/2,
							x + 1280/2 - 50, 768/2,
							MouseButton.NONE, 0,
							false, false, false, false,
							false, false, false,
							false, false, false,
							null));
							 */
						});

						t = (t + step);

					}
				} catch (InterruptedException ex) {
				}
			});
		}
		}

		Stage s = new Stage();
		Group h = new Group(
			new Line(-100, -100, 100, 100),
			new Line(-100, 100, 100, -100));
		Pane p = new Pane(h);
		s.setX(32);
		s.setY(32);
		s.setScene(new Scene(p, 100, 100));
		s.show();

		s.getScene().setOnKeyPressed(ev -> {
			if (ev.getCode() == KeyCode.ESCAPE || ev.getCode() == KeyCode.Q)
				Platform.exit();
		});

		RotateTransition x = new RotateTransition(javafx.util.Duration.seconds(5), h);
		x.setInterpolator(Interpolator.LINEAR);
		x.setFromAngle(0);
		x.setToAngle(360);
		x.setCycleCount(RotateTransition.INDEFINITE);
		x.play();
	}

	static int mode;

	public static void main(String args[]) {
		for (int i = 0; i < args.length;) {
			switch (args[i++]) {
			case "--timer":
				mode = 0;
				break;
			case "--thread":
				mode = 1;
				break;
			case "--anim":
				mode = 2;
				break;
			case "--step":
				step = Double.parseDouble(args[i++]);
				break;
			case "--rate":
				rate = Double.parseDouble(args[i++]);
				break;
			}
		}
		launch(args);
	}

}
