import { Vector3, Euler } from 'three';
import { EventManager } from './EventManager';

const _MOUSE_SENSITIVITY = 0.002;

const _euler = new Euler( 0, 0, 0, 'YXZ' );

class PointerControls extends EventManager {
    constructor(engine) {
        super();
        this.kb = {};
        this.dom = engine.renderer.domElement;

        this.canJump = false;
        this.velocity = new Vector3();
        this.direction = new Vector3();
        this.rotationY = 0;
        this.vertex = new Vector3();
        this.rvelo = 0;

        this.engine = engine;
        this.click = false;

        this.isLocked = false;
		this.minPolarAngle = 0;
		this.maxPolarAngle = Math.PI;
		this.pointerSpeed = 1.0;

        this._onMouseMove = onMouseMove.bind( this );
        this._onMouseWheel = onMouseWheel.bind( this );
		this._onPointerlockChange = onPointerlockChange.bind( this );
		this._onPointerlockError = onPointerlockError.bind( this );

        this.onKeyDown = (event) => {
            this.kb[event.code] = true;
            switch (event.code) {
                case 'Space':
                    if (this.canJump === true) this.velocity.y += 350;
                    this.canJump = false;
                    break;
            }
        };

        this.onKeyUp = (event) => {
            this.kb[event.code] = false;
        };

        this.fnClick = () => {
            this.isLocked && this.clicked && this.clicked();
        }

        this.fnMouseDown = () => {
            this.isLocked && this.onpointer && this.onpointer('start');
        }

        this.fnMouseMove = () => {
            this.isLocked && this.onpointer && this.onpointer('drag');
        }

        this.fnMouseUp = () => {
            this.isLocked && this.onpointer && this.onpointer('end');
        }

        this.update = () => { };

        this.object = engine.cameraWorld;

        this.dom.ownerDocument.addEventListener('keydown', this.onKeyDown);
        this.dom.ownerDocument.addEventListener('keyup', this.onKeyUp);
        this.dom.addEventListener('click', this.fnClick);
        this.dom.addEventListener('mousedown', this.fnMouseDown);
        this.dom.addEventListener('mousemove', this.fnMouseMove);
        this.dom.addEventListener('mouseup', this.fnMouseUp);
		this.dom.ownerDocument.addEventListener( 'wheel', this._onMouseWheel );
		this.dom.ownerDocument.addEventListener( 'mousemove', this._onMouseMove );
		this.dom.ownerDocument.addEventListener( 'pointerlockchange', this._onPointerlockChange );
		this.dom.ownerDocument.addEventListener( 'pointerlockerror', this._onPointerlockError );
    }

	dispose() {
		this.dom.ownerDocument.removeEventListener( 'keydown', this.onKeyDown );
		this.dom.ownerDocument.removeEventListener( 'keyup', this.onKeyUp );
        this.dom.removeEventListener('click', this.fnClick);
        this.dom.removeEventListener('mousedown', this.fnMouseDown);
        this.dom.removeEventListener('mousemove', this.fnMouseMove);
        this.dom.removeEventListener('mouseup', this.fnMouseUp);
		this.dom.ownerDocument.removeEventListener( 'wheel', this._onMouseWheel );
		this.dom.ownerDocument.removeEventListener( 'mousemove', this._onMouseMove );
		this.dom.ownerDocument.removeEventListener( 'pointerlockchange', this._onPointerlockChange );
		this.dom.ownerDocument.removeEventListener( 'pointerlockerror', this._onPointerlockError );
	}

    get moveForward(){
        return this.kb['ArrowUp'] || this.kb['KeyW'] || this.engine.xrController1?.gamepad?.axes[3] < -0.5 || false
    }

    get moveBackward(){
        return this.kb['ArrowDown'] || this.kb['KeyS'] || this.engine.xrController1?.gamepad?.axes[3] > 0.5 || false
    }

    get moveLeft(){
        return this.kb['ArrowLeft'] || this.kb['KeyA'] || this.engine.xrController1?.gamepad?.axes[2] < -0.5 || false
    }

    get moveRight(){
        return this.kb['ArrowRight'] || this.kb['KeyD'] || this.engine.xrController1?.gamepad?.axes[2] > 0.5 || false
    }

