"use strict";

//  edit.js
//
//  Created by Brad Hefta-Gaub on 10/2/14.
//  Persist toolbar by HRS 6/11/15.
//  Copyright 2014 High Fidelity, Inc.
//
//  This script allows you to edit entities with a new UI/UX for mouse and trackpad based editing
//
//  Distributed under the Apache License, Version 2.0.
//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//

/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, 
   Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */

(function() { // BEGIN LOCAL_SCOPE

var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton";
var SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system";
var EDIT_TOOLBAR = "com.highfidelity.interface.toolbar.edit";

Script.include([
    "libraries/stringHelpers.js",
    "libraries/dataViewHelpers.js",
    "libraries/progressDialog.js",

    "libraries/entitySelectionTool.js",

    "libraries/ToolTip.js",

    "libraries/entityCameraTool.js",
    "libraries/gridTool.js",
    "libraries/entityList.js",
    "particle_explorer/particleExplorerTool.js",
    "libraries/entityIconOverlayManager.js"
]);

var selectionDisplay = SelectionDisplay;
var selectionManager = SelectionManager;

const PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg");
const POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg");
const SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg");
entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect'], function(entityID) {
    var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']);
    if (properties.type === 'Light') {
        return {
            url: properties.isSpotlight ? SPOT_LIGHT_URL : POINT_LIGHT_URL,
        }
    } else {
        return {
            url: PARTICLE_SYSTEM_URL,
        }
    }
});

var cameraManager = new CameraManager();

var grid = new Grid();
var gridTool = new GridTool({
    horizontalGrid: grid
});
gridTool.setVisible(false);

var entityListTool = new EntityListTool();

selectionManager.addEventListener(function () {
    selectionDisplay.updateHandles();
    entityIconOverlayManager.updatePositions();

    // Update particle explorer
    var needToDestroyParticleExplorer = false;
    if (selectionManager.selections.length === 1) {
        var selectedEntityID = selectionManager.selections[0];
        if (selectedEntityID === selectedParticleEntityID) {
            return;
        }
        var type = Entities.getEntityProperties(selectedEntityID, "type").type;
        if (type === "ParticleEffect") {
            // Destroy the old particles web view first
            particleExplorerTool.destroyWebView();
            particleExplorerTool.createWebView();
            var properties = Entities.getEntityProperties(selectedEntityID);
            var particleData = {
                messageType: "particle_settings",
                currentProperties: properties
            };
            selectedParticleEntityID = selectedEntityID;
            particleExplorerTool.setActiveParticleEntity(selectedParticleEntityID);

            particleExplorerTool.webView.webEventReceived.connect(function (data) {
                data = JSON.parse(data);
                if (data.messageType === "page_loaded") {
                    particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));
                }
            });

            // Switch to particle explorer
            var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
            tablet.sendToQml({method: 'selectTab', params: {id: 'particle'}});
        } else {
            needToDestroyParticleExplorer = true;
        }
    } else {
        needToDestroyParticleExplorer = true;
    }

    if (needToDestroyParticleExplorer && selectedParticleEntityID !== null) {
        selectedParticleEntityID = null;
        particleExplorerTool.destroyWebView();
    }
});

const KEY_P = 80; //Key code for letter p used for Parenting hotkey.
var DEGREES_TO_RADIANS = Math.PI / 180.0;
var RADIANS_TO_DEGREES = 180.0 / Math.PI;

var MIN_ANGULAR_SIZE = 2;
var MAX_ANGULAR_SIZE = 45;
var allowLargeModels = true;
var allowSmallModels = true;

var DEFAULT_DIMENSION = 0.20;

var DEFAULT_DIMENSIONS = {
    x: DEFAULT_DIMENSION,
    y: DEFAULT_DIMENSION,
    z: DEFAULT_DIMENSION
};

var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS);

var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select";
var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus";
var MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "Show Lights and Particle Systems in Edit Mode";
var MENU_SHOW_ZONES_IN_EDIT_MODE = "Show Zones in Edit Mode";

var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect";
var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus";
var SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "showLightsAndParticlesInEditMode";
var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode";


// marketplace info, etc.  not quite ready yet.
var SHOULD_SHOW_PROPERTY_MENU = false;
var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain.";
var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary permissions to place items on this domain.";

var isActive = false;

var IMPORTING_SVO_OVERLAY_WIDTH = 144;
var IMPORTING_SVO_OVERLAY_HEIGHT = 30;
var IMPORTING_SVO_OVERLAY_MARGIN = 5;
var IMPORTING_SVO_OVERLAY_LEFT_MARGIN = 34;
var importingSVOImageOverlay = Overlays.addOverlay("image", {
    imageURL: Script.resolvePath("assets") + "/images/hourglass.svg",
    width: 20,
    height: 20,
    alpha: 1.0,
    x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH,
    y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT,
    visible: false
});
var importingSVOTextOverlay = Overlays.addOverlay("text", {
    font: {
        size: 14
    },
    text: "Importing SVO...",
    leftMargin: IMPORTING_SVO_OVERLAY_LEFT_MARGIN,
    x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH - IMPORTING_SVO_OVERLAY_MARGIN,
    y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT - IMPORTING_SVO_OVERLAY_MARGIN,
    width: IMPORTING_SVO_OVERLAY_WIDTH,
    height: IMPORTING_SVO_OVERLAY_HEIGHT,
    backgroundColor: {
        red: 80,
        green: 80,
        blue: 80
    },
    backgroundAlpha: 0.7,
    visible: false
});

var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace";
var marketplaceWindow = new OverlayWebWindow({
    title: 'Marketplace',
    source: "about:blank",
    width: 900,
    height: 700,
    visible: false
});

function showMarketplace(marketplaceID) {
    var url = MARKETPLACE_URL;
    if (marketplaceID) {
        url = url + "/items/" + marketplaceID;
    }
    marketplaceWindow.setURL(url);
    marketplaceWindow.setVisible(true);
    marketplaceWindow.raise();

    UserActivityLogger.logAction("opened_marketplace");
}

function hideMarketplace() {
    marketplaceWindow.setVisible(false);
    marketplaceWindow.setURL("about:blank");
}

// function toggleMarketplace() {
//     if (marketplaceWindow.visible) {
//         hideMarketplace();
//     } else {
//         showMarketplace();
//     }
// }

var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit";
var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable";

