import * as THREE from 'three';
import { OrbitControls } from './OrbitControls.js';
import { DeviceOrientationControls } from './DeviceOrientationControls.js';

/**
 * Convert a quaternion to an angle
 *
 * Taken from https://stackoverflow.com/a/35448946
 * Thanks P. Ellul
 */
function Quat2Angle(x, y, z, w) {
    const test = x * y + z * w;

    // singularity at north pole
    if (test > 0.499) {
        const yaw = 2 * Math.atan2(x, w);
        const pitch = Math.PI / 2;
        const roll = 0;

        return new THREE.Vector3(pitch, roll, yaw);
    }

    // singularity at south pole
    if (test < -0.499) {
        const yaw = -2 * Math.atan2(x, w);
        const pitch = -Math.PI / 2;
        const roll = 0;

        return new THREE.Vector3(pitch, roll, yaw);
    }

    const sqx = x * x;
    const sqy = y * y;
    const sqz = z * z;
    const yaw = Math.atan2(2 * y * w - 2 * x * z, 1 - 2 * sqy - 2 * sqz);
    const pitch = Math.asin(2 * test);
    const roll = Math.atan2(2 * x * w - 2 * y * z, 1 - 2 * sqx - 2 * sqz);

    return new THREE.Vector3(pitch, roll, yaw);
}

class OrbitOrientationControls {
    constructor(options) {
        this.object = options.camera;
        this.domElement = options.canvas;
        this.orbit = new OrbitControls(this.object, this.domElement);

        this.speed = 0.5;
        this.orbit.target.set(0, 1, -1);
        this.orbit.enableZoom = true;
        this.orbit.enablePan = false;
        this.orbit.rotateSpeed = -this.speed;

        // if orientation is supported
        if (options.orientation) {
            this.orientation = new DeviceOrientationControls(this.object);
        }

        // if projection is not full view
        // limit the rotation angle in order to not display back half view
        if (options.halfView) {
            this.orbit.minAzimuthAngle = -Math.PI / 4;
            this.orbit.maxAzimuthAngle = Math.PI / 4;
        }
    }

    update() {
        // orientation updates the camera using quaternions and
        // orbit updates the camera using angles. They are incompatible
        // and one update overrides the other. So before
        // orbit overrides orientation we convert our quaternion changes to
        // an angle change. Then save the angle into orbit so that
        // it will take those into account when it updates the camera and overrides
        // our changes
        if (this.orientation) {
            this.orientation.update();

            const quat = this.orientation.object.quaternion;
            const currentAngle = Quat2Angle(quat.x, quat.y, quat.z, quat.w);

            // we also have to store the last angle since quaternions are b
            if (typeof this.lastAngle_ === 'undefined') {
                this.lastAngle_ = currentAngle;
            }

            this.orbit.rotateLeft((this.lastAngle_.z - currentAngle.z) * (1 + this.speed));
            this.orbit.rotateUp((this.lastAngle_.y - currentAngle.y) * (1 + this.speed));
            this.lastAngle_ = currentAngle;
        }

        this.orbit.update();
    }

    dispose() {
        this.orbit.dispose();

        if (this.orientation) {
            this.orientation.dispose();
        }
    }

    enable() {
        this.orbit.enabled = true;

        if (this.orientation)
            this.orientation.enabled = true;
    }

    disable() {
        this.orbit.enabled = false;

        if (this.orientation)
            this.orientation.enabled = false;
    }

}

export default OrbitOrientationControls;