    get moveAway(){
        return this.kb.KeyT || 
        (this.engine.xrController2?.gamepad?.axes[3] < -0.5 && this.engine.xrController1?.gamepad?.buttons[5]?.pressed) 
        || false
    }

    get moveCloser(){
        return this.kb.KeyG || 
        (this.engine.xrController2?.gamepad?.axes[3] > 0.5 && this.engine.xrController1?.gamepad?.buttons[5]?.pressed )
        || false
    }

    get rotateLeft(){
        return this.moveLeft;
    }

    get rotateRight(){
        return this.moveRight;
    }

    get moveUp(){
        return this.kb['KeyR'] || false
    }

    get moveDown(){
        return this.kb['KeyF'] || false
    }

    get cameraLeft(){
        return this.kb['KeyQ'] || this.engine.xrController2?.gamepad?.axes[2] > 0.5 || false
    }

    get cameraRight(){
        return this.kb['KeyE'] || this.engine.xrController2?.gamepad?.axes[2] < -0.5 || false
    }

    get cameraUp(){
        return this.kb.KeyR || 
        (this.engine.xrController2?.gamepad?.axes[3] < -0.5 && this.engine.xrController1?.gamepad?.buttons[4]?.pressed) 
        || false
    }

    get cameraDown(){
        return this.kb.KeyF || 
        (this.engine.xrController2?.gamepad?.axes[3] > 0.5 && this.engine.xrController1?.gamepad?.buttons[4]?.pressed) 
        || false
    }

    get moving(){
        return this.moveForward || this.moveBackward || this.moveLeft || this.moveRight;
    }

    get rotating(){
        return this.rotateLeft || this.rotateRight;
    }

    get motion(){
        return this.moving || this.rotating;
    }

    get running(){
        return this.moving && (this.kb.ShiftLeft || this.engine.xrController2?.gamepad?.buttons[1]?.pressed);
    }

    get input() {
        return [
            this.moveLeft * 1 + this.moveRight * -1, 
            this.moveForward * 1 + this.moveBackward * -1
        ]
    }

    async lock(unadjustedMovement = false){
        await this.dom.requestPointerLock( {
			unadjustedMovement
		} );
    }

    unlock(){
        this.dom.ownerDocument.exitPointerLock();
    }
}

export { PointerControls };

function onMouseMove( event ) {
	if ( this.enabled === false || this.isLocked === false ) return;
	const camera = this.object;
	_euler.setFromQuaternion( camera.quaternion );
	_euler.y -= event.movementX * _MOUSE_SENSITIVITY * this.pointerSpeed;
	_euler.x -= -event.movementY * _MOUSE_SENSITIVITY * this.pointerSpeed;
	_euler.x = Math.max( Math.PI / 2 - this.maxPolarAngle, Math.min( Math.PI / 2 - this.minPolarAngle, _euler.x ) );
	camera.quaternion.setFromEuler( _euler )
	this.dispatchEvent( { type: 'change' } );
}

function onMouseWheel( event ){
    if (this.isLocked){
        this.engine.camera.fov += event.deltaY / 100;
        this.engine.camera.fov = Math.min(Math.max(this.engine.camera.fov, 0.01), this.engine.defaultCameraFov);
        this.engine.camera.updateProjectionMatrix();
        if (this.engine.camera.fov >= this.engine.defaultCameraFov){
            this.unlock();
        }
        this.engine.dashboard?.orient();
    }
}

function onPointerlockChange() {
	if ( this.dom.ownerDocument.pointerLockElement === this.dom ) {
		this.isLocked = true;
		this.dispatchEvent( { type: 'lock' } );
	} else {
		this.isLocked = false;
		this.dispatchEvent( { type: 'unlock' } );
	}
    this.engine.camera.fov = this.engine.defaultCameraFov;
    this.engine.camera.updateProjectionMatrix();
    this.engine.dashboard?.orient();
    //this.engine.cameraRig.rotation.y = this.isLocked ? 0 : Math.PI;
}

function onPointerlockError() {
	console.error( 'Unable to use Pointer Lock API' );
}