var toolBar = (function () {
    var EDIT_SETTING = "io.highfidelity.isEditting"; // for communication with other scripts
    var that = {},
        toolBar,
        activeButton = null,
        systemToolbar = null,
        tablet = null;

    function createNewEntity(properties) {
        var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS;
        var position = getPositionToCreateEntity();
        var entityID = null;
        if (position !== null && position !== undefined) {
            position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions);
            properties.position = position;
            if (Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM)) {
                properties.userData = JSON.stringify({ grabbableKey: { grabbable: true } });
            }
            entityID = Entities.addEntity(properties);
            if (properties.type == "ParticleEffect") {
                selectParticleEntity(entityID);
            }
        } else {
            Window.notifyEditError("Can't create " + properties.type + ": " +
                                   properties.type + " would be out of bounds.");
        }

        selectionManager.clearSelections();
        entityListTool.sendUpdate();
        selectionManager.setSelections([entityID]);

        return entityID;
    }

    function cleanup() {
        that.setActive(false);
        if (tablet) {
            tablet.removeButton(activeButton);
        }
        if (systemToolbar) {
            systemToolbar.removeButton(EDIT_TOGGLE_BUTTON);
        }
        Menu.removeMenuItem(GRABBABLE_ENTITIES_MENU_CATEGORY, GRABBABLE_ENTITIES_MENU_ITEM);
    }

    var buttonHandlers = {}; // only used to tablet mode

    function addButton(name, image, handler) {
        buttonHandlers[name] = handler;
    }

    var SHAPE_TYPE_NONE = 0;
    var SHAPE_TYPE_SIMPLE_HULL = 1;
    var SHAPE_TYPE_SIMPLE_COMPOUND = 2;
    var SHAPE_TYPE_STATIC_MESH = 3;
    var DYNAMIC_DEFAULT = false;

    function handleNewModelDialogResult(result) {
        if (result) {
            var url = result.textInput;
            var shapeType;
            switch (result.comboBox) {
            case SHAPE_TYPE_SIMPLE_HULL:
                shapeType = "simple-hull";
                break;
            case SHAPE_TYPE_SIMPLE_COMPOUND:
                shapeType = "simple-compound";
                break;
            case SHAPE_TYPE_STATIC_MESH:
                shapeType = "static-mesh";
                break;
            default:
                shapeType = "none";
            }

            var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT;
            if (shapeType === "static-mesh" && dynamic) {
                // The prompt should prevent this case
                print("Error: model cannot be both static mesh and dynamic.  This should never happen.");
            } else if (url) {
                createNewEntity({
                    type: "Model",
                    modelURL: url,
                    shapeType: shapeType,
                    dynamic: dynamic,
                    gravity: dynamic ? { x: 0, y: -10, z: 0 } : { x: 0, y: 0, z: 0 }
                });
            }
        }        
    }

    function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
        var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
        tablet.popFromStack();
        switch (message.method) {
        case "newModelDialogAdd":
            handleNewModelDialogResult(message.params);
            break;
        case "newEntityButtonClicked":
            buttonHandlers[message.params.buttonName]();
            break;
        }
    }

    function initialize() {
        Script.scriptEnding.connect(cleanup);

        Window.domainChanged.connect(function () {
            that.setActive(false);
            that.clearEntityList();
        });

        Entities.canAdjustLocksChanged.connect(function (canAdjustLocks) {
            if (isActive && !canAdjustLocks) {
                that.setActive(false);
            }
        });

        tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
        activeButton = tablet.addButton({
            icon: "icons/tablet-icons/edit-i.svg",
            activeIcon: "icons/tablet-icons/edit-a.svg",
            text: "EDIT",
            sortOrder: 10
        });
        tablet.screenChanged.connect(function (type, url) {
            if (isActive && (type !== "QML" || url !== "Edit.qml")) {
                that.toggle();
            }
        });
        tablet.fromQml.connect(fromQml);

        activeButton.clicked.connect(function() {
            that.toggle();
        });

        addButton("importEntitiesButton", "assets-01.svg", function() {
            var importURL = null;
            var fullPath = Window.browse("Select Model to Import", "", "*.json");
            if (fullPath) {
                importURL = "file:///" + fullPath;
            }
            if (importURL) {
                if (!isActive && (Entities.canRez() && Entities.canRezTmp())) {
                    toolBar.toggle();
                }
                importSVO(importURL);
            }
        });

        addButton("openAssetBrowserButton", "assets-01.svg", function() {
            Window.showAssetServer();
        });

        addButton("newModelButton", "model-01.svg", function () {

            var SHAPE_TYPES = [];
            SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision";
            SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model";
            SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes";
            SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons";
            var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH;

            // tablet version of new-model dialog
            var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
            tablet.pushOntoStack("NewModelDialog.qml");
        });

        addButton("newCubeButton", "cube-01.svg", function () {
            createNewEntity({
                type: "Box",
                dimensions: DEFAULT_DIMENSIONS,
                color: {
                    red: 255,
                    green: 0,
                    blue: 0
                }
            });
        });

        addButton("newSphereButton", "sphere-01.svg", function () {
            createNewEntity({
                type: "Sphere",
                dimensions: DEFAULT_DIMENSIONS,
                color: {
                    red: 255,
                    green: 0,
                    blue: 0
                }
            });
        });

        addButton("newLightButton", "light-01.svg", function () {
            createNewEntity({
                type: "Light",
                dimensions: DEFAULT_LIGHT_DIMENSIONS,
                isSpotlight: false,
                color: {
                    red: 150,
                    green: 150,
                    blue: 150
                },

                constantAttenuation: 1,
                linearAttenuation: 0,
                quadraticAttenuation: 0,
                exponent: 0,
                cutoff: 180 // in degrees
            });
        });

        addButton("newTextButton", "text-01.svg", function () {
            createNewEntity({
                type: "Text",
                dimensions: {
                    x: 0.65,
                    y: 0.3,
                    z: 0.01
                },
                backgroundColor: {
                    red: 64,
                    green: 64,
                    blue: 64
                },
                textColor: {
                    red: 255,
                    green: 255,
                    blue: 255
                },
                text: "some text",
                lineHeight: 0.06
            });
        });

        addButton("newWebButton", "web-01.svg", function () {
            createNewEntity({
                type: "Web",
                dimensions: {
                    x: 1.6,
                    y: 0.9,
                    z: 0.01
                },
                sourceUrl: "https://highfidelity.com/"
            });
        });

        addButton("newZoneButton", "zone-01.svg", function () {
            createNewEntity({
                type: "Zone",
                dimensions: {
                    x: 10,
                    y: 10,
                    z: 10
                }
            });
        });

        addButton("newParticleButton", "particle-01.svg", function () {
            createNewEntity({
                type: "ParticleEffect",
                isEmitting: true,
                emitAcceleration: {
                    x: 0,
                    y: -1,
                    z: 0
                },
                accelerationSpread: {
                    x: 5,
                    y: 0,
                    z: 5
                },
                emitSpeed: 1,
                lifespan: 1,
                particleRadius: 0.025,
                alphaFinish: 0,
                emitRate: 100,
                textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png"
            });
        });

        that.setActive(false);
    }

    that.clearEntityList = function () {
        entityListTool.clearEntityList();
    };

    that.toggle = function () {
        that.setActive(!isActive);
        activeButton.editProperties({isActive: isActive});
        if (!isActive) {
            tablet.gotoHomeScreen();
        }
    };

    that.setActive = function (active) {
        Settings.setValue(EDIT_SETTING, active);
        if (active) {
            Controller.captureEntityClickEvents();
        } else {
            Controller.releaseEntityClickEvents();
        }
        if (active === isActive) {
            return;
        }
        if (active && !Entities.canRez() && !Entities.canRezTmp()) {
            Window.notifyEditError(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
            return;
        }
        Messages.sendLocalMessage("edit-events", JSON.stringify({
            enabled: active
        }));
        isActive = active;
        if (!isActive) {
            entityListTool.setVisible(false);
            gridTool.setVisible(false);
            grid.setEnabled(false);
            propertiesTool.setVisible(false);
            selectionManager.clearSelections();
            cameraManager.disable();
            selectionDisplay.triggerMapping.disable();
        } else {
            var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
            tablet.loadQMLSource("Edit.qml");
            UserActivityLogger.enabledEdit();
            entityListTool.setVisible(true);
            gridTool.setVisible(true);
            grid.setEnabled(true);
            propertiesTool.setVisible(true);
            selectionDisplay.triggerMapping.enable();
            // Not sure what the following was meant to accomplish, but it currently causes
            // everybody else to think that Interface has lost focus overall. fogbugzid:558
            // Window.setFocus();
        }
        entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE));
        Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE));
    };

    initialize();
    return that;
})();


function isLocked(properties) {
    // special case to lock the ground plane model in hq.
    if (location.hostname === "hq.highfidelity.io" &&
        properties.modelURL === HIFI_PUBLIC_BUCKET + "ozan/Terrain_Reduce_forAlpha.fbx") {
        return true;
    }
    return false;
}


var selectedEntityID;
var orientation;
var intersection;


