/**
 * @author mrdoob / http://mrdoob.com/
 */

import { EventDispatcher } from '../../core/EventDispatcher.js';
import { Group } from '../../objects/Group.js';
import { Matrix4 } from '../../math/Matrix4.js';
import { Vector2 } from '../../math/Vector2.js';
import { Vector3 } from '../../math/Vector3.js';
import { Vector4 } from '../../math/Vector4.js';
import { Quaternion } from '../../math/Quaternion.js';
import { ArrayCamera } from '../../cameras/ArrayCamera.js';
import { PerspectiveCamera } from '../../cameras/PerspectiveCamera.js';
import { WebGLAnimation } from '../webgl/WebGLAnimation.js';
import { setProjectionFromUnion } from './WebVRUtils.js';

function WebVRManager( renderer ) {

	var renderWidth, renderHeight;
	var scope = this;

	var device = null;
	var frameData = null;

	var controllers = [];
	var standingMatrix = new Matrix4();
	var standingMatrixInverse = new Matrix4();

	var framebufferScaleFactor = 1.0;

	var referenceSpaceType = 'local-floor';

	if ( typeof window !== 'undefined' && 'VRFrameData' in window ) {

		frameData = new window.VRFrameData();
		window.addEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange, false );

	}

	var matrixWorldInverse = new Matrix4();
	var tempQuaternion = new Quaternion();
	var tempPosition = new Vector3();

	var tempCamera = new PerspectiveCamera();

	var cameraL = new PerspectiveCamera();
	cameraL.viewport = new Vector4();
	cameraL.layers.enable( 1 );

	var cameraR = new PerspectiveCamera();
	cameraR.viewport = new Vector4();
	cameraR.layers.enable( 2 );

	var cameraVR = new ArrayCamera( [ cameraL, cameraR ] );
	cameraVR.layers.enable( 1 );
	cameraVR.layers.enable( 2 );

	//

	function isPresenting() {

		return device !== null && device.isPresenting === true;

	}

	var currentSize = new Vector2(), currentPixelRatio;

	function onVRDisplayPresentChange() {

		if ( isPresenting() ) {

			var eyeParameters = device.getEyeParameters( 'left' );
			renderWidth = 2 * eyeParameters.renderWidth * framebufferScaleFactor;
			renderHeight = eyeParameters.renderHeight * framebufferScaleFactor;

			currentPixelRatio = renderer.getPixelRatio();
			renderer.getSize( currentSize );

			renderer.setDrawingBufferSize( renderWidth, renderHeight, 1 );

			cameraL.viewport.set( 0, 0, renderWidth / 2, renderHeight );
			cameraR.viewport.set( renderWidth / 2, 0, renderWidth / 2, renderHeight );

			animation.start();

			scope.dispatchEvent( { type: 'sessionstart' } );

		} else {

			if ( scope.enabled ) {

				renderer.setDrawingBufferSize( currentSize.width, currentSize.height, currentPixelRatio );

			}

			animation.stop();

			scope.dispatchEvent( { type: 'sessionend' } );

		}

	}

	//

	var triggers = [];
	var grips = [];

	function findGamepad( id ) {

		var gamepads = navigator.getGamepads && navigator.getGamepads();

		for ( var i = 0, l = gamepads.length; i < l; i ++ ) {

			var gamepad = gamepads[ i ];

			if ( gamepad && ( gamepad.id === 'Daydream Controller' ||
				gamepad.id === 'Gear VR Controller' || gamepad.id === 'Oculus Go Controller' ||
				gamepad.id === 'OpenVR Gamepad' || gamepad.id.startsWith( 'Oculus Touch' ) ||
				gamepad.id.startsWith( 'HTC Vive Focus' ) ||
				gamepad.id.startsWith( 'Spatial Controller' ) ) ) {

				var hand = gamepad.hand;

				if ( id === 0 && ( hand === '' || hand === 'right' ) ) return gamepad;
				if ( id === 1 && ( hand === 'left' ) ) return gamepad;

			}

		}

	}

	function updateControllers() {

		for ( var i = 0; i < controllers.length; i ++ ) {

			var controller = controllers[ i ];

			var gamepad = findGamepad( i );

			if ( gamepad !== undefined && gamepad.pose !== undefined ) {

				if ( gamepad.pose === null ) return;

				// Pose

				var pose = gamepad.pose;

				if ( pose.hasPosition === false ) controller.position.set( 0.2, - 0.6, - 0.05 );

				if ( pose.position !== null ) controller.position.fromArray( pose.position );
				if ( pose.orientation !== null ) controller.quaternion.fromArray( pose.orientation );
				controller.matrix.compose( controller.position, controller.quaternion, controller.scale );
				controller.matrix.premultiply( standingMatrix );
				controller.matrix.decompose( controller.position, controller.quaternion, controller.scale );
				controller.matrixWorldNeedsUpdate = true;
				controller.visible = true;

				// Trigger

				var buttonId = gamepad.id === 'Daydream Controller' ? 0 : 1;

				if ( triggers[ i ] === undefined ) triggers[ i ] = false;

				if ( triggers[ i ] !== gamepad.buttons[ buttonId ].pressed ) {

					triggers[ i ] = gamepad.buttons[ buttonId ].pressed;

					if ( triggers[ i ] === true ) {

						controller.dispatchEvent( { type: 'selectstart' } );

					} else {

						controller.dispatchEvent( { type: 'selectend' } );
						controller.dispatchEvent( { type: 'select' } );

					}

				}

				// Grip
				buttonId = 2;

				if ( grips[ i ] === undefined ) grips[ i ] = false;

				// Skip if the grip button doesn't exist on this controller
				if ( gamepad.buttons[ buttonId ] !== undefined ) {

					if ( grips[ i ] !== gamepad.buttons[ buttonId ].pressed ) {

						grips[ i ] = gamepad.buttons[ buttonId ].pressed;

						if ( grips[ i ] === true ) {

							controller.dispatchEvent( { type: 'squeezestart' } );

						} else {

							controller.dispatchEvent( { type: 'squeezeend' } );
							controller.dispatchEvent( { type: 'squeeze' } );

						}

					}

				}

			} else {

				controller.visible = false;

			}

		}

	}

	function updateViewportFromBounds( viewport, bounds ) {

		if ( bounds !== null && bounds.length === 4 ) {

			viewport.set( bounds[ 0 ] * renderWidth, bounds[ 1 ] * renderHeight, bounds[ 2 ] * renderWidth, bounds[ 3 ] * renderHeight );

		}

	}

	//

	this.enabled = false;

	this.getController = function ( id ) {

		var controller = controllers[ id ];

		if ( controller === undefined ) {

			controller = new Group();
			controller.matrixAutoUpdate = false;
			controller.visible = false;

			controllers[ id ] = controller;

		}

		return controller;

	};

	this.getDevice = function () {

		return device;

	};

	this.setDevice = function ( value ) {

		if ( value !== undefined ) device = value;

		animation.setContext( value );

	};

	this.setFramebufferScaleFactor = function ( value ) {

		framebufferScaleFactor = value;

	};

	this.setReferenceSpaceType = function ( value ) {

		referenceSpaceType = value;

	};

	this.getCamera = function ( camera ) {

		var userHeight = referenceSpaceType === 'local-floor' ? 1.6 : 0;

		device.depthNear = camera.near;
		device.depthFar = camera.far;

		device.getFrameData( frameData );

		//

		if ( referenceSpaceType === 'local-floor' ) {

			var stageParameters = device.stageParameters;

			if ( stageParameters ) {

				standingMatrix.fromArray( stageParameters.sittingToStandingTransform );

			} else {

				standingMatrix.makeTranslation( 0, userHeight, 0 );

			}

		}


		var pose = frameData.pose;

		tempCamera.matrix.copy( standingMatrix );
		tempCamera.matrix.decompose( tempCamera.position, tempCamera.quaternion, tempCamera.scale );

		if ( pose.orientation !== null ) {

			tempQuaternion.fromArray( pose.orientation );
			tempCamera.quaternion.multiply( tempQuaternion );

		}

		if ( pose.position !== null ) {

			tempQuaternion.setFromRotationMatrix( standingMatrix );
			tempPosition.fromArray( pose.position );
			tempPosition.applyQuaternion( tempQuaternion );
			tempCamera.position.add( tempPosition );

		}

		tempCamera.updateMatrixWorld();

		//

		camera.matrixWorld.copy( tempCamera.matrixWorld );

		var children = camera.children;

		for ( var i = 0, l = children.length; i < l; i ++ ) {

			children[ i ].updateMatrixWorld( true );

		}

		//

		cameraL.near = camera.near;
		cameraR.near = camera.near;

		cameraL.far = camera.far;
		cameraR.far = camera.far;

		cameraL.matrixWorldInverse.fromArray( frameData.leftViewMatrix );
		cameraR.matrixWorldInverse.fromArray( frameData.rightViewMatrix );

		// TODO (mrdoob) Double check this code

		standingMatrixInverse.getInverse( standingMatrix );

		if ( referenceSpaceType === 'local-floor' ) {

			cameraL.matrixWorldInverse.multiply( standingMatrixInverse );
			cameraR.matrixWorldInverse.multiply( standingMatrixInverse );

		}

		var parent = camera.parent;

		if ( parent !== null ) {

			matrixWorldInverse.getInverse( parent.matrixWorld );

			cameraL.matrixWorldInverse.multiply( matrixWorldInverse );
			cameraR.matrixWorldInverse.multiply( matrixWorldInverse );

		}

		// envMap and Mirror needs camera.matrixWorld

		cameraL.matrixWorld.getInverse( cameraL.matrixWorldInverse );
		cameraR.matrixWorld.getInverse( cameraR.matrixWorldInverse );

		cameraL.projectionMatrix.fromArray( frameData.leftProjectionMatrix );
		cameraR.projectionMatrix.fromArray( frameData.rightProjectionMatrix );

		setProjectionFromUnion( cameraVR, cameraL, cameraR );

		//

		var layers = device.getLayers();

		if ( layers.length ) {

			var layer = layers[ 0 ];

			updateViewportFromBounds( cameraL.viewport, layer.leftBounds );
			updateViewportFromBounds( cameraR.viewport, layer.rightBounds );

		}

		updateControllers();

		return cameraVR;

	};

	this.getStandingMatrix = function () {

		return standingMatrix;

	};

	this.isPresenting = isPresenting;

	// Animation Loop

	var animation = new WebGLAnimation();

	this.setAnimationLoop = function ( callback ) {

		animation.setAnimationLoop( callback );

		if ( isPresenting() ) animation.start();

	};

	this.submitFrame = function () {

		if ( isPresenting() ) device.submitFrame();

	};

	this.dispose = function () {

		if ( typeof window !== 'undefined' ) {

			window.removeEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange );

		}

	};

	// DEPRECATED

	this.setFrameOfReferenceType = function () {

		console.warn( 'THREE.WebVRManager: setFrameOfReferenceType() has been deprecated.' );

	};

}

Object.assign( WebVRManager.prototype, EventDispatcher.prototype );

export { WebVRManager };
