package com.example.rgcas;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.Cylinder;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.io.IOException;
import java.io.SerializablePermission;
import java.util.concurrent.ScheduledThreadPoolExecutor;

class MovableCamera extends PerspectiveCamera {
	
	private Translate pan;
	private Rotate rotateX;
	private Rotate rotateY;
	private Rotate yaw;
	private Rotate tilt;
	private Rotate roll;
	private double x;
	private double y;
	
	public MovableCamera ( double xAngle, double zDistance, double nearClip, double farClip ) {
		super ( true );
		
		this.setNearClip ( nearClip );
		this.setFarClip ( farClip );
		
		this.pan = new Translate ( 0, 0, zDistance );
		this.rotateX = new Rotate ( xAngle, Rotate.X_AXIS );
		this.rotateY = new Rotate ( 0, Rotate.Y_AXIS );
		
		this.yaw = new Rotate ( 0, Rotate.Y_AXIS );
		this.tilt = new Rotate ( 0, Rotate.X_AXIS );
		this.roll = new Rotate ( 0, Rotate.Z_AXIS );
		
		this.getTransforms ( ).addAll (
				this.rotateY,
				this.rotateX,
				this.pan,
				this.yaw,
				this.tilt,
				this.roll
		);
	}
	
	public void handleScrollEvent ( ScrollEvent event ) {
		double sign = event.getDeltaY ( ) > 0 ? 1 : -1;
		
		this.pan.setZ ( this.pan.getZ ( ) + sign * 10 );
	}
	
	public void handleMouseEvent ( MouseEvent event ) {
		if ( event.getEventType ( ).equals ( MouseEvent.MOUSE_PRESSED ) ) {
			this.x = event.getSceneX ( );
			this.y = event.getSceneY ( );
		} else if ( event.getEventType ( ).equals ( MouseEvent.MOUSE_DRAGGED ) ) {
			double dX = this.x - event.getSceneX ( );
			double dY = this.y - event.getSceneY ( );
			
			this.x = event.getSceneX ( );
			this.y = event.getSceneY ( );
			
			if ( event.isPrimaryButtonDown ( ) ) {
				double angleX = this.rotateX.getAngle ( );
				double angleY = this.rotateY.getAngle ( );
				
				this.rotateX.setAngle ( angleX + dY );
				this.rotateY.setAngle ( angleY + dX );
			} else if ( event.isSecondaryButtonDown ( ) ) {
				double panX = this.pan.getX ( );
				double panY = this.pan.getY ( );
				
				this.pan.setX ( panX + dX );
				this.pan.setY ( panY + dY );
			}
		}
	}
	
	public void handleKeyEvent ( KeyEvent event ) {
		switch ( event.getCode ( ) ) {
			case UP: {
				this.tilt.setAngle ( this.tilt.getAngle ( ) + 1 );
				break;
			}
			case DOWN: {
				this.tilt.setAngle ( this.tilt.getAngle ( ) - 1 );
				break;
			}
			case LEFT: {
				this.yaw.setAngle ( this.yaw.getAngle ( ) - 1 );
				break;
			}
			case RIGHT: {
				this.yaw.setAngle ( this.yaw.getAngle ( ) + 1 );
				break;
			}
			case PAGE_UP: {
				this.roll.setAngle ( this.roll.getAngle ( ) + 1 );
				break;
			}
			case PAGE_DOWN: {
				this.roll.setAngle ( this.roll.getAngle ( ) - 1 );
				break;
			}
		}
	}
}

public class HelloApplication extends Application {
	
