//
//  Created by Luis Cuenca on 1/31/18
//  Copyright 2018 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
//

/* jslint bitwise: true */

/* global Script
*/

(function(){
    var Vector3 = new (function() {
        var self = this;
        this.EPSILON = 0.000001;
        this.EPSILON_SQUARED = self.EPSILON * self.EPSILON;
        this.PI = 3.14159265358979;
        this.ALMOST_ONE= 1.0 - self.EPSILON;
        this.PI_OVER_TWO = 1.57079632679490;
        
        this.cross = function(A, B) {
            return {x: (A.y * B.z - A.z * B.y), y: (A.z * B.x - A.x * B.z), z: (A.x * B.y - A.y * B.x)};
        };
        this.distance = function(A, B) {
            return Math.sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y) + (A.z - B.z) * (A.z - B.z));
        };
        this.dot = function(A, B) {
            return A.x * B.x + A.y * B.y + A.z * B.z;
        };
        this.length = function(V) {
            return Math.sqrt(V.x * V.x + V.y * V.y + V.z * V.z);
        };
        this.subtract = function(A, B) {
            return {x: (A.x - B.x), y: (A.y - B.y), z: (A.z - B.z)};
        };
        this.sum = function(A, B) {
            return {x: (A.x + B.x), y: (A.y + B.y), z: (A.z + B.z)};
        };
        this.multiply = function(V, scale) {
            return {x: scale * V.x, y: scale * V.y, z: scale * V.z};
        };
        this.normalize = function(V) {
            var L2 = V.x*V.x + V.y*V.y + V.z*V.z;
            if (L2 < self.EPSILON_SQUARED) {
                return {x: V.x, y: V.y, z: V.z};
            }
            var invL = 1.0/Math.sqrt(L2);
            return {x: invL * V.x, y: invL * V.y, z: invL * V.z}; 
        };
        this.multiplyQbyV = function(Q,V) {
            var num = Q.x * 2.0;
            var num2 = Q.y * 2.0;
            var num3 = Q.z * 2.0;
            var num4 = Q.x * num;
            var num5 = Q.y * num2;
            var num6 = Q.z * num3;
            var num7 = Q.x * num2;
            var num8 = Q.x * num3;
            var num9 = Q.y * num3;
            var num10 = Q.w * num;
            var num11 = Q.w * num2;
            var num12 = Q.w * num3;
            var result = {x: 0, y: 0, z: 0};
            result.x = (1.0 - (num5 + num6)) * V.x + (num7 - num12) * V.y + (num8 + num11) * V.z;
            result.y = (num7 + num12) * V.x + (1.0 - (num4 + num6)) * V.y + (num9 - num10) * V.z;
            result.z = (num8 - num11) * V.x + (num9 + num10) * V.y + (1.0 - (num4 + num5)) * V.z;
            return result;
        };  
    })();

    var Quaternion = new (function() {
        var self = this;
        
        this.IDENTITY = function() {
            return {x:0, y:0, z:0, w:1};
        };
        
        this.dot = function(A, B) {
            return A.x * B.x + A.y * B.y + A.z * B.z + A.w * B.w;
        };
        
        this.multiply = function(Q, R) {
            // from this page:
            // http://mathworld.wolfram.com/Quaternion.html
            return {
                w: Q.w * R.w - Q.x * R.x - Q.y * R.y - Q.z * R.z,
                x: Q.w * R.x + Q.x * R.w + Q.y * R.z - Q.z * R.y,
                y: Q.w * R.y - Q.x * R.z + Q.y * R.w + Q.z * R.x,
                z: Q.w * R.z + Q.x * R.y - Q.y * R.x + Q.z * R.w};
        };

        this.angleAxis = function(angle, axis) {
            var s = Math.sin(0.5 * angle);
            return {w: Math.cos(0.5 * angle),x: s * axis.x, y: s * axis.y, z: s * axis.z};
        };
        
        this.inverse = function(Q) {
            return {w: -Q.w, x: Q.x, y: Q.y, z: Q.z};
        };
        
        this.rotationBetween = function(orig, dest) {
            var v1 = Vector3.normalize(orig);
            var v2 = Vector3.normalize(dest);
            var cosTheta = Vector3.dot(v1, v2);
            var rotationAxis;
            if(cosTheta >= 1 - Vector3.EPSILON){
                return self.IDENTITY();
            }

            if(cosTheta < -1 + Vector3.EPSILON)
            {
                // special case when vectors in opposite directions :
                // there is no "ideal" rotation axis
                // So guess one; any will do as long as it's perpendicular to start
                // This implementation favors a rotation around the Up axis (Y),
                // since it's often what you want to do.
                rotationAxis = Vector3.cross({x: 0, y: 0, z: 1}, v1);
                if(Vector3.length(rotationAxis) < Vector3.EPSILON) { // bad luck, they were parallel, try again!
                    rotationAxis = Vector3.cross({x:1, y:0, z:0}, v1);
                }
                rotationAxis = Vector3.normalize(rotationAxis);
                return self.angleAxis(Vector3.PI, rotationAxis);
            }
            // Implementation from Stan Melax's Game Programming Gems 1 article
            rotationAxis = Vector3.cross(v1, v2);

            var s = Math.sqrt((1 + cosTheta) * 2);
            var invs = 1 / s;

            return {
                w: s * 0.5,
                x: rotationAxis.x * invs, 
                y: rotationAxis.y * invs,
                z: rotationAxis.z * invs,
            }
        }
    })();
    Script.registerValue("VEC3", Vector3);
    Script.registerValue("QUAT", Quaternion);
})();