function rayPlaneIntersection(pickRay, point, normal) { //
    //
    //  This version of the test returns the intersection of a line with a plane
    //
    var collides = Vec3.dot(pickRay.direction, normal);

    var d = -Vec3.dot(point, normal);
    var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides;

    return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t));
}

function rayPlaneIntersection2(pickRay, point, normal) {
    //
    //  This version of the test returns false if the ray is directed away from the plane
    //
    var collides = Vec3.dot(pickRay.direction, normal);
    var d = -Vec3.dot(point, normal);
    var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides;
    if (t < 0.0) {
        return false;
    } else {
        return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t));
    }
}

function findClickedEntity(event) {
    var pickZones = event.isControl;

    if (pickZones) {
        Entities.setZonesArePickable(true);
    }

    var pickRay = Camera.computePickRay(event.x, event.y);

    var overlayResult = Overlays.findRayIntersection(pickRay, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]);
    if (overlayResult.intersects) {
        return null;
    }

    var entityResult = Entities.findRayIntersection(pickRay, true); // want precision picking
    var iconResult = entityIconOverlayManager.findRayIntersection(pickRay);
    iconResult.accurate = true;

    if (pickZones) {
        Entities.setZonesArePickable(false);
    }

    var result;

    if (iconResult.intersects) {
        result = iconResult;
    } else if (entityResult.intersects) {
        result = entityResult;
    } else {
        return null;
    }

    if (!result.accurate) {
        return null;
    }

    var foundEntity = result.entityID;
    return {
        pickRay: pickRay,
        entityID: foundEntity,
        intersection: result.intersection
    };
}

// Handles selections on overlays while in edit mode by querying entities from
// entityIconOverlayManager.
function handleOverlaySelectionToolUpdates(channel, message, sender) {
    if (sender !== MyAvatar.sessionUUID || channel !== 'entityToolUpdates')
        return;

    var data = JSON.parse(message);
    
    if (data.method === "selectOverlay") {
        print("setting selection to overlay " + data.overlayID);
        var entity = entityIconOverlayManager.findEntity(data.overlayID);

        if (entity !== null) {
            selectionManager.setSelections([entity]);
        }
    } 
}

Messages.subscribe("entityToolUpdates");
Messages.messageReceived.connect(handleOverlaySelectionToolUpdates);

var mouseHasMovedSincePress = false;
var mousePressStartTime = 0;
var mousePressStartPosition = {
    x: 0,
    y: 0
};
var mouseDown = false;

function mousePressEvent(event) {
    mouseDown = true;
    mousePressStartPosition = {
        x: event.x,
        y: event.y
    };
    mousePressStartTime = Date.now();
    mouseHasMovedSincePress = false;
    mouseCapturedByTool = false;

    if (propertyMenu.mousePressEvent(event) || progressDialog.mousePressEvent(event)) {
        mouseCapturedByTool = true;
        return;
    }
    if (isActive) {
        if (cameraManager.mousePressEvent(event) || selectionDisplay.mousePressEvent(event)) {
            // Event handled; do nothing.
            return;
        }
    }
}

var mouseCapturedByTool = false;
var lastMousePosition = null;
var CLICK_TIME_THRESHOLD = 500 * 1000; // 500 ms
var CLICK_MOVE_DISTANCE_THRESHOLD = 20;
var IDLE_MOUSE_TIMEOUT = 200;

var lastMouseMoveEvent = null;

function mouseMoveEventBuffered(event) {
    lastMouseMoveEvent = event;
}

function mouseMove(event) {
    if (mouseDown && !mouseHasMovedSincePress) {
        var timeSincePressMicro = Date.now() - mousePressStartTime;

        var dX = mousePressStartPosition.x - event.x;
        var dY = mousePressStartPosition.y - event.y;
        var sqDist = (dX * dX) + (dY * dY);

        // If less than CLICK_TIME_THRESHOLD has passed since the mouse click AND the mouse has moved
        // less than CLICK_MOVE_DISTANCE_THRESHOLD distance, then don't register this as a mouse move
        // yet. The goal is to provide mouse clicks that are more lenient to small movements.
        if (timeSincePressMicro < CLICK_TIME_THRESHOLD && sqDist < CLICK_MOVE_DISTANCE_THRESHOLD) {
            return;
        }
        mouseHasMovedSincePress = true;
    }

    if (!isActive) {
        return;
    }

    // allow the selectionDisplay and cameraManager to handle the event first, if it doesn't handle it, then do our own thing
    if (selectionDisplay.mouseMoveEvent(event) || propertyMenu.mouseMoveEvent(event) || cameraManager.mouseMoveEvent(event)) {
        return;
    }

    lastMousePosition = {
        x: event.x,
        y: event.y
    };
}

function mouseReleaseEvent(event) {
    mouseDown = false;

    if (lastMouseMoveEvent) {
        mouseMove(lastMouseMoveEvent);
        lastMouseMoveEvent = null;
    }
    if (propertyMenu.mouseReleaseEvent(event)) {
        return true;
    }
    if (isActive && selectionManager.hasSelection()) {
        tooltip.show(false);
    }
    if (mouseCapturedByTool) {

        return;
    }

    cameraManager.mouseReleaseEvent(event);

    if (!mouseHasMovedSincePress) {
        mouseClickEvent(event);
    }
}

function wasTabletClicked(event) {
    var rayPick = Camera.computePickRay(event.x, event.y);
    var result = Overlays.findRayIntersection(rayPick, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]);
    return result.intersects;
}
    
function mouseClickEvent(event) {
    var wantDebug = false;
    var result, properties, tabletClicked;
    if (isActive && event.isLeftButton) {
        result = findClickedEntity(event);
        tabletClicked = wasTabletClicked(event);
        if (tabletClicked) {
            return;
        }
        
        if (result === null || result === undefined) {
            if (!event.isShifted) {
                selectionManager.clearSelections();
            }
            return;
        }
        toolBar.setActive(true);
        var pickRay = result.pickRay;
        var foundEntity = result.entityID;
        if (foundEntity === HMD.tabletID) {
            return;
        }
        properties = Entities.getEntityProperties(foundEntity);
        if (isLocked(properties)) {
            if (wantDebug) {
                print("Model locked " + properties.id);
            }
        } else {
            var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;

            if (wantDebug) {
                print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal);
            }
            //                P         P - Model
            //               /|         A - Palm
            //              / | d       B - unit vector toward tip
            //             /  |         X - base of the perpendicular line
            //            A---X----->B  d - distance fom axis
            //              x           x - distance from A
            //
            //            |X-A| = (P-A).B
            //            X === A + ((P-A).B)B
            //            d = |P-X|

            var A = pickRay.origin;
            var B = Vec3.normalize(pickRay.direction);
            var P = properties.position;

            var x = Vec3.dot(Vec3.subtract(P, A), B);

            var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) *
                              180 / Math.PI;

            var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) &&
                         (allowSmallModels || angularSize > MIN_ANGULAR_SIZE);

            if (0 < x && sizeOK) {
                selectedEntityID = foundEntity;
                orientation = MyAvatar.orientation;
                intersection = rayPlaneIntersection(pickRay, P, Quat.getForward(orientation));

                if (event.isShifted) {
                    particleExplorerTool.destroyWebView();
                }
                if (properties.type !== "ParticleEffect") {
                    particleExplorerTool.destroyWebView();
                }

                if (!event.isShifted) {
                    selectionManager.setSelections([foundEntity]);
                } else {
                    selectionManager.addEntity(foundEntity, true);
                }
                if (wantDebug) {
                    print("Model selected: " + foundEntity);
                }
                selectionDisplay.select(selectedEntityID, event);

                if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) {
                    cameraManager.enable();
                    cameraManager.focus(selectionManager.worldPosition,
                        selectionManager.worldDimensions,
                        Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
                }
            }
        }
    } else if (event.isRightButton) {
        result = findClickedEntity(event);
        if (result) {
            if (SHOULD_SHOW_PROPERTY_MENU !== true) {
                return;
            }
            properties = Entities.getEntityProperties(result.entityID);
            if (properties.marketplaceID) {
                propertyMenu.marketplaceID = properties.marketplaceID;
                propertyMenu.updateMenuItemText(showMenuItem, "Show in Marketplace");
            } else {
                propertyMenu.marketplaceID = null;
                propertyMenu.updateMenuItemText(showMenuItem, "No marketplace info");
            }
            propertyMenu.setPosition(event.x, event.y);
            propertyMenu.show();
        } else {
            propertyMenu.hide();
        }
    }
}

