//
//  frisbee.js
//  examples
//
//  Created by Thijs Wenker on 7/5/14.
//  Copyright 2014 High Fidelity, Inc.
//  
//  Requirements: Razer Hydra's
//
//  Fun game to throw frisbee's to eachother. Hold the trigger on any of the hydra's to create or catch a frisbee.
//
//  Tip: use this together with the squeezeHands.js script to make it look nicer.
//
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//

Script.include("libraries/globals.js");
Script.include("libraries/toolBars.js");

const LEFT_PALM = 0;
const LEFT_TIP = 1;
const LEFT_BUTTON_FWD = 5;
const LEFT_BUTTON_3 = 3;

const RIGHT_PALM = 2;
const RIGHT_TIP = 3;
const RIGHT_BUTTON_FWD = 11;
const RIGHT_BUTTON_3 = 9;

const FRISBEE_RADIUS = 0.08;
const GRAVITY_STRENGTH = 0.5;

const CATCH_RADIUS = 0.5;
const MIN_SIMULATION_SPEED = 0.15;
const THROWN_VELOCITY_SCALING = 1.5;

const SOUNDS_ENABLED = true;
const FRISBEE_BUTTON_URL = "http://test.thoys.nl/hifi/images/frisbee/frisbee_button_by_Judas.svg";
const FRISBEE_MODEL_SCALE = 275;
const FRISBEE_MENU = "Toys>Frisbee";
const FRISBEE_DESIGN_MENU = "Toys>Frisbee>Design";
const FRISBEE_ENABLED_SETTING = "Frisbee>Enabled";
const FRISBEE_CREATENEW_SETTING = "Frisbee>CreateNew";
const FRISBEE_DESIGN_SETTING = "Frisbee>Design";
const FRISBEE_FORCE_MOUSE_CONTROLS_SETTING = "Frisbee>ForceMouseControls";

//Add your own designs in FRISBEE_DESIGNS, be sure to put frisbee in the URL if you want others to be able to catch it without having a copy of your frisbee script.
const FRISBEE_DESIGNS = [
                          {"name":"Interface", "model":"http://test.thoys.nl/hifi/models/frisbee/frisbee.fbx"},
                          {"name":"Pizza", "model":"http://test.thoys.nl/hifi/models/frisbee/pizza.fbx"},
                          {"name":"Swirl", "model":"http://test.thoys.nl/hifi/models/frisbee/swirl.fbx"},
                          {"name":"Mayan", "model":"http://test.thoys.nl/hifi/models/frisbee/mayan.fbx"},
                        ];
const FRISBEE_MENU_DESIGN_POSTFIX = " Design";
const FRISBEE_DESIGN_RANDOM = "Random";

const SPIN_MULTIPLIER = 1000;
const FRISBEE_LIFETIME = 300; // 5 minutes

var windowDimensions = Controller.getViewportDimensions();
var toolHeight = 50;
var toolWidth = 50;
var frisbeeToggle;
var toolBar;
var frisbeeEnabled = true;
var newfrisbeeEnabled = false;
var forceMouseControls = false;
var hydrasConnected = false;
var selectedDesign = FRISBEE_DESIGN_RANDOM;

function loadSettings() {
    frisbeeEnabled = Settings.getValue(FRISBEE_ENABLED_SETTING, "true") == "true";
    newfrisbeeEnabled = Settings.getValue(FRISBEE_CREATENEW_SETTING, "false") == "true";
    forceMouseControls = Settings.getValue(FRISBEE_FORCE_MOUSE_CONTROLS_SETTING, "false") == "true";
    selectedDesign = Settings.getValue(FRISBEE_DESIGN_SETTING, "Random");
}

function saveSettings() {
    Settings.setValue(FRISBEE_ENABLED_SETTING, frisbeeEnabled ? "true" : "false");
    Settings.setValue(FRISBEE_CREATENEW_SETTING, newfrisbeeEnabled ? "true" : "false");
    Settings.setValue(FRISBEE_FORCE_MOUSE_CONTROLS_SETTING, forceMouseControls ? "true" : "false");
    Settings.setValue(FRISBEE_DESIGN_SETTING, selectedDesign); 
}

function moveOverlays() {
    var newViewPort = Controller.getViewportDimensions();
    if (typeof(toolBar) === 'undefined') {
        initToolBar();
    } else if (windowDimensions.x == newViewPort.x &&
               windowDimensions.y == newViewPort.y) {
        return;
    }
    
    windowDimensions = newViewPort;
    var toolsX = windowDimensions.x - 8 - toolBar.width;
    var toolsY = (windowDimensions.y - toolBar.height) / 2 + 80;    
    toolBar.move(toolsX, toolsY);
}

