//  sword.js
//  examples
//
//  Created by Seth Alves on 2015-6-10
//  Copyright 2015 High Fidelity, Inc.
//
//  Allow avatar to hold a sword
//
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
"use strict";
/*jslint vars: true*/
var Script, Entities, MyAvatar, Window, Overlays, Controller, Vec3, Quat, print, ToolBar, Settings; // Referenced globals provided by High Fidelity.
Script.include("http://s3.amazonaws.com/hifi-public/scripts/libraries/toolBars.js");
var zombieGameScriptURL = "https://hifi-public.s3.amazonaws.com/eric/scripts/zombieFight.js?v2";
// var zombieGameScriptURL = "zombieFight.js";
Script.include(zombieGameScriptURL);


var zombieFight = new ZombieFight();

var hand = "right";
var zombieFight;
var nullActionID = "00000000-0000-0000-0000-000000000000";
var controllerID;
var controllerActive;
var swordID = null;
var actionID = nullActionID;
var dimensions = {
    x: 0.3,
    y: 0.15,
    z: 2.0
};
var BUTTON_SIZE = 32;

var health = 100;
var healthLossOnHit = 10;

var swordModel = "https://hifi-public.s3.amazonaws.com/ozan/props/sword/sword.fbx";
// var swordCollisionShape = "https://hifi-public.s3.amazonaws.com/ozan/props/sword/sword.obj";
var swordCollisionShape = "https://hifi-public.s3.amazonaws.com/eric/models/noHandleSwordCollisionShape.obj?=v2";
var swordCollisionSoundURL = "http://public.highfidelity.io/sounds/Collisions-hitsandslaps/swordStrike1.wav";
var avatarCollisionSoundURL = "https://hifi-public.s3.amazonaws.com/eric/sounds/blankSound.wav"; //Just to avoid no collision callback bug
var zombieGameScriptURL = "https://hifi-public.s3.amazonaws.com/eric/scripts/zombieFight.js";
var whichModel = "sword";
var originalAvatarCollisionSound;

var avatarCollisionSounds = [SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/avatarHit.wav"), SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/avatarHit2.wav?=v2")];

var toolBar = new ToolBar(0, 0, ToolBar.vertical, "highfidelity.sword.toolbar", function() {
    return {
        x: 100,
        y: 380
    };
});

var gameStarted = false;

var SWORD_IMAGE = "https://hifi-public.s3.amazonaws.com/images/sword/sword.svg"; // Toggle between brandishing/sheathing sword (creating if necessary)
var TARGET_IMAGE = "https://hifi-public.s3.amazonaws.com/images/sword/dummy2.svg"; // Create a target dummy
var CLEANUP_IMAGE = "http://s3.amazonaws.com/hifi-public/images/delete.png"; // Remove sword and all target dummies.f
var swordButton = toolBar.addOverlay("image", {
    width: BUTTON_SIZE,
    height: BUTTON_SIZE,
    imageURL: SWORD_IMAGE,
    alpha: 1
});
var targetButton = toolBar.addOverlay("image", {
    width: BUTTON_SIZE,
    height: BUTTON_SIZE,
    imageURL: TARGET_IMAGE,
    alpha: 1
});

var cleanupButton = toolBar.addOverlay("image", {
    width: BUTTON_SIZE,
    height: BUTTON_SIZE,
    imageURL: CLEANUP_IMAGE,
    alpha: 1
});

var flasher;

var leftHandClick = 14;
var leftTriggerValue = 0;
var prevLeftTriggerValue = 0;


var LEFT = 0;
var RIGHT = 1;

var leftPalm = 2 * LEFT;
var rightPalm = 2 * RIGHT;
var rightHandClick = 15;
var prevRightTriggerValue = 0;
var rightTriggerValue = 0;
var TRIGGER_THRESHOLD = 0.2;

var swordHeld = false;

function clearFlash() {
    if (!flasher) {
        return;
    }
    Script.clearTimeout(flasher.timer);
    Overlays.deleteOverlay(flasher.overlay);
    flasher = null;
}

function flash(color) {
    return;
    clearFlash();
    flasher = {};
    flasher.overlay = Overlays.addOverlay("text", {
        backgroundColor: color,
        backgroundAlpha: 0.7,
        width: Window.innerWidth,
        height: Window.innerHeight
    });
    flasher.timer = Script.setTimeout(clearFlash, 500);
}


var display2d, display3d;

function trackAvatarWithText() {
    Entities.editEntity(display3d, {
        position: Vec3.sum(MyAvatar.position, {
            x: 0,
            y: 1.5,
            z: 0
        }),
        rotation: Quat.multiply(MyAvatar.orientation, Quat.fromPitchYawRollDegrees(0, 180, 0))
    });
}