Controller.mousePressEvent.connect(mousePressEvent);
Controller.mouseMoveEvent.connect(mouseMoveEventBuffered);
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);


// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already
// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that
// added it.
var modelMenuAddedDelete = false;
var originalLightsArePickable = Entities.getLightsArePickable();

function setupModelMenus() {
    // adj our menuitems
    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: "Entities",
        isSeparator: true,
        grouping: "Advanced"
    });
    if (!Menu.menuItemExists("Edit", "Delete")) {
        Menu.addMenuItem({
            menuName: "Edit",
            menuItemName: "Delete",
            shortcutKeyEvent: {
                text: "delete"
            },
            afterItem: "Entities",
            grouping: "Advanced"
        });
        modelMenuAddedDelete = true;
    }
    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: "Entity List...",
        shortcutKey: "CTRL+META+L",
        afterItem: "Entities",
        grouping: "Advanced"
    });

    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: "Parent Entity to Last",
        afterItem: "Entity List...",
        grouping: "Advanced"
    });

    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: "Unparent Entity",
        afterItem: "Parent Entity to Last",
        grouping: "Advanced"
    });

    Menu.addMenuItem({
        menuName: GRABBABLE_ENTITIES_MENU_CATEGORY,
        menuItemName: GRABBABLE_ENTITIES_MENU_ITEM,
        afterItem: "Unparent Entity",
        isCheckable: true,
        isChecked: true,
        grouping: "Advanced"
    });

    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: "Allow Selecting of Large Models",
        shortcutKey: "CTRL+META+L",
        afterItem: GRABBABLE_ENTITIES_MENU_ITEM,
        isCheckable: true,
        isChecked: true,
        grouping: "Advanced"
    });
    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: "Allow Selecting of Small Models",
        shortcutKey: "CTRL+META+S",
        afterItem: "Allow Selecting of Large Models",
        isCheckable: true,
        isChecked: true,
        grouping: "Advanced"
    });
    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: "Allow Selecting of Lights",
        shortcutKey: "CTRL+SHIFT+META+L",
        afterItem: "Allow Selecting of Small Models",
        isCheckable: true,
        grouping: "Advanced"
    });
    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: "Select All Entities In Box",
        shortcutKey: "CTRL+SHIFT+META+A",
        afterItem: "Allow Selecting of Lights",
        grouping: "Advanced"
    });
    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: "Select All Entities Touching Box",
        shortcutKey: "CTRL+SHIFT+META+T",
        afterItem: "Select All Entities In Box",
        grouping: "Advanced"
    });

    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: "Export Entities",
        shortcutKey: "CTRL+META+E",
        afterItem: "Entities",
        grouping: "Advanced"
    });
    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: "Import Entities",
        shortcutKey: "CTRL+META+I",
        afterItem: "Export Entities",
        grouping: "Advanced"
    });
    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: "Import Entities from URL",
        shortcutKey: "CTRL+META+U",
        afterItem: "Import Entities",
        grouping: "Advanced"
    });

    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: MENU_AUTO_FOCUS_ON_SELECT,
        isCheckable: true,
        isChecked: Settings.getValue(SETTING_AUTO_FOCUS_ON_SELECT) === "true",
        grouping: "Advanced"
    });
    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: MENU_EASE_ON_FOCUS,
        afterItem: MENU_AUTO_FOCUS_ON_SELECT,
        isCheckable: true,
        isChecked: Settings.getValue(SETTING_EASE_ON_FOCUS) === "true",
        grouping: "Advanced"
    });
    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE,
        afterItem: MENU_EASE_ON_FOCUS,
        isCheckable: true,
        isChecked: Settings.getValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) !== "false",
        grouping: "Advanced"
    });
    Menu.addMenuItem({
        menuName: "Edit",
        menuItemName: MENU_SHOW_ZONES_IN_EDIT_MODE,
        afterItem: MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE,
        isCheckable: true,
        isChecked: Settings.getValue(SETTING_SHOW_ZONES_IN_EDIT_MODE) !== "false",
        grouping: "Advanced"
    });

    Entities.setLightsArePickable(false);
}

setupModelMenus(); // do this when first running our script.

function cleanupModelMenus() {
    Menu.removeSeparator("Edit", "Entities");
    if (modelMenuAddedDelete) {
        // delete our menuitems
        Menu.removeMenuItem("Edit", "Delete");
    }

    Menu.removeMenuItem("Edit", "Parent Entity to Last");
    Menu.removeMenuItem("Edit", "Unparent Entity");
    Menu.removeMenuItem("Edit", "Entity List...");
    Menu.removeMenuItem("Edit", "Allow Selecting of Large Models");
    Menu.removeMenuItem("Edit", "Allow Selecting of Small Models");
    Menu.removeMenuItem("Edit", "Allow Selecting of Lights");
    Menu.removeMenuItem("Edit", "Select All Entities In Box");
    Menu.removeMenuItem("Edit", "Select All Entities Touching Box");

    Menu.removeMenuItem("Edit", "Export Entities");
    Menu.removeMenuItem("Edit", "Import Entities");
    Menu.removeMenuItem("Edit", "Import Entities from URL");

    Menu.removeMenuItem("Edit", MENU_AUTO_FOCUS_ON_SELECT);
    Menu.removeMenuItem("Edit", MENU_EASE_ON_FOCUS);
    Menu.removeMenuItem("Edit", MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE);
    Menu.removeMenuItem("Edit", MENU_SHOW_ZONES_IN_EDIT_MODE);
}

Script.scriptEnding.connect(function () {
    toolBar.setActive(false);
    Settings.setValue(SETTING_AUTO_FOCUS_ON_SELECT, Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT));
    Settings.setValue(SETTING_EASE_ON_FOCUS, Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
    Settings.setValue(SETTING_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE));
    Settings.setValue(SETTING_SHOW_ZONES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE));

    progressDialog.cleanup();
    cleanupModelMenus();
    tooltip.cleanup();
    selectionDisplay.cleanup();
    Entities.setLightsArePickable(originalLightsArePickable);

    Overlays.deleteOverlay(importingSVOImageOverlay);
    Overlays.deleteOverlay(importingSVOTextOverlay);

    Controller.keyReleaseEvent.disconnect(keyReleaseEvent);
    Controller.keyPressEvent.disconnect(keyPressEvent);

    Controller.mousePressEvent.disconnect(mousePressEvent);
    Controller.mouseMoveEvent.disconnect(mouseMoveEventBuffered);
    Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent);

    Messages.messageReceived.disconnect(handleOverlaySelectionToolUpdates);
    Messages.unsubscribe("entityToolUpdates");
});

var lastOrientation = null;
var lastPosition = null;

// Do some stuff regularly, like check for placement of various overlays
Script.update.connect(function (deltaTime) {
    progressDialog.move();
    selectionDisplay.checkMove();
    var dOrientation = Math.abs(Quat.dot(Camera.orientation, lastOrientation) - 1);
    var dPosition = Vec3.distance(Camera.position, lastPosition);
    if (dOrientation > 0.001 || dPosition > 0.001) {
        propertyMenu.hide();
        lastOrientation = Camera.orientation;
        lastPosition = Camera.position;
    }
    if (lastMouseMoveEvent) {
        mouseMove(lastMouseMoveEvent);
        lastMouseMoveEvent = null;
    }
});