	private static Group getCarousel ( ) {
		Group carousel = new Group ( );
		
		Cylinder podium = new Cylinder ( 200, 20 );
		PhongMaterial podiumMaterial = new PhongMaterial ( Color.BLUE );
		podium.setMaterial ( podiumMaterial );
		carousel.getChildren ( ).add ( podium );
		
		for ( int i = 0; i < 4; ++i ) {
			PhongMaterial chairMaterial = new PhongMaterial ( Color.YELLOW );
			
			Cylinder base = new Cylinder ( 40, 10 );
			base.setMaterial ( chairMaterial );
			base.getTransforms ( ).add (
					new Translate ( 0, -15, 0 )
			);
			
			Box seat = new Box ( 40, 20, 40 );
			seat.setMaterial ( chairMaterial );
			seat.getTransforms ( ).add (
				new Translate ( 0, -25, 0 )
			);
			
			Box rest = new Box ( 10, 40, 40 );
			rest.setMaterial ( chairMaterial );
			rest.getTransforms ( ).add (
					new Translate ( 15, -45, 0 )
			);
			
			Group chair = new Group ( base, seat, rest );
			
			Rotate chairRotate = new Rotate ( 0, Rotate.Y_AXIS );
			chair.getTransforms ( ).addAll (
					new Rotate ( i * 90, Rotate.Y_AXIS ),
					new Translate ( 0, 0, -150 ),
					chairRotate
			);
			
			Timeline chairTimeline = new Timeline (
					new KeyFrame ( Duration.ZERO, new KeyValue ( chairRotate.angleProperty ( ), 0 ) ),
					new KeyFrame ( Duration.seconds ( 2 ), new KeyValue ( chairRotate.angleProperty ( ), 360 ) )
			);
			chairTimeline.setCycleCount ( Animation.INDEFINITE );
			chairTimeline.play ( );
			
			carousel.getChildren ( ).add ( chair );
		}
		
		Rotate carouselRotate = new Rotate ( 0, Rotate.Y_AXIS );
		Timeline carouselTimeline = new Timeline (
				new KeyFrame ( Duration.ZERO, new KeyValue ( carouselRotate.angleProperty ( ), 0 ) ),
				new KeyFrame ( Duration.seconds ( 10 ), new KeyValue ( carouselRotate.angleProperty ( ), 360 ) )
		);
		carouselTimeline.setCycleCount ( Animation.INDEFINITE );
		carouselTimeline.play ( );
		
		carousel.getTransforms ( ).add ( carouselRotate );
		
		return carousel;
	}
	
	private static Group getGlobe ( ) {
		
		Sphere earth = new Sphere ( 100 );
		PhongMaterial earthMaterial = new PhongMaterial ( );
		earthMaterial.setDiffuseMap ( new Image ( "diffuse.jpg" ) );
		earthMaterial.setBumpMap ( new Image ( "normal.jpg" ) );
		earthMaterial.setSpecularMap ( new Image ( "specular.jpg" ) );
		
		earth.setMaterial ( earthMaterial );
		
		return new Group ( earth );
	}
	
	@Override
	public void start ( Stage stage ) throws IOException {
		Group root = new Group ( );
		
		PointLight pointLight = new PointLight ( Color.WHITE );
		pointLight.getTransforms ( ).add (
				new Translate ( 0, -500, 0 )
		);
		
		// root.getChildren ( ).addAll ( HelloApplication.getCarousel ( ), pointLight );
		root.getChildren ( ).addAll ( HelloApplication.getGlobe ( ) );
		
		Scene scene = new Scene ( root, 600, 600, true, SceneAntialiasing.BALANCED );
		
		MovableCamera camera = new MovableCamera ( -45, -1000, 1, 10000 );
		
		scene.setCamera ( camera );
		
		scene.addEventHandler ( MouseEvent.ANY, event -> camera.handleMouseEvent ( event ) );
		scene.addEventHandler ( ScrollEvent.ANY, event -> camera.handleScrollEvent ( event ) );
		scene.addEventHandler ( KeyEvent.ANY, event -> camera.handleKeyEvent ( event ) );
		
		scene.setFill ( Color.BLACK );
		stage.setTitle ( "Hello!" );
		stage.setScene ( scene );
		stage.show ( );
	}
	
	public static void main ( String[] args ) {
		launch ( );
	}
}