function frisbeeURL() {
    return selectedDesign == FRISBEE_DESIGN_RANDOM ? FRISBEE_DESIGNS[Math.floor(Math.random() * FRISBEE_DESIGNS.length)].model : getFrisbee(selectedDesign).model;
}

//This function checks if the modelURL is inside of our Designs or contains "frisbee" in it.
function validFrisbeeURL(frisbeeURL) {
    for (var frisbee in FRISBEE_DESIGNS) {
        if (FRISBEE_DESIGNS[frisbee].model == frisbeeURL) {
            return true;
        }
    }
    return frisbeeURL.toLowerCase().indexOf("frisbee") !== -1;
}

function getFrisbee(frisbeeName) {
    for (var frisbee in FRISBEE_DESIGNS) {
        if (FRISBEE_DESIGNS[frisbee].name == frisbeeName) {
            return FRISBEE_DESIGNS[frisbee];
        }
    }
    return undefined;
}

function Hand(name, palm, tip, forwardButton, button3, trigger) {
    this.name = name;
    this.palm = palm;
    this.tip = tip;
    this.forwardButton = forwardButton;
    this.button3 = button3;
    this.trigger = trigger;
    this.holdingFrisbee = false;
    this.entity = false;
    this.palmPosition = function() { return Controller.getSpatialControlPosition(this.palm); }
    this.grabButtonPressed = function() { 
                                    return (
                                            Controller.isButtonPressed(this.forwardButton)  ||
                                            Controller.isButtonPressed(this.button3)        ||
                                            Controller.getTriggerValue(this.trigger) > 0.5
                                            )
                                        };
    this.holdPosition = function() { return this.palm == LEFT_PALM ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); };
    this.holdRotation = function() {
        var q = Controller.getSpatialControlRawRotation(this.palm);
        q = Quat.multiply(MyAvatar.orientation, q);
        return {x: q.x, y: q.y, z: q.z, w: q.w};
    };
    this.tipVelocity = function() { return Controller.getSpatialControlVelocity(this.tip); };
}

function MouseControl(button) {
    this.button = button;
}

var leftHand = new Hand("LEFT", LEFT_PALM, LEFT_TIP, LEFT_BUTTON_FWD, LEFT_BUTTON_3, 0);
var rightHand = new Hand("RIGHT", RIGHT_PALM, RIGHT_TIP, RIGHT_BUTTON_FWD, RIGHT_BUTTON_3, 1);

var leftMouseControl = new MouseControl("LEFT");
var middleMouseControl = new MouseControl("MIDDLE");
var rightMouseControl = new MouseControl("RIGHT");
var mouseControls = [leftMouseControl, middleMouseControl, rightMouseControl];
var currentMouseControl = false;

var newSound = SoundCache.getSound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/throw.raw");
var catchSound = SoundCache.getSound("https://dl.dropboxusercontent.com/u/1864924/hifi-sounds/catch.raw");
var throwSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Switches%20and%20sliders/slider%20-%20whoosh1.raw");

var simulatedFrisbees = [];

var wantDebugging = false;
function debugPrint(message) {
    if (wantDebugging) {
        print(message);
    }
}

function playSound(sound, position) {
    if (!SOUNDS_ENABLED) {
        return;
    }
    
    Audio.playSound(sound,{
      position: position
    });
}

function cleanupFrisbees() {
    simulatedFrisbees = [];
    var entities = Entities.findEntities(MyAvatar.position, 1000);
    for (entity in entities) {
        Entities.deleteEntity(entities[entity]);
    }
}