function insideBox(center, dimensions, point) {
    return (Math.abs(point.x - center.x) <= (dimensions.x / 2.0)) &&
           (Math.abs(point.y - center.y) <= (dimensions.y / 2.0)) &&
           (Math.abs(point.z - center.z) <= (dimensions.z / 2.0));
}

function selectAllEtitiesInCurrentSelectionBox(keepIfTouching) {
    if (selectionManager.hasSelection()) {
        // Get all entities touching the bounding box of the current selection
        var boundingBoxCorner = Vec3.subtract(selectionManager.worldPosition,
            Vec3.multiply(selectionManager.worldDimensions, 0.5));
        var entities = Entities.findEntitiesInBox(boundingBoxCorner, selectionManager.worldDimensions);

        if (!keepIfTouching) {
            var isValid;
            if (selectionManager.localPosition === null || selectionManager.localPosition === undefined) {
                isValid = function (position) {
                    return insideBox(selectionManager.worldPosition, selectionManager.worldDimensions, position);
                };
            } else {
                isValid = function (position) {
                    var localPosition = Vec3.multiplyQbyV(Quat.inverse(selectionManager.localRotation),
                        Vec3.subtract(position,
                            selectionManager.localPosition));
                    return insideBox({
                        x: 0,
                        y: 0,
                        z: 0
                    }, selectionManager.localDimensions, localPosition);
                };
            }
            for (var i = 0; i < entities.length; ++i) {
                var properties = Entities.getEntityProperties(entities[i]);
                if (!isValid(properties.position)) {
                    entities.splice(i, 1);
                    --i;
                }
            }
        }
        selectionManager.setSelections(entities);
    }
}

function sortSelectedEntities(selected) {
    var sortedEntities = selected.slice();
    var begin = 0;
    while (begin < sortedEntities.length) {
        var elementRemoved = false;
        var next = begin + 1;
        while (next < sortedEntities.length) {
            var beginID = sortedEntities[begin];
            var nextID = sortedEntities[next];

            if (Entities.isChildOfParent(beginID, nextID)) {
                sortedEntities[begin] = nextID;
                sortedEntities[next] = beginID;
                sortedEntities.splice(next, 1);
                elementRemoved = true;
                break;
            } else if (Entities.isChildOfParent(nextID, beginID)) {
                sortedEntities.splice(next, 1);
                elementRemoved = true;
                break;
            }
            next++;
        }
        if (!elementRemoved) {
            begin++;
        }
    }
    return sortedEntities;
}

function recursiveDelete(entities, childrenList) {
    var entitiesLength = entities.length;
    for (var i = 0; i < entitiesLength; i++) {
        var entityID = entities[i];
        var children = Entities.getChildrenIDs(entityID);
        var grandchildrenList = [];
        recursiveDelete(children, grandchildrenList);
        var initialProperties = Entities.getEntityProperties(entityID);
        childrenList.push({
            entityID: entityID,
            properties: initialProperties,
            children: grandchildrenList
        });
        Entities.deleteEntity(entityID);
    }
}
function unparentSelectedEntities() {
    if (SelectionManager.hasSelection()) {
        var selectedEntities = selectionManager.selections;
        var parentCheck = false;

        if (selectedEntities.length < 1) {
            Window.notifyEditError("You must have an entity selected inorder to unparent it.");
            return;
        }
        selectedEntities.forEach(function (id, index) {
            var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID;
            if (parentId !== null && parentId.length > 0 && parentId !== "{00000000-0000-0000-0000-000000000000}") {
                parentCheck = true;
            }
            Entities.editEntity(id, {parentID: null})
            return true;
        });
        if (parentCheck) {
            if (selectedEntities.length > 1) {
                Window.notify("Entities unparented");
            } else {
                Window.notify("Entity unparented");
            }
        } else {
            if (selectedEntities.length > 1) {
                Window.notify("Selected Entities have no parents");
            } else {
                Window.notify("Selected Entity does not have a parent");
            }
        }
    } else {
        Window.notifyEditError("You have nothing selected to unparent");
    }
}
function parentSelectedEntities() {
    if (SelectionManager.hasSelection()) {
        var selectedEntities = selectionManager.selections;
        if (selectedEntities.length <= 1) {
            Window.notifyEditError("You must have multiple entities selected in order to parent them");
            return;
        }
        var parentCheck = false;
        var lastEntityId = selectedEntities[selectedEntities.length-1];
        selectedEntities.forEach(function (id, index) {
            if (lastEntityId !== id) {
                var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID;
                if (parentId !== lastEntityId) {
                    parentCheck = true;
                }
                Entities.editEntity(id, {parentID: lastEntityId})
            }
        });

        if(parentCheck) {
            Window.notify("Entities parented");
        }else {
            Window.notify("Entities are already parented to last");
        }
    } else {
        Window.notifyEditError("You have nothing selected to parent");
    }
}
function deleteSelectedEntities() {
    if (SelectionManager.hasSelection()) {
        selectedParticleEntityID = null;
        particleExplorerTool.destroyWebView();
        SelectionManager.saveProperties();
        var savedProperties = [];
        var newSortedSelection = sortSelectedEntities(selectionManager.selections);
        for (var i = 0; i < newSortedSelection.length; i++) {
            var entityID = newSortedSelection[i];
            var initialProperties = SelectionManager.savedProperties[entityID];
            var children = Entities.getChildrenIDs(entityID);
            var childList = [];
            recursiveDelete(children, childList);
            savedProperties.push({
                entityID: entityID,
                properties: initialProperties,
                children: childList
            });
            Entities.deleteEntity(entityID);
        }
        SelectionManager.clearSelections();
        pushCommandForSelections([], savedProperties);
    }
}

function toggleSelectedEntitiesLocked() {
    if (SelectionManager.hasSelection()) {
        var locked = !Entities.getEntityProperties(SelectionManager.selections[0], ["locked"]).locked;
        for (var i = 0; i < selectionManager.selections.length; i++) {
            var entityID = SelectionManager.selections[i];
            Entities.editEntity(entityID, {
                locked: locked
            });
        }
        entityListTool.sendUpdate();
        selectionManager._update();
    }
}

function toggleSelectedEntitiesVisible() {
    if (SelectionManager.hasSelection()) {
        var visible = !Entities.getEntityProperties(SelectionManager.selections[0], ["visible"]).visible;
        for (var i = 0; i < selectionManager.selections.length; i++) {
            var entityID = SelectionManager.selections[i];
            Entities.editEntity(entityID, {
                visible: visible
            });
        }
        entityListTool.sendUpdate();
        selectionManager._update();
    }
}

