//
//  attachedEntitiesManager.js
//
//  Created by Seth Alves on 2016-1-20
//  Copyright 2016 High Fidelity, Inc.
//
//  This script handles messages from the grab script related to wearables, and interacts with a doppelganger.
//
//  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/utils.js");

var NULL_UUID = "{00000000-0000-0000-0000-000000000000}";
var DEFAULT_WEARABLE_DATA = {
    joints: {}
};


var MINIMUM_DROP_DISTANCE_FROM_JOINT = 0.4;
var ATTACHED_ENTITY_SEARCH_DISTANCE = 10.0;
var ATTACHED_ENTITIES_SETTINGS_KEY = "ATTACHED_ENTITIES";
var DRESSING_ROOM_DISTANCE = 2.0;

// tool bar

HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var BUTTON_SIZE = 32;
var PADDING = 3;
Script.include(["libraries/toolBars.js"]);
var toolBar = new ToolBar(0, 0, ToolBar.VERTICAL, "highfidelity.attachedEntities.toolbar", function(screenSize) {
    return {
        x: (BUTTON_SIZE + PADDING),
        y: (screenSize.y / 2 - BUTTON_SIZE * 2 + PADDING)
    };
});
var saveButton = toolBar.addOverlay("image", {
    width: BUTTON_SIZE,
    height: BUTTON_SIZE,
    imageURL: "http://headache.hungry.com/~seth/hifi/save.png",
    color: {
        red: 255,
        green: 255,
        blue: 255
    },
    alpha: 1
});
var loadButton = toolBar.addOverlay("image", {
    width: BUTTON_SIZE,
    height: BUTTON_SIZE,
    imageURL: "http://headache.hungry.com/~seth/hifi/load.png",
    color: {
        red: 255,
        green: 255,
        blue: 255
    },
    alpha: 1
});


function mousePressEvent(event) {
    var clickedOverlay = Overlays.getOverlayAtPoint({
        x: event.x,
        y: event.y
    });

    if (clickedOverlay == saveButton) {
        manager.saveAttachedEntities();
    } else if (clickedOverlay == loadButton) {
        manager.loadAttachedEntities();
    }
}

function scriptEnding() {
  toolBar.cleanup();
}

Controller.mousePressEvent.connect(mousePressEvent);
Script.scriptEnding.connect(scriptEnding);




// attached entites