function checkControllerSide(hand) {
    // If I don't currently have a frisbee in my hand, then try to catch closest one
    if (!hand.holdingFrisbee && hand.grabButtonPressed()) {
        var closestEntity = Entities.findClosestEntity(hand.palmPosition(), CATCH_RADIUS);
        var modelUrl = Entities.getEntityProperties(closestEntity).modelURL;
        if (closestEntity.isKnownID && validFrisbeeURL(Entities.getEntityProperties(closestEntity).modelURL)) {
            Entities.editEntity(closestEntity, {modelScale: 1, inHand: true, position: hand.holdPosition(), shouldDie: true});
            Entities.deleteEntity(closestEntity);
            debugPrint(hand.message + " HAND- CAUGHT SOMETHING!!");
            
            var properties = {
                type: "Model",
                position: hand.holdPosition(), 
                velocity: { x: 0, y: 0, z: 0}, 
                gravity: { x: 0, y: 0, z: 0}, 
                inHand: true,
                dimensions: { x: FRISBEE_RADIUS, y: FRISBEE_RADIUS / 5, z: FRISBEE_RADIUS },
                damping: 0.00001,
                modelURL: modelUrl,
                modelScale: FRISBEE_MODEL_SCALE,
                modelRotation: hand.holdRotation(),
                lifetime: FRISBEE_LIFETIME
            };

            newEntity = Entities.addEntity(properties);
            
            hand.holdingFrisbee = true;
            hand.entity = newEntity;

            playSound(catchSound, hand.holdPosition());
            
            return; // exit early
        }
    }

    //  If '3' is pressed, and not holding a frisbee, make a new one
    if (hand.grabButtonPressed() && !hand.holdingFrisbee && newfrisbeeEnabled) {
        var properties = {
                type: "Model",
                position: hand.holdPosition(), 
                velocity: { x: 0, y: 0, z: 0}, 
                gravity: { x: 0, y: 0, z: 0}, 
                inHand: true,
                dimensions: { x: FRISBEE_RADIUS, y: FRISBEE_RADIUS / 5, z: FRISBEE_RADIUS },
                damping: 0.00001,
                modelURL: frisbeeURL(),
                modelScale: FRISBEE_MODEL_SCALE,
                modelRotation: hand.holdRotation(),
                lifetime: FRISBEE_LIFETIME
            };

        newEntity = Entities.addEntity(properties);
        hand.holdingFrisbee = true;
        hand.entity = newEntity;

        // Play a new frisbee sound
        playSound(newSound, hand.holdPosition());
        
        return; // exit early
    }

    if (hand.holdingFrisbee) {
        //  If holding the frisbee keep it in the palm
        if (hand.grabButtonPressed()) {
            debugPrint(">>>>> " + hand.name + "-FRISBEE IN HAND, grabbing, hold and move");
            var properties = { 
                    position: hand.holdPosition(),
                    modelRotation: hand.holdRotation()
                };
            Entities.editEntity(hand.entity, properties);
        } else {
            debugPrint(">>>>> " + hand.name + "-FRISBEE IN HAND, not grabbing, THROW!!!");
            //  If frisbee just released, add velocity to it!
            
            var properties = {
                    velocity: Vec3.multiply(hand.tipVelocity(), THROWN_VELOCITY_SCALING),
                    inHand: false,
                    lifetime: FRISBEE_LIFETIME,
                    gravity: { x: 0, y: -GRAVITY_STRENGTH, z: 0},
                    modelRotation: hand.holdRotation()
                };

            Entities.editEntity(hand.entity, properties);

            simulatedFrisbees.push(hand.entity);

            hand.holdingFrisbee = false;
            hand.entity = false;
            
            playSound(throwSound, hand.holdPosition());
        }
    }
}

function initToolBar() {
    toolBar = new ToolBar(0, 0, ToolBar.VERTICAL);
    frisbeeToggle = toolBar.addTool({
                               imageURL: FRISBEE_BUTTON_URL,
                               subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT },
                               width: toolWidth,
                               height: toolHeight,
                               visible: true,
                               alpha: 0.9
                               }, true);
    enableNewFrisbee(newfrisbeeEnabled);
}

function hydraCheck() {
    var numberOfButtons = Controller.getNumberOfButtons();
    var numberOfTriggers = Controller.getNumberOfTriggers();
    var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
    var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
    hydrasConnected = (numberOfButtons == 12 && numberOfTriggers == 2 && controllersPerTrigger == 2);
    return hydrasConnected;
}

function checkController(deltaTime) {
    moveOverlays();
    if (!frisbeeEnabled) {
        return;
    }
    // this is expected for hydras
    if (hydraCheck()) {
        checkControllerSide(leftHand);
        checkControllerSide(rightHand);
    }
    if (!hydrasConnected || forceMouseControls) {
        //TODO: add mouse cursor control code here.
    }
}