function handeMenuEvent(menuItem) {
    if (menuItem === "Allow Selecting of Small Models") {
        allowSmallModels = Menu.isOptionChecked("Allow Selecting of Small Models");
    } else if (menuItem === "Allow Selecting of Large Models") {
        allowLargeModels = Menu.isOptionChecked("Allow Selecting of Large Models");
    } else if (menuItem === "Allow Selecting of Lights") {
        Entities.setLightsArePickable(Menu.isOptionChecked("Allow Selecting of Lights"));
    } else if (menuItem === "Delete") {
        deleteSelectedEntities();
    } else if (menuItem === "Parent Entity to Last") {
        parentSelectedEntities();
    } else if (menuItem === "Unparent Entity") {
        unparentSelectedEntities();
    } else if (menuItem === "Export Entities") {
        if (!selectionManager.hasSelection()) {
            Window.notifyEditError("No entities have been selected.");
        } else {
            var filename = Window.save("Select Where to Save", "", "*.json");
            if (filename) {
                var success = Clipboard.exportEntities(filename, selectionManager.selections);
                if (!success) {
                    Window.notifyEditError("Export failed.");
                }
            }
        }
    } else if (menuItem === "Import Entities" || menuItem === "Import Entities from URL") {
        var importURL = null;
        if (menuItem === "Import Entities") {
            var fullPath = Window.browse("Select Model to Import", "", "*.json");
            if (fullPath) {
                importURL = "file:///" + fullPath;
            }
        } else {
            importURL = Window.prompt("URL of SVO to import", "");
        }

        if (importURL) {
            if (!isActive && (Entities.canRez() && Entities.canRezTmp())) {
                toolBar.toggle();
            }
            importSVO(importURL);
        }
    } else if (menuItem === "Entity List...") {
        entityListTool.toggleVisible();
    } else if (menuItem === "Select All Entities In Box") {
        selectAllEtitiesInCurrentSelectionBox(false);
    } else if (menuItem === "Select All Entities Touching Box") {
        selectAllEtitiesInCurrentSelectionBox(true);
    } else if (menuItem === MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE) {
        entityIconOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE));
    } else if (menuItem === MENU_SHOW_ZONES_IN_EDIT_MODE) {
        Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE));
    }
    tooltip.show(false);
}
function getPositionToCreateEntity() {
    var HALF_TREE_SCALE = 16384;
    var direction = Quat.getForward(MyAvatar.orientation);
    var distance = 1;
    var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, distance));

    if (Camera.mode === "entity" || Camera.mode === "independent") {
        position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), distance));
    }
    position.y += 0.5;
    if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) {
        return null;
    }
    return position;
}

function getPositionToImportEntity() {
    var dimensions = Clipboard.getContentsDimensions();
    var HALF_TREE_SCALE = 16384;
    var direction = Quat.getForward(MyAvatar.orientation);
    var longest = 1;
    longest = Math.sqrt(Math.pow(dimensions.x, 2) + Math.pow(dimensions.z, 2));
    var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, longest));

    if (Camera.mode === "entity" || Camera.mode === "independent") {
        position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getForward(Camera.orientation), longest));
    }

    if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) {
        return null;
    }

    return position;
}
function importSVO(importURL) {
    if (!Entities.canRez() && !Entities.canRezTmp()) {
        Window.notifyEditError(INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG);
        return;
    }

    Overlays.editOverlay(importingSVOTextOverlay, {
        visible: true
    });
    Overlays.editOverlay(importingSVOImageOverlay, {
        visible: true
    });

    var success = Clipboard.importEntities(importURL);

    if (success) {
        var VERY_LARGE = 10000;
        var position = {
            x: 0,
            y: 0,
            z: 0
        };
        if (Clipboard.getClipboardContentsLargestDimension() < VERY_LARGE) {
            position = getPositionToImportEntity();
        }
        if (position !== null && position !== undefined) {
            var pastedEntityIDs = Clipboard.pasteEntities(position);

            if (isActive) {
                selectionManager.setSelections(pastedEntityIDs);
            }
        } else {
            Window.notifyEditError("Can't import objects: objects would be out of bounds.");
        }
    } else {
        Window.notifyEditError("There was an error importing the entity file.");
    }

    Overlays.editOverlay(importingSVOTextOverlay, {
        visible: false
    });
    Overlays.editOverlay(importingSVOImageOverlay, {
        visible: false
    });
}
Window.svoImportRequested.connect(importSVO);

Menu.menuItemEvent.connect(handeMenuEvent);