function updateDisplay() {
    var text = health.toString();
    if (!display2d) {
        display2d = Overlays.addOverlay("text", {
            text: text,
            font: {
                size: 20
            },
            color: {
                red: 0,
                green: 255,
                blue: 0
            },
            backgroundColor: {
                red: 100,
                green: 100,
                blue: 100
            }, // Why doesn't this and the next work?
            backgroundAlpha: 0.9,
            x: toolBar.x - 5, // I'd like to add the score to the toolBar and have it drag with it, but toolBar doesn't support text (just buttons).
            y: toolBar.y - 30 // So next best thing is to position it each time as if it were on top.
        });
        display3d = Entities.addEntity({
            name: MyAvatar.displayName + " score",
            textColor: {
                red: 255,
                green: 255,
                blue: 255
            },
            type: "Text",
            text: text,
            lineHeight: 0.14,
            backgroundColor: {
                red: 64,
                green: 64,
                blue: 64
            },
            dimensions: {
                x: 0.3,
                y: 0.2,
                z: 0.01
            },
        });
        Script.update.connect(trackAvatarWithText);
    } else {
        Overlays.editOverlay(display2d, {
            text: text
        });
        Entities.editEntity(display3d, {
            text: text
        });
    }
}

function removeDisplay() {
    if (display2d) {
        Overlays.deleteOverlay(display2d);
        display2d = null;
        Script.update.disconnect(trackAvatarWithText);
        Entities.deleteEntity(display3d);
        display3d = null;
    }
}


function gotHit(collision) {
    Audio.playSound(avatarCollisionSounds[randInt(0, avatarCollisionSounds.length)], {
        position: MyAvatar.position,
        volume: 0.5
    });
    health -= healthLossOnHit;
    if (health <= 30) {
        Overlays.editOverlay(display2d, {
            color: {
                red: 200,
                green: 10,
                blue: 10
            }
        });
    }

    if (health <= 0 && zombieFight) {
        zombieFight.loseGame();
    }
    flash({
        red: 255,
        green: 0,
        blue: 0
    });
    updateDisplay();
}


function isFighting() {
    return swordID && (actionID !== nullActionID);
}


var inHand = false;


function isControllerActive() {
    // I don't think the hydra API provides any reliable way to know whether a particular controller is active. Ask for both.
    controllerActive = (Vec3.length(MyAvatar.leftHandPose.translation) > 0) || Vec3.length(MyAvatar.rightHandPose.translation) > 0;
    return controllerActive;
}


function removeSword() {
    if (swordID) {
        print('deleting action ' + actionID + ' and entity ' + swordID);
        Entities.deleteAction(swordID, actionID);
        Entities.deleteEntity(swordID);
        swordID = null;
        actionID = nullActionID;
        Controller.mouseMoveEvent.disconnect(mouseMoveEvent);
        MyAvatar.collisionWithEntity.disconnect(gotHit);
        // removeEventhHandler happens automatically when the entity is deleted.
    }
    inHand = false;
    if (originalAvatarCollisionSound !== undefined) {
        MyAvatar.collisionSoundURL = originalAvatarCollisionSound;
    }
    removeDisplay();
    swordHeld = false;
}

function cleanUp(leaveButtons) {
    if (!leaveButtons) {
        toolBar.cleanup();
    }
    removeSword();
    gameStarted = false;
    zombieFight.cleanup();
}

function makeSword() {
    var swordPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(5, Quat.getFront(MyAvatar.orientation)));
    var orientationAdjustment = Quat.fromPitchYawRollDegrees(90, 0, 0);

    swordID = Entities.addEntity({
        type: "Model",
        name: "sword",
        modelURL: swordModel,
        compoundShapeURL: swordCollisionShape,
        dimensions: dimensions,
        position: swordPosition,
        rotation: Quat.fromPitchYawRollDegrees(90, 0, 0),
        damping: 0.1,
        collisionSoundURL: swordCollisionSoundURL,
        restitution: 0.01,
        dynamic: true,
    });

    if (originalAvatarCollisionSound === undefined) {
        originalAvatarCollisionSound = MyAvatar.collisionSoundURL; // We won't get MyAvatar.collisionWithEntity unless there's a sound URL. (Bug.)
        SoundCache.getSound(avatarCollisionSoundURL); // Interface does not currently "preload" this? (Bug?)
    }

    if (!isControllerActive()) {
        grabSword("right");
    }
    MyAvatar.collisionSoundURL = avatarCollisionSoundURL;
    Controller.mouseMoveEvent.connect(mouseMoveEvent);
    MyAvatar.collisionWithEntity.connect(gotHit);
    updateDisplay();
}



