# lib/controls/DeviceOrientationControls.js

    import * as THREE from 'three';

    /**
     * @classdesc Device Orientation Control
     * @constructor
     * @external DeviceOrientationControls
     * @param {THREE.Camera} camera
     * @param {HTMLElement} domElement
     */
    function DeviceOrientationControls ( camera, domElement ) {

        var scope = this;
        var changeEvent = { type: 'change' };

        var rotY = 0;
        var rotX = 0;
        var tempX = 0;
        var tempY = 0;

        this.camera = camera;
        this.camera.rotation.reorder( 'YXZ' );
        this.domElement = ( domElement !== undefined ) ? domElement : document;

        this.enabled = true;

        this.deviceOrientation = {};
        this.screenOrientation = 0;

        this.alpha = 0;
        this.alphaOffsetAngle = 0;

        var onDeviceOrientationChangeEvent = function( event ) {

            scope.deviceOrientation = event;

        };

        var onScreenOrientationChangeEvent = function() {

            scope.screenOrientation = window.orientation || 0;

        };

        var onTouchStartEvent = function (event) {

            event.preventDefault();
            event.stopPropagation();

            tempX = event.touches[ 0 ].pageX;
            tempY = event.touches[ 0 ].pageY;

        };

        var onTouchMoveEvent = function (event) {

            event.preventDefault();
            event.stopPropagation();

            rotY += THREE.Math.degToRad( ( event.touches[ 0 ].pageX - tempX ) / 4 );
            rotX += THREE.Math.degToRad( ( tempY - event.touches[ 0 ].pageY ) / 4 );

            scope.updateAlphaOffsetAngle( rotY );

            tempX = event.touches[ 0 ].pageX;
            tempY = event.touches[ 0 ].pageY;

        };

        // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y''

        var setCameraQuaternion = function( quaternion, alpha, beta, gamma, orient ) {

            var zee = new THREE.Vector3( 0, 0, 1 );

            var euler = new THREE.Euler();

            var q0 = new THREE.Quaternion();

            var q1 = new THREE.Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis

            var vectorFingerY;
            var fingerQY = new THREE.Quaternion();
            var fingerQX = new THREE.Quaternion();

            if ( scope.screenOrientation == 0 ) {

                vectorFingerY = new THREE.Vector3( 1, 0, 0 );
                fingerQY.setFromAxisAngle( vectorFingerY, -rotX );

            } else if ( scope.screenOrientation == 180 ) {

                vectorFingerY = new THREE.Vector3( 1, 0, 0 );
                fingerQY.setFromAxisAngle( vectorFingerY, rotX );

            } else if ( scope.screenOrientation == 90 ) {

                vectorFingerY = new THREE.Vector3( 0, 1, 0 );
                fingerQY.setFromAxisAngle( vectorFingerY, rotX );

            } else if ( scope.screenOrientation == - 90) {

                vectorFingerY = new THREE.Vector3( 0, 1, 0 );
                fingerQY.setFromAxisAngle( vectorFingerY, -rotX );

            }

            q1.multiply( fingerQY );
            q1.multiply( fingerQX );

            euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us

            quaternion.setFromEuler( euler ); // orient the device

            quaternion.multiply( q1 ); // camera looks out the back of the device, not the top

            quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation

        };

        this.connect = function() {

            onScreenOrientationChangeEvent(); // run once on load

            window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent, { passive: true } );
            window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, { passive: true } );
            window.addEventListener( 'deviceorientation', this.update.bind( this ), { passive: true } );

            scope.domElement.addEventListener( 'touchstart', onTouchStartEvent, { passive: false } );
            scope.domElement.addEventListener( 'touchmove', onTouchMoveEvent, { passive: false } );

            scope.enabled = true;

        };

        this.disconnect = function() {

            window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent, false );
            window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false );
            window.removeEventListener( 'deviceorientation', this.update.bind( this ), false );

            scope.domElement.removeEventListener( 'touchstart', onTouchStartEvent, false );
            scope.domElement.removeEventListener( 'touchmove', onTouchMoveEvent, false );

            scope.enabled = false;

        };

        this.update = function( ignoreUpdate ) {

            if ( scope.enabled === false ) return;

            var alpha = scope.deviceOrientation.alpha ? THREE.Math.degToRad( scope.deviceOrientation.alpha ) + scope.alphaOffsetAngle : 0; // Z
            var beta = scope.deviceOrientation.beta ? THREE.Math.degToRad( scope.deviceOrientation.beta ) : 0; // X'
            var gamma = scope.deviceOrientation.gamma ? THREE.Math.degToRad( scope.deviceOrientation.gamma ) : 0; // Y''
            var orient = scope.screenOrientation ? THREE.Math.degToRad( scope.screenOrientation ) : 0; // O

            setCameraQuaternion( scope.camera.quaternion, alpha, beta, gamma, orient );
            scope.alpha = alpha;

            if ( ignoreUpdate !== true ) { scope.dispatchEvent( changeEvent ); }

        };

        this.updateAlphaOffsetAngle = function( angle ) {

            this.alphaOffsetAngle = angle;
            this.update();

        };

        this.dispose = function() {

            this.disconnect();

        };

        this.connect();

    };

    DeviceOrientationControls.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype), {

        constructor: DeviceOrientationControls

    } );

    export { DeviceOrientationControls };