var keyPressEvent = function (event) {
    if (isActive) {
        cameraManager.keyPressEvent(event);
    }
};
var keyReleaseEvent = function (event) {
    if (isActive) {
        cameraManager.keyReleaseEvent(event);
    }
    // since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items
    if (event.text === "DELETE") {
        deleteSelectedEntities();
    } else if (event.text === "ESC") {
        selectionManager.clearSelections();
    } else if (event.text === "TAB") {
        selectionDisplay.toggleSpaceMode();
    } else if (event.text === "f") {
        if (isActive) {
            if (selectionManager.hasSelection()) {
                cameraManager.enable();
                cameraManager.focus(selectionManager.worldPosition,
                    selectionManager.worldDimensions,
                    Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
            }
        }
    } else if (event.text === '[') {
        if (isActive) {
            cameraManager.enable();
        }
    } else if (event.text === 'g') {
        if (isActive && selectionManager.hasSelection()) {
            var newPosition = selectionManager.worldPosition;
            newPosition = Vec3.subtract(newPosition, {
                x: 0,
                y: selectionManager.worldDimensions.y * 0.5,
                z: 0
            });
            grid.setPosition(newPosition);
        }
    } else if (event.key === KEY_P && event.isControl && !event.isAutoRepeat ) {
        if (event.isShifted) {
            unparentSelectedEntities();
        } else {
            parentSelectedEntities();
        }
    }
};
Controller.keyReleaseEvent.connect(keyReleaseEvent);
Controller.keyPressEvent.connect(keyPressEvent);

function recursiveAdd(newParentID, parentData) {
    var children = parentData.children;
    for (var i = 0; i < children.length; i++) {
        var childProperties = children[i].properties;
        childProperties.parentID = newParentID;
        var newChildID = Entities.addEntity(childProperties);
        recursiveAdd(newChildID, children[i]);
    }
}

// When an entity has been deleted we need a way to "undo" this deletion.  Because it's not currently
// possible to create an entity with a specific id, earlier undo commands to the deleted entity
// will fail if there isn't a way to find the new entity id.
var DELETED_ENTITY_MAP = {};

function applyEntityProperties(data) {
    var properties = data.setProperties;
    var selectedEntityIDs = [];
    var i, entityID;
    for (i = 0; i < properties.length; i++) {
        entityID = properties[i].entityID;
        if (DELETED_ENTITY_MAP[entityID] !== undefined) {
            entityID = DELETED_ENTITY_MAP[entityID];
        }
        Entities.editEntity(entityID, properties[i].properties);
        selectedEntityIDs.push(entityID);
    }
    for (i = 0; i < data.createEntities.length; i++) {
        entityID = data.createEntities[i].entityID;
        var entityProperties = data.createEntities[i].properties;
        var newEntityID = Entities.addEntity(entityProperties);
        recursiveAdd(newEntityID, data.createEntities[i]);
        DELETED_ENTITY_MAP[entityID] = newEntityID;
        if (data.selectCreated) {
            selectedEntityIDs.push(newEntityID);
        }
    }
    for (i = 0; i < data.deleteEntities.length; i++) {
        entityID = data.deleteEntities[i].entityID;
        if (DELETED_ENTITY_MAP[entityID] !== undefined) {
            entityID = DELETED_ENTITY_MAP[entityID];
        }
        Entities.deleteEntity(entityID);
    }

    selectionManager.setSelections(selectedEntityIDs);
}

// For currently selected entities, push a command to the UndoStack that uses the current entity properties for the
// redo command, and the saved properties for the undo command.  Also, include create and delete entity data.
function pushCommandForSelections(createdEntityData, deletedEntityData) {
    var undoData = {
        setProperties: [],
        createEntities: deletedEntityData || [],
        deleteEntities: createdEntityData || [],
        selectCreated: true
    };
    var redoData = {
        setProperties: [],
        createEntities: createdEntityData || [],
        deleteEntities: deletedEntityData || [],
        selectCreated: false
    };
    for (var i = 0; i < SelectionManager.selections.length; i++) {
        var entityID = SelectionManager.selections[i];
        var initialProperties = SelectionManager.savedProperties[entityID];
        var currentProperties = Entities.getEntityProperties(entityID);
        if (!initialProperties) {
            continue;
        }
        undoData.setProperties.push({
            entityID: entityID,
            properties: initialProperties
        });
        redoData.setProperties.push({
            entityID: entityID,
            properties: currentProperties
        });
    }
    UndoStack.pushCommand(applyEntityProperties, undoData, applyEntityProperties, redoData);
}

var ENTITY_PROPERTIES_URL = Script.resolvePath('html/entityProperties.html');

var ServerScriptStatusMonitor = function(entityID, statusCallback) {
    var self = this;

    self.entityID = entityID;
    self.active = true;
    self.sendRequestTimerID = null;

    var onStatusReceived = function(success, isRunning, status, errorInfo) {
        if (self.active) {
            statusCallback({
                statusRetrieved: success,
                isRunning: isRunning,
                status: status,
                errorInfo: errorInfo
            });
            self.sendRequestTimerID = Script.setTimeout(function() {
                if (self.active) {
                    Entities.getServerScriptStatus(entityID, onStatusReceived);
                }
            }, 1000);
        }
    };
    self.stop = function() {
        self.active = false;
    };

    Entities.getServerScriptStatus(entityID, onStatusReceived);
};

var PropertiesTool = function (opts) {
    var that = {};

    var webView = null;
    webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
    webView.setVisible = function(value) {};

    var visible = false;

    // This keeps track of the last entity ID that was selected. If multiple entities
    // are selected or if no entity is selected this will be `null`.
    var currentSelectedEntityID = null;
    var statusMonitor = null;

    webView.setVisible(visible);

    that.setVisible = function (newVisible) {
        visible = newVisible;
        webView.setVisible(visible);
    };

    function updateScriptStatus(info) {
        info.type = "server_script_status";
        webView.emitScriptEvent(JSON.stringify(info));
    }

    function resetScriptStatus() {
        updateScriptStatus({
            statusRetrieved: undefined,
            isRunning: undefined,
            status: "",
            errorInfo: ""
        });
    }

    function updateSelections(selectionUpdated) {
        var data = {
            type: 'update'
        };

        if (selectionUpdated) {
            resetScriptStatus();

            if (selectionManager.selections.length !== 1) {
                if (statusMonitor !== null) {
                    statusMonitor.stop();
                    statusMonitor = null;
                }
                currentSelectedEntityID = null;
            } else if (currentSelectedEntityID != selectionManager.selections[0]) {
                if (statusMonitor !== null) {
                    statusMonitor.stop();
                }
                var entityID = selectionManager.selections[0];
                currentSelectedEntityID = entityID;
                statusMonitor = new ServerScriptStatusMonitor(entityID, updateScriptStatus);
            }
        }

        var selections = [];
        for (var i = 0; i < selectionManager.selections.length; i++) {
            var entity = {};
            entity.id = selectionManager.selections[i];
            entity.properties = Entities.getEntityProperties(selectionManager.selections[i]);
            if (entity.properties.rotation !== undefined) {
                entity.properties.rotation = Quat.safeEulerAngles(entity.properties.rotation);
            }
            if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) {
                entity.properties.keyLight.direction = Vec3.multiply(RADIANS_TO_DEGREES,
                                                                     Vec3.toPolar(entity.properties.keyLight.direction));
                entity.properties.keyLight.direction.z = 0.0;
            }
            selections.push(entity);
        }
        data.selections = selections;
        webView.emitScriptEvent(JSON.stringify(data));
    }
    selectionManager.addEventListener(updateSelections);

    webView.webEventReceived.connect(function (data) {
        try {
            data = JSON.parse(data);
        }
        catch(e) {
            print('Edit.js received web event that was not valid json.');
            return;
        }
        var i, properties, dY, diff, newPosition;
        if (data.type === "print") {
            if (data.message) {
                print(data.message);
            }
        } else if (data.type === "update") {
            selectionManager.saveProperties();
            if (selectionManager.selections.length > 1) {
                properties = {
                    locked: data.properties.locked,
                    visible: data.properties.visible
                };
                for (i = 0; i < selectionManager.selections.length; i++) {
                    Entities.editEntity(selectionManager.selections[i], properties);
                }
            } else if (data.properties) {
                if (data.properties.dynamic === false) {
                    // this object is leaving dynamic, so we zero its velocities
                    data.properties.velocity = {
                        x: 0,
                        y: 0,
                        z: 0
                    };
                    data.properties.angularVelocity = {
                        x: 0,
                        y: 0,
                        z: 0
                    };
                }
                if (data.properties.rotation !== undefined) {
                    var rotation = data.properties.rotation;
                    data.properties.rotation = Quat.fromPitchYawRollDegrees(rotation.x, rotation.y, rotation.z);
                }
                if (data.properties.keyLight !== undefined && data.properties.keyLight.direction !== undefined) {
                    data.properties.keyLight.direction = Vec3.fromPolar(
                        data.properties.keyLight.direction.x * DEGREES_TO_RADIANS,
                        data.properties.keyLight.direction.y * DEGREES_TO_RADIANS
                    );
                }
                Entities.editEntity(selectionManager.selections[0], data.properties);
                if (data.properties.name !== undefined || data.properties.modelURL !== undefined ||
                        data.properties.visible !== undefined || data.properties.locked !== undefined) {
                    entityListTool.sendUpdate();
                }
            }
            pushCommandForSelections();
            selectionManager._update();
        } else if(data.type === 'parent') {
            parentSelectedEntities();
        } else if(data.type === 'unparent') {
            unparentSelectedEntities();
        } else if(data.type === 'saveUserData'){
            //the event bridge and json parsing handle our avatar id string differently.
            var actualID = data.id.split('"')[1];
            Entities.editEntity(actualID, data.properties);
        } else if (data.type === "showMarketplace") {
            showMarketplace();
        } else if (data.type === "action") {
            if (data.action === "moveSelectionToGrid") {
                if (selectionManager.hasSelection()) {
                    selectionManager.saveProperties();
                    dY = grid.getOrigin().y - (selectionManager.worldPosition.y - selectionManager.worldDimensions.y / 2);
                    diff = {
                        x: 0,
                        y: dY,
                        z: 0
                    };
                    for (i = 0; i < selectionManager.selections.length; i++) {
                        properties = selectionManager.savedProperties[selectionManager.selections[i]];
                        newPosition = Vec3.sum(properties.position, diff);
                        Entities.editEntity(selectionManager.selections[i], {
                            position: newPosition
                        });
                    }
                    pushCommandForSelections();
                    selectionManager._update();
                }
            } else if (data.action === "moveAllToGrid") {
                if (selectionManager.hasSelection()) {
                    selectionManager.saveProperties();
                    for (i = 0; i < selectionManager.selections.length; i++) {
                        properties = selectionManager.savedProperties[selectionManager.selections[i]];
                        var bottomY = properties.boundingBox.center.y - properties.boundingBox.dimensions.y / 2;
                        dY = grid.getOrigin().y - bottomY;
                        diff = {
                            x: 0,
                            y: dY,
                            z: 0
                        };
                        newPosition = Vec3.sum(properties.position, diff);
                        Entities.editEntity(selectionManager.selections[i], {
                            position: newPosition
                        });
                    }
                    pushCommandForSelections();
                    selectionManager._update();
                }
            } else if (data.action === "resetToNaturalDimensions") {
                if (selectionManager.hasSelection()) {
                    selectionManager.saveProperties();
                    for (i = 0; i < selectionManager.selections.length; i++) {
                        properties = selectionManager.savedProperties[selectionManager.selections[i]];
                        var naturalDimensions = properties.naturalDimensions;

                        // If any of the natural dimensions are not 0, resize
                        if (properties.type === "Model" && naturalDimensions.x === 0 && naturalDimensions.y === 0 &&
                                naturalDimensions.z === 0) {
                            Window.notifyEditError("Cannot reset entity to its natural dimensions: Model URL" +
                                         " is invalid or the model has not yet been loaded.");
                        } else {
                            Entities.editEntity(selectionManager.selections[i], {
                                dimensions: properties.naturalDimensions
                            });
                        }
                    }
                    pushCommandForSelections();
                    selectionManager._update();
                }
            } else if (data.action === "previewCamera") {
                if (selectionManager.hasSelection()) {
                    Camera.mode = "entity";
                    Camera.cameraEntity = selectionManager.selections[0];
                }
            } else if (data.action === "rescaleDimensions") {
                var multiplier = data.percentage / 100.0;
                if (selectionManager.hasSelection()) {
                    selectionManager.saveProperties();
                    for (i = 0; i < selectionManager.selections.length; i++) {
                        properties = selectionManager.savedProperties[selectionManager.selections[i]];
                        Entities.editEntity(selectionManager.selections[i], {
                            dimensions: Vec3.multiply(multiplier, properties.dimensions)
                        });
                    }
                    pushCommandForSelections();
                    selectionManager._update();
                }
            } else if (data.action === "reloadClientScripts") {
                if (selectionManager.hasSelection()) {
                    var timestamp = Date.now();
                    for (i = 0; i < selectionManager.selections.length; i++) {
                        Entities.editEntity(selectionManager.selections[i], {
                            scriptTimestamp: timestamp
                        });
                    }
                }
            } else if (data.action === "reloadServerScripts") {
                if (selectionManager.hasSelection()) {
                    for (i = 0; i < selectionManager.selections.length; i++) {
                        Entities.reloadServerScripts(selectionManager.selections[i]);
                    }
                }
            }
        } else if (data.type === "propertiesPageReady") {
            updateSelections(true);
        }
    });

    return that;
};