function grabSword(hand) {
    if (!swordID) {
        print("Create a sword by clicking on sword icon!")
        return;
    }
    var handRotation;
    if (hand === "right") {
        handRotation = MyAvatar.rightHandPose.rotation;

    } else if (hand === "left") {
        handRotation = MyAvatar.leftHandPose.rotation;
    }
    var swordRotation = Entities.getEntityProperties(swordID).rotation;
    var offsetRotation = Quat.multiply(Quat.inverse(handRotation), swordRotation);
    actionID = Entities.addAction("hold", swordID, {
        relativePosition: {
            x: 0.0,
            y: 0.0,
            z: -dimensions.z * 0.5
        },
        relativeRotation: offsetRotation,
        hand: hand,
        timeScale: 0.05
    });
    if (actionID === nullActionID) {
        print('*** FAILED TO MAKE SWORD ACTION ***');
        cleanUp();
    } else {
        swordHeld = true;
    }
}

function releaseSword() {
    Entities.deleteAction(swordID, actionID);
    actionID = nullActionID;
    Entities.editEntity(swordID, {
        velocity: {
            x: 0,
            y: 0,
            z: 0
        },
        angularVelocity: {
            x: 0,
            y: 0,
            z: 0
        }
    });
    swordHeld = false;
}

function update() {
    updateControllerState();

}

function updateControllerState() {
    rightTriggerValue = Controller.getValue(Controller.Standard.RT);
    leftTriggerValue =Controller.getValue(Controller.Standard.LT);

    if (rightTriggerValue > TRIGGER_THRESHOLD && !swordHeld) {
        grabSword("right")
    } else if (rightTriggerValue < TRIGGER_THRESHOLD && prevRightTriggerValue > TRIGGER_THRESHOLD && swordHeld) {
        releaseSword();
    }

    if (leftTriggerValue > TRIGGER_THRESHOLD && !swordHeld) {
        grabSword("left")
    } else if (leftTriggerValue < TRIGGER_THRESHOLD && prevLeftTriggerValue > TRIGGER_THRESHOLD && swordHeld) {
        releaseSword();
    }

    prevRightTriggerValue = rightTriggerValue;
    prevLeftTriggerValue = leftTriggerValue;
}

randFloat = function(low, high) {
    return low + Math.random() * (high - low);
}


randInt = function(low, high) {
    return Math.floor(randFloat(low, high));
}

function positionSword(swordOrientation) {
    var reorient = Quat.fromPitchYawRollDegrees(0, -90, 0);
    var baseOffset = {
        x: -dimensions.z * 0.8,
        y: 0,
        z: 0
    };
    var offset = Vec3.multiplyQbyV(reorient, baseOffset);
    swordOrientation = Quat.multiply(reorient, swordOrientation);
    inHand = false;
    Entities.updateAction(swordID, actionID, {
        relativePosition: offset,
        relativeRotation: swordOrientation,
        hand: "right"
    });
}

function resetToHand() { // For use with controllers, puts the sword in contact with the hand.
    // Maybe coordinate with positionSword?
    if (inHand) { // Optimization: bail if we're already inHand.
        return;
    }
    print('Reset to hand');
    Entities.updateAction(swordID, actionID, {
        relativePosition: {
            x: 0.0,
            y: 0.0,
            z: -dimensions.z * 0.5
        },
        relativeRotation: Quat.fromVec3Degrees({
            x: 45.0,
            y: 0.0,
            z: 0.0
        }),
        hand: "right", // It should not be necessary to repeat these two, but there seems to be a bug in that that
        timeScale: 0.05 // they do not retain their earlier values if you don't repeat them.
    });
    inHand = true;
}

function mouseMoveEvent(event) {
    //When a controller like the hydra gives a mouse event, the x/y is not meaningful to us, but we can detect with a truty deviceID
    if (event.deviceID || !isFighting() || isControllerActive()) {
        resetToHand();
        return;
    }
    var windowCenterX = Window.innerWidth / 2;
    var windowCenterY = Window.innerHeight / 2;
    var mouseXCenterOffset = event.x - windowCenterX;
    var mouseYCenterOffset = event.y - windowCenterY;
    var mouseXRatio = mouseXCenterOffset / windowCenterX;
    var mouseYRatio = mouseYCenterOffset / windowCenterY;

    var swordOrientation = Quat.fromPitchYawRollDegrees(mouseYRatio * 90, mouseXRatio * 90, 0);
    positionSword(swordOrientation);
}


function onClick(event) {
    switch (Overlays.getOverlayAtPoint(event)) {
        case swordButton:
            if (!swordID) {
                makeSword();
            } else {
                removeSword();
            }
            break;
        case targetButton:
            if (gameStarted) {
                return;
            }
            Script.include("https://hifi-public.s3.amazonaws.com/eric/scripts/zombieFight.js");
            zombieFight = new ZombieFight();
            zombieFight.initiateZombieApocalypse();
            gameStarted = true;

            break;
        case cleanupButton:
            cleanUp('leaveButtons');
            break;
    }
}

Script.scriptEnding.connect(cleanUp);
Script.update.connect(update);
Controller.mousePressEvent.connect(onClick);