//License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.streetside.gui.imageinfo;

import java.awt.image.BufferedImage;

import org.openstreetmap.josm.plugins.streetside.cubemap.CameraTransformer;
import org.openstreetmap.josm.plugins.streetside.cubemap.GraphicsUtils;
import org.openstreetmap.josm.plugins.streetside.utils.CubemapBox;
import org.openstreetmap.josm.tools.I18n;
import org.openstreetmap.josm.tools.Logging;

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.transform.NonInvertibleTransformException;


@SuppressWarnings("restriction")
public class ThreeSixtyDegreeViewerPanel extends JFXPanel {

	private static final long serialVersionUID = -4940350009018422000L;

	private static Group root;
	private static Group subGroup;
	private static CubemapBox cubemapBox;
	private static PerspectiveCamera camera;
	private static CameraTransformer cameraTransform = new CameraTransformer();

	private static double mousePosX;
	private static double mousePosY;
	private static double mouseOldX;
	private static double mouseOldY;
	private static double mouseDeltaX;
	private static double mouseDeltaY;
	private static double cameraDistance = 5000;

	// Supply Image Paths or a NullPointer will occur
	private static Image front;
	private static Image right;
	private static Image back;
	private static Image left;
	private static Image up;
	private static Image down;

	public ThreeSixtyDegreeViewerPanel() {
		// constructor
	}

	void initialize() {

		root = new Group();

		camera = new PerspectiveCamera(true);
		cameraTransform.setTranslate(0, 0, 0);
		cameraTransform.getChildren().addAll(camera);
		camera.setNearClip(0.1);
		camera.setFarClip(1000000.0);
		camera.setFieldOfView(42);
		camera.setTranslateZ(-cameraDistance);
		// cameraTransform.ry.setAngle(-45.0);
		// cameraTransform.rx.setAngle(-10.0);
		// add a Point Light for better viewing of the grid coordinate system
		final PointLight light = new PointLight(Color.WHITE);

		cameraTransform.getChildren().add(light);
		light.setTranslateX(camera.getTranslateX());
		light.setTranslateY(camera.getTranslateY());
		light.setTranslateZ(camera.getTranslateZ());

		root.getChildren().add(cameraTransform);

		final double size = 100000D;

		cubemapBox = new CubemapBox(front, right, back, left, up, down, size, camera);

		subGroup = new Group();
		subGroup.getChildren().add(cameraTransform);

		Platform.runLater(new Runnable() {
			@Override
			public void run() {
				try {
					// TODO: create Default Scene and replace with 360 degree scene @rrh
					setScene(createScene());
				} catch (NonInvertibleTransformException e) {
					Logging.error(I18n.tr("Error initializing StreetsideViewerPanel - JavaFX {0}", e.getMessage()));
				}
			}
		});
	}