var PopupMenu = function () {
    var self = this;

    var MENU_ITEM_HEIGHT = 21;
    var MENU_ITEM_SPACING = 1;
    var TEXT_MARGIN = 7;

    var overlays = [];
    var overlayInfo = {};

    var upColor = {
        red: 0,
        green: 0,
        blue: 0
    };
    var downColor = {
        red: 192,
        green: 192,
        blue: 192
    };
    var overColor = {
        red: 128,
        green: 128,
        blue: 128
    };

    self.onSelectMenuItem = function () {};

    self.addMenuItem = function (name) {
        var id = Overlays.addOverlay("text", {
            text: name,
            backgroundAlpha: 1.0,
            backgroundColor: upColor,
            topMargin: TEXT_MARGIN,
            leftMargin: TEXT_MARGIN,
            width: 210,
            height: MENU_ITEM_HEIGHT,
            font: {
                size: 12
            },
            visible: false
        });
        overlays.push(id);
        overlayInfo[id] = {
            name: name
        };
        return id;
    };

    self.updateMenuItemText = function (id, newText) {
        Overlays.editOverlay(id, {
            text: newText
        });
    };

    self.setPosition = function (x, y) {
        for (var key in overlayInfo) {
            Overlays.editOverlay(key, {
                x: x,
                y: y
            });
            y += MENU_ITEM_HEIGHT + MENU_ITEM_SPACING;
        }
    };

    self.onSelected = function () {};

    var pressingOverlay = null;
    var hoveringOverlay = null;

    self.mousePressEvent = function (event) {
        if (event.isLeftButton) {
            var overlay = Overlays.getOverlayAtPoint({
                x: event.x,
                y: event.y
            });
            if (overlay in overlayInfo) {
                pressingOverlay = overlay;
                Overlays.editOverlay(pressingOverlay, {
                    backgroundColor: downColor
                });
            } else {
                self.hide();
            }
            return false;
        }
    };
    self.mouseMoveEvent = function (event) {
        if (visible) {
            var overlay = Overlays.getOverlayAtPoint({
                x: event.x,
                y: event.y
            });
            if (!pressingOverlay) {
                if (hoveringOverlay !== null && hoveringOverlay !== null && overlay !== hoveringOverlay) {
                    Overlays.editOverlay(hoveringOverlay, {
                        backgroundColor: upColor
                    });
                    hoveringOverlay = null;
                }
                if (overlay !== hoveringOverlay && overlay in overlayInfo) {
                    Overlays.editOverlay(overlay, {
                        backgroundColor: overColor
                    });
                    hoveringOverlay = overlay;
                }
            }
        }
        return false;
    };
    self.mouseReleaseEvent = function (event) {
        var overlay = Overlays.getOverlayAtPoint({
            x: event.x,
            y: event.y
        });
        if (pressingOverlay !== null && pressingOverlay !== undefined) {
            if (overlay === pressingOverlay) {
                self.onSelectMenuItem(overlayInfo[overlay].name);
            }
            Overlays.editOverlay(pressingOverlay, {
                backgroundColor: upColor
            });
            pressingOverlay = null;
            self.hide();
        }
    };

    var visible = false;

    self.setVisible = function (newVisible) {
        if (newVisible !== visible) {
            visible = newVisible;
            for (var key in overlayInfo) {
                Overlays.editOverlay(key, {
                    visible: newVisible
                });
            }
        }
    };
    self.show = function () {
        self.setVisible(true);
    };
    self.hide = function () {
        self.setVisible(false);
    };

    function cleanup() {
        for (var i = 0; i < overlays.length; i++) {
            Overlays.deleteOverlay(overlays[i]);
        }
        Controller.mousePressEvent.disconnect(self.mousePressEvent);
        Controller.mouseMoveEvent.disconnect(self.mouseMoveEvent);
        Controller.mouseReleaseEvent.disconnect(self.mouseReleaseEvent);
    }

    Controller.mousePressEvent.connect(self.mousePressEvent);
    Controller.mouseMoveEvent.connect(self.mouseMoveEvent);
    Controller.mouseReleaseEvent.connect(self.mouseReleaseEvent);
    Script.scriptEnding.connect(cleanup);

    return this;
};


var propertyMenu = new PopupMenu();

propertyMenu.onSelectMenuItem = function (name) {

    if (propertyMenu.marketplaceID) {
        showMarketplace(propertyMenu.marketplaceID);
    }
};

var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace");

var propertiesTool = new PropertiesTool();
var particleExplorerTool = new ParticleExplorerTool();
var selectedParticleEntity = 0;
var selectedParticleEntityID = null;


function selectParticleEntity(entityID) {
    var properties = Entities.getEntityProperties(entityID);
    var particleData = {
        messageType: "particle_settings",
        currentProperties: properties
    };
    particleExplorerTool.destroyWebView();
    particleExplorerTool.createWebView();

    selectedParticleEntity = entityID;
    particleExplorerTool.setActiveParticleEntity(entityID);
    particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));    

    // Switch to particle explorer
    var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
    tablet.sendToQml({method: 'selectTab', params: {id: 'particle'}});
}

entityListTool.webView.webEventReceived.connect(function (data) {
    data = JSON.parse(data);
    if (data.type === 'parent') {
        parentSelectedEntities();
    } else if(data.type === 'unparent') {
        unparentSelectedEntities();
    } else if (data.type === "selectionUpdate") {
        var ids = data.entityIds;
        if (ids.length === 1) {
            if (Entities.getEntityProperties(ids[0], "type").type === "ParticleEffect") {
                if (JSON.stringify(selectedParticleEntity) === JSON.stringify(ids[0])) {
                    // This particle entity is already selected, so return
                    return;
                }
                // Destroy the old particles web view first
                selectParticleEntity(ids[0]);
            } else {
                selectedParticleEntity = 0;
                particleExplorerTool.destroyWebView();
            }
        }
    }
});

}()); // END LOCAL_SCOPE