// designate component
"atomic component";
//import gl-matrix library
//https://github.com/toji/gl-matrix for more information
var glmatrix = require("gl-matrix");
var quat = glmatrix.quat;
var vec3 = glmatrix.vec3;

//define an inspectorFields to make variables visible in editor
var inspectorFields = {
    //needs default value to make editor understand type of that value
    speed: 1.0
};
//define a component AvatarController
exports.component = function (self) {

    //link to the current node
    var node = self.node;

    var animModel = node.getComponent("AnimatedModel");

    var cameraNode;

    var onGround = true;
    var okToJump = true;
    var inAirTime = 0;

    //define constants
    var MOVE_FORCE = 1.8;
    var INAIR_MOVE_FORCE = 0.02;
    var BRAKE_FORCE = 0.2;
    var JUMP_FORCE = 7.0;
    var YAW_SENSITIVITY = 0.1;
    var INAIR_THRESHOLD_TIME = 0.1;

    var cameraMode = 0;

    var yaw = 0;
    var pitch = 0;

    var moveForward = false;
    var moveBackwards = false;
    var moveLeft = false;
    var moveRight = false;
    var mouseMoveX = 0.0;
    var mouseMoveY = 0.0;
    var button0 = false;
    var button1 = false;

    var lastButton0 = false;
    var lastButton1 = false;

    self.idle = true;

    self.start = function () {
        //get main camera and set its node to cameraNode
        var camera = node.scene.getMainCamera();
        cameraNode = camera.node;

        // Create rigidbody, and set non-zero mass so that the body becomes dynamic
        var body = node.createComponent("RigidBody");
        body.mass = 1.0;

        // Set zero angular factor so that physics doesn't turn the character on its own.
        // Instead we will control the character yaw manually
        body.angularFactor = [0, 0, 0];

        // Set the rigidbody to signal collision also when in rest, so that we get ground collisions properly
        body.collisionEventMode = Atomic.CollisionEventMode.COLLISION_ALWAYS;

        // Set a capsule shape for collision
        var shape = node.createComponent("CollisionShape");
        shape.setCapsule(2, 4, [0, 2, 0]);

    };

    self.fixedUpdate = function (timestep) {

        //get a RigidBody component from the current node
        var body = node.getComponent("RigidBody");
        var inAirTimer = 0.0;

        // Update the in air timer. Reset if grounded
        if (!onGround)
            inAirTimer += timestep;
        else
            inAirTimer = 0.0;

        // When character has been in air less than 1/10 second, it's still interpreted as being on ground
        var softGrounded = inAirTimer < INAIR_THRESHOLD_TIME;

        // Get rotation of the current node
        var rot = node.getRotation();

        var moveDir = [0, 0, 0];

        // Update movement & animation
        var velocity = body.getLinearVelocity();

        // Velocity on the XZ plane
        var planeVelocity = [velocity[0], 0.0, velocity[2]];

        if (cameraMode != 2) {
            if (moveForward) {
                vec3.add(moveDir, moveDir, [0, 0, -1]);
            }
            if (moveBackwards) {
                vec3.add(moveDir, moveDir, [0, 0, 1]);
            }
            if (moveLeft) {
                vec3.add(moveDir, moveDir, [1, 0, 0]);
            }
            if (moveRight) {
                vec3.add(moveDir, moveDir, [-1, 0, 0]);
            }
        }

        if (vec3.length(moveDir) > 0.0)
            vec3.normalize(moveDir, moveDir);

        vec3.transformQuat(moveDir, moveDir, [rot[1], rot[2], rot[3], rot[0]]);
        vec3.scale(moveDir, moveDir, (softGrounded ? MOVE_FORCE : INAIR_MOVE_FORCE));

        if (softGrounded)
            vec3.scale(moveDir, moveDir, self.speed);

        body.applyImpulse(moveDir);

        if (softGrounded) {

            // When on ground, apply a braking force to limit maximum ground velocity
            vec3.negate(planeVelocity, planeVelocity);
            vec3.scale(planeVelocity, planeVelocity, BRAKE_FORCE);
            body.applyImpulse(planeVelocity);

            // Jump. Must release jump control inbetween jumps
            if (button1) {
                if (okToJump) {
                    var jumpforce = [0, 1, 0];
                    vec3.scale(jumpforce, jumpforce, JUMP_FORCE);
                    //Apply impulse to the body
                    body.applyImpulse(jumpforce);
                    okToJump = false;
                }
            } else {
                okToJump = true;
            }
        }


        if (softGrounded && vec3.length(moveDir) > 0.0)
            self.idle = false;
        else
            self.idle = true;


        // Reset grounded flag for next frame
        onGround = true;

    };

    function MoveCamera(timeStep) {

        // Movement speed as world units per second
        var MOVE_SPEED = 15.0;
        // Mouse sensitivity as degrees per pixel
        var MOUSE_SENSITIVITY = 0.1;

        yaw = yaw + MOUSE_SENSITIVITY * mouseMoveX;
        pitch = pitch + MOUSE_SENSITIVITY * mouseMoveY;

        if (pitch < -90)
            pitch = -90;

        if (pitch > 90)
            pitch = 90;

        // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero
        cameraNode.rotation = QuatFromEuler(pitch, yaw, 0.0);

        var speed = MOVE_SPEED * timeStep;
        
        if (Atomic.input.getKeyDown(Atomic.KEY_LSHIFT))
            speed *= 2.0;


        //translate camera on the amount of speed value
        if (moveForward)
            cameraNode.translate([0.0, 0.0, speed]);
        if (moveBackwards)
            cameraNode.translate([0.0, 0.0, -speed]);
        if (moveLeft)
            cameraNode.translate([-speed, 0.0, 0.0]);
        if (moveRight)
            cameraNode.translate([speed, 0.0, 0.0]);

    }

    function UpdateControls() {

        var input = Atomic.input;

        moveForward = false;
        moveBackwards = false;
        moveLeft = false;
        moveRight = false;
        mouseMoveX = 0.0;
        mouseMoveY = 0.0;
        button0 = false;
        button1 = false;

        // Movement speed as world units per second
        var MOVE_SPEED = 20.0;
        // Mouse sensitivity as degrees per pixel
        var MOUSE_SENSITIVITY = 0.1;

        //check input
        if (input.getKeyDown(Atomic.KEY_W) || input.getKeyDown(Atomic.KEY_UP))
            moveForward = true;
        if (input.getKeyDown(Atomic.KEY_S) || input.getKeyDown(Atomic.KEY_DOWN))
            moveBackwards = true;
        if (input.getKeyDown(Atomic.KEY_A) || input.getKeyDown(Atomic.KEY_LEFT))
            moveLeft = true;
        if (input.getKeyDown(Atomic.KEY_D) || input.getKeyDown(Atomic.KEY_RIGHT))
            moveRight = true;

        if (input.getKeyPress(Atomic.KEY_F))
            button0 = true;
        if (input.getKeyPress(Atomic.KEY_SPACE))
            button1 = true;

        //if we are on mobile
        if (Atomic.platform == "Android" || Atomic.platform == "iOS") {
            //iterate through each TouchState, if it doesn't touch any widgets, use it as a `mouse`
            for (var i = 0; i < Atomic.input.getNumTouches(); i++) {
                var touchState = Atomic.input.getTouch(i);
                if (touchState.touchedWidget == null) {
                    var delta = touchState.delta;
                    mouseMoveX = delta[0];
                    mouseMoveY = delta[1];
                }
            }
            //if its a desktop
        } else {
            // update mouse coordinates
            mouseMoveX = input.getMouseMoveX();
            mouseMoveY = input.getMouseMoveY();
        }

    }

    self.update = function (timeStep) {

        UpdateControls();

        //if it's a free view
        if (cameraMode != 2) {
            yaw += mouseMoveX * YAW_SENSITIVITY;
            pitch += mouseMoveY * YAW_SENSITIVITY;
        }

        if (pitch < -80)
            pitch = -80;
        if (pitch > 80)
            pitch = 80;

        if (button0) {
            cameraMode++;
            if (cameraMode == 3)
                cameraMode = 0;
            if (cameraMode == 1)
                animModel.enabled = false;
            else
                animModel.enabled = true;
        }

    };

    //that function called right after update function
    self.postUpdate = function (timestep) {

        // Get camera lookat dir from character yaw + pitch
        var rot = node.getRotation();

        //create quaternion
        var dir = quat.create();

        quat.setAxisAngle(dir, [1, 0, 0], (-pitch * Math.PI / 180.0));
        quat.rotateY(dir, dir, 3.14);
        quat.multiply(dir, [rot[1], rot[2], rot[3], rot[0]], dir);

        var headNode = node.getChild("Head_Tip", true);

        //if it's a FPS view
        if (cameraMode == 1) {

            var headPos = headNode.getWorldPosition();
            var offset = [0.0, 0.15, 0.2];
            vec3.add(headPos, headPos, vec3.transformQuat(offset, offset, [rot[1], rot[2], rot[3], rot[0]]));
            cameraNode.setPosition(headPos);
            cameraNode.setRotation([dir[3], dir[0], dir[1], dir[2]]);
            quat.setAxisAngle(dir, [0, 1, 0], (yaw * Math.PI / 180.0));
            quat.rotateY(dir, dir, 3.14);
            node.setRotation([dir[3], dir[0], dir[1], dir[2]]);

        }
        //if it's a third person view
        if (cameraMode == 0) {

            var aimPoint = node.getWorldPosition();
            var aimOffset = [0, 1.7, 0];
            vec3.transformQuat(aimOffset, aimOffset, dir);
            vec3.add(aimPoint, aimPoint, aimOffset);

            var rayDir = vec3.create();
            vec3.transformQuat(rayDir, [0, 0, -1], dir);
            vec3.scale(rayDir, rayDir, 8);

            vec3.add(aimPoint, aimPoint, rayDir);

            cameraNode.setPosition(aimPoint);
            cameraNode.setRotation([dir[3], dir[0], dir[1], dir[2]]);
            quat.setAxisAngle(dir, [0, 1, 0], (yaw * Math.PI / 180.0));
            node.setRotation([dir[3], dir[0], dir[1], dir[2]]);

        }
        else {
            MoveCamera(timestep);
        }
    };
};

function QuatFromEuler(x, y, z) {
    var M_PI = 3.14159265358979323846264338327950288;
    var q = [0, 0, 0, 0];
    x *= (M_PI / 360);
    y *= (M_PI / 360);
    z *= (M_PI / 360);
    var sinX = Math.sin(x);
    var cosX = Math.cos(x);
    var sinY = Math.sin(y);
    var cosY = Math.cos(y);
    var sinZ = Math.sin(z);
    var cosZ = Math.cos(z);
    q[0] = cosY * cosX * cosZ + sinY * sinX * sinZ;
    q[1] = cosY * sinX * cosZ + sinY * cosX * sinZ;
    q[2] = sinY * cosX * cosZ - cosY * sinX * sinZ;
    q[3] = cosY * cosX * sinZ - sinY * sinX * cosZ;
    return q;
}