	private static Scene createScene() throws NonInvertibleTransformException {

		/*root = new Group();

		camera = new PerspectiveCamera(true);
		cameraTransform.setTranslate(0, 0, 0);
		cameraTransform.getChildren().addAll(camera);
		camera.setNearClip(0.1);
		camera.setFarClip(1000000.0);
		camera.setFieldOfView(42);
		camera.setTranslateZ(-cameraDistance);
		final PointLight light = new PointLight(Color.WHITE);

		cameraTransform.getChildren().add(light);
		light.setTranslateX(camera.getTranslateX());
		light.setTranslateY(camera.getTranslateY());
		light.setTranslateZ(camera.getTranslateZ());

		root.getChildren().add(cameraTransform);

		// Load Cubemap box AFTER camera is initialized
		final double size = 100000D;

		cubemapBox = new CubemapBox(null, null, null, null, null, null, size, camera);

		subGroup = new Group();
		subGroup.getChildren().add(cameraTransform);*/

		final Scene scene = new Scene(new Group(root), 1024, 668, true, SceneAntialiasing.BALANCED);
		scene.setFill(Color.TRANSPARENT);
		scene.setCamera(camera);

		// First person shooter keyboard movement
		scene.setOnKeyPressed(event -> {
			double change = 10.0;
			// Add shift modifier to simulate "Running Speed"
			if (event.isShiftDown()) {
				change = 50.0;
			}
			// What key did the user press?
			final KeyCode keycode = event.getCode();
			// Step 2c: Add Zoom controls
			if (keycode == KeyCode.W) {
				camera.setTranslateZ(camera.getTranslateZ() + change);
			}
			if (keycode == KeyCode.S) {
				camera.setTranslateZ(camera.getTranslateZ() - change);
			}
			// Step 2d: Add Strafe controls
			if (keycode == KeyCode.A) {
				camera.setTranslateX(camera.getTranslateX() - change);
			}
			if (keycode == KeyCode.D) {
				camera.setTranslateX(camera.getTranslateX() + change);
			}
		});

		scene.setOnMousePressed((MouseEvent me) -> {
			mousePosX = me.getSceneX();
			mousePosY = me.getSceneY();
			mouseOldX = me.getSceneX();
			mouseOldY = me.getSceneY();
		});
		scene.setOnMouseDragged((MouseEvent me) -> {
			mouseOldX = mousePosX;
			mouseOldY = mousePosY;
			mousePosX = me.getSceneX();
			mousePosY = me.getSceneY();
			mouseDeltaX = mousePosX - mouseOldX;
			mouseDeltaY = mousePosY - mouseOldY;

			double modifier = 10.0;
			final double modifierFactor = 0.1;

			if (me.isControlDown()) {
				modifier = 0.1;
			}
			if (me.isShiftDown()) {
				modifier = 50.0;
			}
			if (me.isPrimaryButtonDown()) {
				cameraTransform.ry.setAngle(
						((cameraTransform.ry.getAngle() + mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540)
								% 360 - 180); // +
				cameraTransform.rx.setAngle(
						((cameraTransform.rx.getAngle() - mouseDeltaY * modifierFactor * modifier * 2.0) % 360 + 540)
								% 360 - 180); // -

			} else if (me.isSecondaryButtonDown()) {
				final double z = camera.getTranslateZ();
				final double newZ = z + mouseDeltaX * modifierFactor * modifier;
				camera.setTranslateZ(newZ);
			} else if (me.isMiddleButtonDown()) {
				cameraTransform.t.setX(cameraTransform.t.getX() + mouseDeltaX * modifierFactor * modifier * 0.3); // -
				cameraTransform.t.setY(cameraTransform.t.getY() + mouseDeltaY * modifierFactor * modifier * 0.3); // -
			}
		});

		/*scene.widthProperty().addListener(new ChangeListener<Number>() {
		    @Override public void changed(ObservableValue<? extends Number> observableValue, Number oldSceneWidth, Number newSceneWidth) {
		        System.out.println("Width: " + newSceneWidth);
		    }

			@Override
			public void changed(ObservableValue<? extends Number> observable, Number oldSceneWidth, Number newSceneWidth) {
				draw();
			}
		});*/
		/*scene.heightProperty().addListener(new ChangeListener<Number>() {
		    @Override public void changed(ObservableValue<? extends Number> observableValue, Number oldSceneHeight, Number newSceneHeight) {
		        //System.out.println("Height: " + newSceneHeight);
		    	draw();
		    }
		});*/

		root.getChildren().addAll(cubemapBox, subGroup);
		root.setAutoSizeChildren(true);

		subGroup.setAutoSizeChildren(true);

		// TODO: attempt to prevent content from disappearing after resizing
		Platform.setImplicitExit(false);

		return scene;
	}

	private static Scene createDefaultScene() {
		// TODO: default scene with message? @rrh

		// Load Cubemap box AFTER camera is initialized
		final double size = 100000D;

		TextArea textArea = new TextArea();
		textArea.setText("No Streetside image selected.");

		VBox vbox = new VBox(textArea);

		Scene scene = new Scene(vbox, 200, 100);
		return scene;
	}

	public static Scene createScene(BufferedImage img0, BufferedImage img1, BufferedImage img2, BufferedImage img3,
			BufferedImage img4, BufferedImage img5) throws NonInvertibleTransformException {
		front = GraphicsUtils.convertBufferedImage2JavaFXImage(img0);
		right = GraphicsUtils.convertBufferedImage2JavaFXImage(img1);
		back = GraphicsUtils.convertBufferedImage2JavaFXImage(img2);
		left = GraphicsUtils.convertBufferedImage2JavaFXImage(img3);
		up = GraphicsUtils.convertBufferedImage2JavaFXImage(img4);
		down = GraphicsUtils.convertBufferedImage2JavaFXImage(img5);

		root = new Group();

		camera = new PerspectiveCamera(true);
		cameraTransform.setTranslate(0, 0, 0);
		cameraTransform.getChildren().addAll(camera);
		camera.setNearClip(0.1);
		camera.setFarClip(1000000.0);
		camera.setFieldOfView(42);
		camera.setTranslateZ(-cameraDistance);
		// cameraTransform.ry.setAngle(-45.0);
		// cameraTransform.rx.setAngle(-10.0);
		// add a Point Light for better viewing of the grid coordinate system
		final PointLight light = new PointLight(Color.WHITE);

		cameraTransform.getChildren().add(light);
		light.setTranslateX(camera.getTranslateX());
		light.setTranslateY(camera.getTranslateY());
		light.setTranslateZ(camera.getTranslateZ());

		root.getChildren().add(cameraTransform);

		// Load Cubemap box AFTER camera is initialized
		final double size = 100000D;

		cubemapBox = new CubemapBox(front, right, back, left, up, down, size, camera);

		final Group torusGroup = new Group();
		torusGroup.getChildren().add(cameraTransform);

		final Scene scene = new Scene(new Group(root), 1024, 668, true, SceneAntialiasing.BALANCED);
		scene.setFill(Color.TRANSPARENT);
		scene.setCamera(camera);

		// First person shooter keyboard movement
		scene.setOnKeyPressed(event -> {
			double change = 10.0;
			// Add shift modifier to simulate "Running Speed"
			if (event.isShiftDown()) {
				change = 50.0;
			}
			// What key did the user press?
			final KeyCode keycode = event.getCode();
			// Step 2c: Add Zoom controls
			if (keycode == KeyCode.W) {
				camera.setTranslateZ(camera.getTranslateZ() + change);
			}
			if (keycode == KeyCode.S) {
				camera.setTranslateZ(camera.getTranslateZ() - change);
			}
			// Step 2d: Add Strafe controls
			if (keycode == KeyCode.A) {
				camera.setTranslateX(camera.getTranslateX() - change);
			}
			if (keycode == KeyCode.D) {
				camera.setTranslateX(camera.getTranslateX() + change);
			}

		});

		scene.setOnMousePressed((MouseEvent me) -> {
			mousePosX = me.getSceneX();
			mousePosY = me.getSceneY();
			mouseOldX = me.getSceneX();
			mouseOldY = me.getSceneY();
		});
		scene.setOnMouseDragged((MouseEvent me) -> {
			mouseOldX = mousePosX;
			mouseOldY = mousePosY;
			mousePosX = me.getSceneX();
			mousePosY = me.getSceneY();
			mouseDeltaX = mousePosX - mouseOldX;
			mouseDeltaY = mousePosY - mouseOldY;

			double modifier = 10.0;
			final double modifierFactor = 0.1;

			if (me.isControlDown()) {
				modifier = 0.1;
			}
			if (me.isShiftDown()) {
				modifier = 50.0;
			}
			if (me.isPrimaryButtonDown()) {
				cameraTransform.ry.setAngle(
						((cameraTransform.ry.getAngle() + mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540)
								% 360 - 180); // +
				cameraTransform.rx.setAngle(
						((cameraTransform.rx.getAngle() - mouseDeltaY * modifierFactor * modifier * 2.0) % 360 + 540)
								% 360 - 180); // -

			} else if (me.isSecondaryButtonDown()) {
				final double z = camera.getTranslateZ();
				final double newZ = z + mouseDeltaX * modifierFactor * modifier;
				camera.setTranslateZ(newZ);
			} else if (me.isMiddleButtonDown()) {
				cameraTransform.t.setX(cameraTransform.t.getX() + mouseDeltaX * modifierFactor * modifier * 0.3); // -
				cameraTransform.t.setY(cameraTransform.t.getY() + mouseDeltaY * modifierFactor * modifier * 0.3); // -
			}
		});

		root.getChildren().addAll(cubemapBox, torusGroup);
		root.setAutoSizeChildren(true);

		return scene;
	}

	/*public void setCubemapImages(BufferedImage img, BufferedImage img1, BufferedImage img2, BufferedImage img3,
			BufferedImage img4, BufferedImage img5) {
		cubemapBox = null;

		GraphicsUtils.PlatformHelper.run(new Runnable() {
			@Override
			public void run() {
				try {
					// initialize without imagery.
					scene = createScene(img, img1, img2, img3, img4, img5);
					setScene(scene);
				} catch (NonInvertibleTransformException e) {
					Logging.error(I18n.tr("Error initializing StreetsideViewerPanel - JavaFX {0}", e.getMessage()));
				}
			}
		});
	}*/

	public CubemapBox getCubemapBox() {
		if (cubemapBox == null) {
			// shouldn't happen
			initialize();
		}
		return cubemapBox;
	}
}