function controlFrisbees(deltaTime) {
    var killSimulations = [];
    for (frisbee in simulatedFrisbees) {
        var properties = Entities.getEntityProperties(simulatedFrisbees[frisbee]);
        //get the horizon length from the velocity origin in order to get speed
        var speed = Vec3.length({x:properties.velocity.x, y:0, z:properties.velocity.z});
        if (speed < MIN_SIMULATION_SPEED) {
            //kill the frisbee simulation when speed is low
            killSimulations.push(frisbee);
            continue;
        }
        Entities.editEntity(simulatedFrisbees[frisbee], {modelRotation: Quat.multiply(properties.modelRotation, Quat.fromPitchYawRollDegrees(0, speed * deltaTime * SPIN_MULTIPLIER, 0))});
        
    }
    for (var i = killSimulations.length - 1; i >= 0; i--) {
        simulatedFrisbees.splice(killSimulations[i], 1);
    }
}

//catches interfering calls of hydra-cursors
function withinBounds(coords) {
    return coords.x >= 0 && coords.x < windowDimensions.x && coords.y >= 0 && coords.y < windowDimensions.y;    
}

function mouseMoveEvent(event) {
    //TODO: mouse controls //print(withinBounds(event)); //print("move"+event.x);
}

function mousePressEvent(event) {
    print(event.x);
    var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
    if (frisbeeToggle == toolBar.clicked(clickedOverlay)) {
        newfrisbeeEnabled = !newfrisbeeEnabled;
        saveSettings();
        enableNewFrisbee(newfrisbeeEnabled);
    }
}

function enableNewFrisbee(enable) {
    if (toolBar.numberOfTools() > 0) {
        toolBar.tools[0].select(enable);
    }
}

function mouseReleaseEvent(event) {
    //TODO: mouse controls //print(JSON.stringify(event));
}

function setupMenus() {
    Menu.addMenu(FRISBEE_MENU);
    Menu.addMenuItem({ 
        menuName: FRISBEE_MENU, 
        menuItemName: "Frisbee Enabled",
        isCheckable: true,
        isChecked: frisbeeEnabled
    });
    Menu.addMenuItem({ 
        menuName: FRISBEE_MENU, 
        menuItemName: "Cleanup Frisbees"
    });
    Menu.addMenuItem({ 
        menuName: FRISBEE_MENU, 
        menuItemName: "Force Mouse Controls",
        isCheckable: true,
        isChecked: forceMouseControls
    });
    Menu.addMenu(FRISBEE_DESIGN_MENU);
    Menu.addMenuItem({
        menuName: FRISBEE_DESIGN_MENU, 
        menuItemName: FRISBEE_DESIGN_RANDOM + FRISBEE_MENU_DESIGN_POSTFIX,
        isCheckable: true,
        isChecked: selectedDesign == FRISBEE_DESIGN_RANDOM
    });
    for (frisbee in FRISBEE_DESIGNS) {
        Menu.addMenuItem({ 
            menuName: FRISBEE_DESIGN_MENU, 
            menuItemName: FRISBEE_DESIGNS[frisbee].name + FRISBEE_MENU_DESIGN_POSTFIX,
            isCheckable: true,
            isChecked: selectedDesign == FRISBEE_DESIGNS[frisbee].name
        });
    }
}

//startup calls:
loadSettings();
setupMenus();
function scriptEnding() {
    toolBar.cleanup();
    Menu.removeMenu(FRISBEE_MENU);
}

function menuItemEvent(menuItem) {
    if (menuItem == "Cleanup Frisbees") {
        cleanupFrisbees();
        return;
    } else if (menuItem == "Frisbee Enabled") {
        frisbeeEnabled = Menu.isOptionChecked(menuItem);
        saveSettings();
        return;
    } else if (menuItem == "Force Mouse Controls") {
        forceMouseControls = Menu.isOptionChecked(menuItem);
        saveSettings();
        return;
    }
    if (menuItem.indexOf(FRISBEE_MENU_DESIGN_POSTFIX, menuItem.length - FRISBEE_MENU_DESIGN_POSTFIX.length) !== -1) {
        var item_name = menuItem.substring(0, menuItem.length - FRISBEE_MENU_DESIGN_POSTFIX.length);
        if (item_name == FRISBEE_DESIGN_RANDOM || getFrisbee(item_name) != undefined) {
            Menu.setIsOptionChecked(selectedDesign + FRISBEE_MENU_DESIGN_POSTFIX, false);
            selectedDesign = item_name;
            saveSettings();
            Menu.setIsOptionChecked(selectedDesign + FRISBEE_MENU_DESIGN_POSTFIX, true);
        }
    }
}

// register the call back so it fires before each data send
Controller.mouseMoveEvent.connect(mouseMoveEvent);
Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
Menu.menuItemEvent.connect(menuItemEvent);
Script.scriptEnding.connect(scriptEnding);
Script.update.connect(checkController);
Script.update.connect(controlFrisbees);