//
//  handControllerMouse.js
//  examples/controllers
//
//  Created by Brad Hefta-Gaub on 2015/12/15
//  Copyright 2015 High Fidelity, Inc.
//
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//

var DEBUGGING = false;
var angularVelocityTrailingAverage = 0.0;  //  Global trailing average used to decide whether to move reticle at all
var lastX = 0;
var lastY = 0;

Math.clamp=function(a,b,c) {
    return Math.max(b,Math.min(c,a));
}

function length(posA, posB) {
    var dx = posA.x - posB.x;
    var dy = posA.y - posB.y;
    var length = Math.sqrt((dx*dx) + (dy*dy))
    return length;
}

function moveReticleAbsolute(x, y) {
    var globalPos = Reticle.getPosition();
    globalPos.x = x;
    globalPos.y = y;
    Reticle.setPosition(globalPos);
}

var MAPPING_NAME = "com.highfidelity.testing.reticleWithHandRotation";
var mapping = Controller.newMapping(MAPPING_NAME);
if (Controller.Hardware.Hydra !== undefined) {
    mapping.from(Controller.Hardware.Hydra.L3).peek().to(Controller.Actions.ReticleClick);
    mapping.from(Controller.Hardware.Hydra.R4).peek().to(Controller.Actions.ReticleClick);
}
if (Controller.Hardware.Vive !== undefined) {
    mapping.from(Controller.Hardware.Vive.LeftPrimaryThumb).peek().to(Controller.Actions.ReticleClick);
    mapping.from(Controller.Hardware.Vive.RightPrimaryThumb).peek().to(Controller.Actions.ReticleClick);
}

mapping.enable();

function debugPrint(message) {
    if (DEBUGGING) {
        print(message);
    }
}

var leftRightBias = 0.0;
var filteredRotatedLeft = Vec3.UNIT_NEG_Y;
var filteredRotatedRight = Vec3.UNIT_NEG_Y;
var lastAlpha = 0;

Script.update.connect(function(deltaTime) {

    // avatar frame
    var poseRight = Controller.getPoseValue(Controller.Standard.RightHand);
    var poseLeft = Controller.getPoseValue(Controller.Standard.LeftHand);

    // NOTE: hack for now
    var screenSize = Reticle.maximumPosition;
    var screenSizeX = screenSize.x;
    var screenSizeY = screenSize.y;

    // transform hand facing vectors from avatar frame into sensor frame.
    var worldToSensorMatrix = Mat4.inverse(MyAvatar.sensorToWorldMatrix);
    var rotatedRight = Mat4.transformVector(worldToSensorMatrix, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.multiplyQbyV(poseRight.rotation, Vec3.UNIT_NEG_Y)));
    var rotatedLeft = Mat4.transformVector(worldToSensorMatrix, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.multiplyQbyV(poseLeft.rotation, Vec3.UNIT_NEG_Y)));

    lastRotatedRight = rotatedRight;

    // Decide which hand should be controlling the pointer
    // by comparing which one is moving more, and by
    // tending to stay with the one moving more.
    if (deltaTime > 0.001) {
        // leftRightBias is a running average of the difference in angular hand speed.
        // a positive leftRightBias indicates the right hand is spinning faster then the left hand.
        // a negative leftRightBias indicates the left hand is spnning faster.
        var BIAS_ADJUST_PERIOD = 1.0;
        var tau = Math.clamp(deltaTime / BIAS_ADJUST_PERIOD, 0, 1);
        newLeftRightBias = Vec3.length(poseRight.angularVelocity) - Vec3.length(poseLeft.angularVelocity);
        leftRightBias = (1 - tau) * leftRightBias + tau * newLeftRightBias;
    }

    // add a bit of hysteresis to prevent control flopping back and forth
    // between hands when they are both mostly stationary.
    var alpha;
    var HYSTERESIS_OFFSET = 0.25;
    if (lastAlpha > 0.5) {
        // prefer right hand over left
        alpha = leftRightBias > -HYSTERESIS_OFFSET ? 1 : 0;
    } else {
        alpha = leftRightBias > HYSTERESIS_OFFSET ? 1 : 0;
    }
    lastAlpha = alpha;

    // Velocity filter the hand rotation used to position reticle so that it is easier to target small things with the hand controllers
    var VELOCITY_FILTER_GAIN = 0.5;
    filteredRotatedLeft = Vec3.mix(filteredRotatedLeft, rotatedLeft, Math.clamp(Vec3.length(poseLeft.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0));
    filteredRotatedRight = Vec3.mix(filteredRotatedRight, rotatedRight, Math.clamp(Vec3.length(poseRight.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0));
    var rotated = Vec3.mix(filteredRotatedLeft, filteredRotatedRight, alpha);

    var absolutePitch = rotated.y; // from 1 down to -1 up ... but note: if you rotate down "too far" it starts to go up again...
    var absoluteYaw = -rotated.x; // from -1 left to 1 right

    var x = Math.clamp(screenSizeX * (absoluteYaw + 0.5), 0, screenSizeX);
    var y = Math.clamp(screenSizeX * absolutePitch, 0, screenSizeY);

    // don't move the reticle with the hand controllers unless the controllers are actually being moved
    //  take a time average of angular velocity, and don't move mouse at all if it's below threshold

    var AVERAGING_INTERVAL = 0.95;
    var MINIMUM_CONTROLLER_ANGULAR_VELOCITY = 0.03;
    var angularVelocityMagnitude = Vec3.length(poseLeft.angularVelocity) * (1.0 - alpha) + Vec3.length(poseRight.angularVelocity) * alpha;
    angularVelocityTrailingAverage = angularVelocityTrailingAverage * AVERAGING_INTERVAL + angularVelocityMagnitude * (1.0 - AVERAGING_INTERVAL);

    if ((angularVelocityTrailingAverage > MINIMUM_CONTROLLER_ANGULAR_VELOCITY) && ((x != lastX) || (y != lastY))) {
        moveReticleAbsolute(x, y);
        lastX = x;
        lastY = y;
    }
});

Script.scriptEnding.connect(function(){
    mapping.disable();
});