function AttachedEntitiesManager() {
    this.subscribeToMessages = function() {
        Messages.subscribe('Hifi-Object-Manipulation');
        Messages.messageReceived.connect(this.handleWearableMessages);
    }

    this.handleWearableMessages = function(channel, message, sender) {
        if (channel !== 'Hifi-Object-Manipulation') {
            return;
        }
        // if (sender !== MyAvatar.sessionUUID) {
        //     print('wearablesManager got message from wrong sender');
        //     return;
        // }

        var parsedMessage = null;

        try {
            parsedMessage = JSON.parse(message);
        } catch (e) {
            print('error parsing wearable message');
            return;
        }

        if (parsedMessage.action === 'update' ||
            parsedMessage.action === 'loaded') {
            // ignore
        } else if (parsedMessage.action === 'release') {
            manager.checkIfWearable(parsedMessage.grabbedEntity, parsedMessage.joint)
            // manager.saveAttachedEntities();
        } else if (parsedMessage.action === 'shared-release') {
            // manager.saveAttachedEntities();
        } else if (parsedMessage.action === 'equip') {
            // manager.saveAttachedEntities();
        } else {
            print('attachedEntitiesManager -- unknown actions: ' + parsedMessage.action);
        }
    }

    this.avatarIsInDressingRoom = function() {
        // return true if MyAvatar is near the dressing room
        var possibleDressingRoom = Entities.findEntities(MyAvatar.position, DRESSING_ROOM_DISTANCE);
        for (i = 0; i < possibleDressingRoom.length; i++) {
            var entityID = possibleDressingRoom[i];
            var props = Entities.getEntityProperties(entityID);
            if (props.name == 'Hifi-Dressing-Room-Base') {
                return true;
            }
        }
        return false;
    }

    this.checkIfWearable = function(grabbedEntity, releasedFromJoint) {
        var allowedJoints = getEntityCustomData('wearable', grabbedEntity, DEFAULT_WEARABLE_DATA).joints;

        var props = Entities.getEntityProperties(grabbedEntity, ["position", "parentID"]);
        if (props.parentID === NULL_UUID || props.parentID === MyAvatar.sessionUUID) {
            var bestJointName = "";
            var bestJointIndex = -1;
            var bestJointDistance = 0;
            var bestJointOffset = null;
            for (var jointName in allowedJoints) {
                if ((releasedFromJoint == "LeftHand" || releasedFromJoint == "RightHand") &&
                    (jointName == "LeftHand" || jointName == "RightHand")) {
                    // don't auto-attach to a hand if a hand just dropped something
                    continue;
                }
                var jointIndex = MyAvatar.getJointIndex(jointName);
                if (jointIndex > 0) {
                    var jointPosition = MyAvatar.getJointPosition(jointIndex);
                    var distanceFromJoint = Vec3.distance(jointPosition, props.position);
                    if (distanceFromJoint <= MINIMUM_DROP_DISTANCE_FROM_JOINT) {
                        if (bestJointIndex == -1 || distanceFromJoint < bestJointDistance) {
                            bestJointName = jointName;
                            bestJointIndex = jointIndex;
                            bestJointDistance = distanceFromJoint;
                            bestJointOffset = allowedJoints[jointName];
                        }
                    }
                }
            }

            if (bestJointIndex != -1) {
                var wearProps = {
                    parentID: MyAvatar.sessionUUID,
                    parentJointIndex: bestJointIndex
                };

                if (!this.avatarIsInDressingRoom() &&
                    bestJointOffset && bestJointOffset.constructor === Array && bestJointOffset.length > 1) {
                    // don't snap the entity to the preferred position if the avatar is in the dressing room.
                    wearProps.localPosition = bestJointOffset[0];
                    wearProps.localRotation = bestJointOffset[1];
                }
                Entities.editEntity(grabbedEntity, wearProps);
            } else if (props.parentID != NULL_UUID) {
                // drop the entity with no parent (not on the avatar)
                Entities.editEntity(grabbedEntity, {
                    parentID: NULL_UUID
                });
            }
        }
    }

    this.updateRelativeOffsets = function(entityID, props) {
        // save the preferred (current) relative position and rotation into the user-data of the entity
        var wearableData = getEntityCustomData('wearable', entityID, DEFAULT_WEARABLE_DATA);
        var currentJointName = MyAvatar.getJointNames()[props.parentJointIndex];
        wearableData.joints[currentJointName] = [props.localPosition, props.localRotation];
        setEntityCustomData('wearable', entityID, wearableData);
    }

    this.saveAttachedEntities = function() {
        print("--- saving attached entities ---");
        saveData = [];
        var nearbyEntities = Entities.findEntities(MyAvatar.position, ATTACHED_ENTITY_SEARCH_DISTANCE);
        for (i = 0; i < nearbyEntities.length; i++) {
            var entityID = nearbyEntities[i];
            var props = Entities.getEntityProperties(entityID);
            if (props.parentID == MyAvatar.sessionUUID) {
                grabData = getEntityCustomData('grabKey', entityID, {});
                grabbableData = getEntityCustomData('grabbableKey', entityID, {});
                this.updateRelativeOffsets(entityID, props);
                props = Entities.getEntityProperties(entityID); // refresh, because updateRelativeOffsets changed them
                this.scrubProperties(props);
                saveData.push(props);
            }
        }
        Settings.setValue(ATTACHED_ENTITIES_SETTINGS_KEY, JSON.stringify(saveData));
    }

    this.scrubProperties = function(props) {
        var toScrub = ["queryAACube", "position", "rotation",
                       "created", "ageAsText", "naturalDimensions",
                       "naturalPosition", "velocity", "acceleration",
                       "angularVelocity", "boundingBox"];
        toScrub.forEach(function(propertyName) {
            delete props[propertyName];
        });
        // if the userData has a grabKey, clear old state
        if ("userData" in props) {
            try {
                parsedUserData = JSON.parse(props.userData);
                if ("grabKey" in parsedUserData) {
                    parsedUserData.grabKey.refCount = 0;
                    delete parsedUserData.grabKey["avatarId"];
                    props["userData"] = JSON.stringify(parsedUserData);
                }
            } catch (e) {
            }
        }
    }

    this.loadAttachedEntities = function(grabbedEntity) {
        print("--- loading attached entities ---");
        jsonAttachmentData = Settings.getValue(ATTACHED_ENTITIES_SETTINGS_KEY);
        var loadData = [];
        try {
            loadData = JSON.parse(jsonAttachmentData);
        } catch (e) {
            print('error parsing saved attachment data');
            return;
        }

        for (i = 0; i < loadData.length; i ++) {
            var savedProps = loadData[ i ];
            var currentProps = Entities.getEntityProperties(savedProps.id);
            if (currentProps.id == savedProps.id &&
                // TODO -- also check that parentJointIndex matches?
                currentProps.parentID == MyAvatar.sessionUUID) {
                // entity is already in-world.  TODO -- patch it up?
                continue;
            }
            this.scrubProperties(savedProps);
            delete savedProps["id"];
            savedProps.parentID = MyAvatar.sessionUUID; // this will change between sessions
            var loadedEntityID = Entities.addEntity(savedProps);

            Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
                action: 'loaded',
                grabbedEntity: loadedEntityID
            }));
        }
    }
}

var manager = new AttachedEntitiesManager();
manager.subscribeToMessages();