package kha.js.vr;

import js.Syntax;
import js.lib.Float32Array;
import kha.vr.Pose;
import kha.vr.PoseState;
import kha.vr.SensorState;
import kha.vr.TimeWarpParms;
import kha.math.FastMatrix4;
import kha.math.Vector3;
import kha.math.Quaternion;
import kha.SystemImpl;

class VrInterface extends kha.vr.VrInterface {
	var vrEnabled: Bool = false;

	var vrDisplay: Dynamic;
	var frameData: Dynamic;

	var leftProjectionMatrix: FastMatrix4 = FastMatrix4.identity();
	var rightProjectionMatrix: FastMatrix4 = FastMatrix4.identity();
	var leftViewMatrix: FastMatrix4 = FastMatrix4.identity();
	var rightViewMatrix: FastMatrix4 = FastMatrix4.identity();

	var width: Int = 0;
	var height: Int = 0;
	var vrWidth: Int = 0;
	var vrHeight: Int = 0;

	public function new() {
		super();
		#if kha_webvr
		var displayEnabled: Bool = Syntax.code("navigator.getVRDisplays");
		#else
		var displayEnabled = false;
		#end
		if (displayEnabled) {
			vrEnabled = true;
			getVRDisplays();
			trace("Display enabled.");
		}
	}

	function getVRDisplays() {
		var vrDisplayInstance = Syntax.code("navigator.getVRDisplays()");
		vrDisplayInstance.then(function(displays) {
			if (displays.length > 0) {
				frameData = Syntax.code("new VRFrameData()");
				vrDisplay = Syntax.code("displays[0]");
				vrDisplay.depthNear = 0.1;
				vrDisplay.depthFar = 1024.0;

				var leftEye = vrDisplay.getEyeParameters("left");
				var rightEye = vrDisplay.getEyeParameters("right");
				width = SystemImpl.khanvas.width;
				height = SystemImpl.khanvas.height;
				vrWidth = Std.int(Math.max(leftEye.renderWidth, rightEye.renderWidth) * 2);
				vrHeight = Std.int(Math.max(leftEye.renderHeight, rightEye.renderHeight));
			}
			else {
				trace("There are no VR displays connected.");
			}
		});
	}

	public override function onVRRequestPresent() {
		try {
			vrDisplay.requestPresent([{source: SystemImpl.khanvas}]).then(function() {
				onResize();
				vrDisplay.requestAnimationFrame(onAnimationFrame);
			});
		}
		catch (err:Dynamic) {
			trace("Failed to requestPresent.");
			trace(err);
		}
	}

	public override function onVRExitPresent() {
		try {
			vrDisplay.exitPresent([{source: SystemImpl.khanvas}]).then(function() {
				onResize();
			});
		}
		catch (err:Dynamic) {
			trace("Failed to exitPresent.");
			trace(err);
		}
	}

	public override function onResetPose() {
		try {
			vrDisplay.resetPose();
		}
		catch (err:Dynamic) {
			trace("Failed to resetPose");
			trace(err);
		}
	}

	function onAnimationFrame(timestamp: Float): Void {
		if (vrDisplay != null && vrDisplay.isPresenting) {
			vrDisplay.requestAnimationFrame(onAnimationFrame);

			vrDisplay.getFrameData(frameData);

			leftProjectionMatrix = createMatrixFromArray(untyped frameData.leftProjectionMatrix);
			leftViewMatrix = createMatrixFromArray(untyped frameData.leftViewMatrix);

			rightProjectionMatrix = createMatrixFromArray(untyped frameData.rightProjectionMatrix);
			rightViewMatrix = createMatrixFromArray(untyped frameData.rightViewMatrix);

			// Submit the newly rendered layer to be presented by the VRDisplay
			vrDisplay.submitFrame();
		}
	}

	function onResize() {
		if (vrDisplay != null && vrDisplay.isPresenting) {
			SystemImpl.khanvas.width = vrWidth;
			SystemImpl.khanvas.height = vrHeight;
		}
		else {
			SystemImpl.khanvas.width = width;
			SystemImpl.khanvas.height = height;
		}
	}

	public override function GetSensorState(): SensorState {
		return GetPredictedSensorState(0.0);
	}

	public override function GetPredictedSensorState(time: Float): SensorState {
		var result: SensorState = new SensorState();

		result.Predicted = new PoseState();
		result.Recorded = result.Predicted;

		result.Predicted.AngularAcceleration = new Vector3();
		result.Predicted.AngularVelocity = new Vector3();
		result.Predicted.LinearAcceleration = new Vector3();
		result.Predicted.LinearVelocity = new Vector3();
		result.Predicted.TimeInSeconds = time;
		result.Predicted.Pose = new Pose();
		result.Predicted.Pose.Orientation = new Quaternion();
		result.Predicted.Pose.Position = new Vector3();

		var mPose = frameData.pose; // predicted pose of the vrDisplay
		if (mPose != null) {
			result.Predicted.AngularVelocity = createVectorFromArray(untyped mPose.angularVelocity);
			result.Predicted.AngularAcceleration = createVectorFromArray(untyped mPose.angularAcceleration);
			result.Predicted.LinearVelocity = createVectorFromArray(untyped mPose.linearVelocity);
			result.Predicted.LinearAcceleration = createVectorFromArray(untyped mPose.linearAcceleration);
			result.Predicted.Pose.Orientation = createQuaternion(untyped mPose.orientation);
			result.Predicted.Pose.Position = createVectorFromArray(untyped mPose.position);
		}

		return result;
	}

	// Sends a black image to the warp swap thread
	public override function WarpSwapBlack(): Void {
		// TODO: Implement
	}

	// Sends the Oculus loading symbol to the warp swap thread
	public override function WarpSwapLoadingIcon(): Void {
		// TODO: Implement
	}

	// Sends the set of images to the warp swap thread
	public override function WarpSwap(parms: TimeWarpParms): Void {
		// TODO: Implement
	}

	public override function IsPresenting(): Bool {
		if (vrDisplay != null)
			return vrDisplay.isPresenting;
		return false;
	}

	public override function IsVrEnabled(): Bool {
		return vrEnabled;
	}

	public override function GetTimeInSeconds(): Float {
		return Scheduler.time();
	}

	public override function GetProjectionMatrix(eye: Int): FastMatrix4 {
		if (eye == 0) {
			return leftProjectionMatrix;
		}
		else {
			return rightProjectionMatrix;
		}
	}

	public override function GetViewMatrix(eye: Int): FastMatrix4 {
		if (eye == 0) {
			return leftViewMatrix;
		}
		else {
			return rightViewMatrix;
		}
	}

	function createMatrixFromArray(array: Float32Array): FastMatrix4 {
		var matrix: FastMatrix4 = FastMatrix4.identity();
		matrix._00 = array[0];
		matrix._01 = array[1];
		matrix._02 = array[2];
		matrix._03 = array[3];
		matrix._10 = array[4];
		matrix._11 = array[5];
		matrix._12 = array[6];
		matrix._13 = array[7];
		matrix._20 = array[8];
		matrix._21 = array[9];
		matrix._22 = array[10];
		matrix._23 = array[11];
		matrix._30 = array[12];
		matrix._31 = array[13];
		matrix._32 = array[14];
		matrix._33 = array[15];
		return matrix;
	}

	function createVectorFromArray(array: Float32Array): Vector3 {
		var vector: Vector3 = new Vector3(0, 0, 0);
		if (array != null) {
			vector.x = array[0];
			vector.y = array[1];
			vector.z = array[2];
		}
		return vector;
	}

	function createQuaternion(array: Float32Array): Quaternion {
		var quaternion: Quaternion = new Quaternion(0, 0, 0, 0);
		if (array != null) {
			quaternion.x = array[0];
			quaternion.y = array[1];
			quaternion.z = array[2];
			quaternion.w = array[3];
		}
		return quaternion;
	}
}