"use strict";
/*jslint vars:true, plusplus:true, forin:true*/
/*global Tablet, Script,  */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
//
// lookAt.js
//
// Created by Zach Fox on 2018-07-30
// Copyright 2018 High Fidelity, Inc
//
// Distributed under the Apache License, Version 2.0
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//

(function () { // BEGIN LOCAL_SCOPE
    var AppUi = Script.require('appUi');

    /********************************
    // START Debug Functions
    ********************************/
    var DEBUG_UNIMPORTANT = 0;
    var DEBUG_IMPORTANT = 1;
    var DEBUG_URGENT = 2;

    var DEBUG_ONLY_PRINT_URGENT = 0;
    var DEBUG_PRINT_URGENT_AND_IMPORTANT = 1;
    var DEBUG_PRINT_EVERYTHING = 2;
    var debugLevel = DEBUG_PRINT_URGENT_AND_IMPORTANT;

    function maybePrint(string, importance) {
        if (importance >= (DEBUG_URGENT - debugLevel)) {
            console.log(string);
        }
    }
    /********************************
    // END Debug Functions
    ********************************/

    /********************************
    // START Shared Math Utility Functions
    ********************************/
    // Function Name: inFrontOf()
    //
    // Description:
    //   -Returns the position in front of the given "position" argument, where the forward vector is based off
    //    the "orientation" argument and the amount in front is based off the "distance" argument.
    function inFrontOf(distance, position, orientation) {
        return Vec3.sum(position,
            Vec3.multiply(distance, Quat.getForward(orientation)));
    }
    /********************************
    // END Shared Math Utility Functions
    ********************************/

    /********************************
    // START Hover Handler Functions
    ********************************/
    var hoverHandlersConnected = false;

    var isHoveringOverLookAtOverlay = false;
    function handleHoverEnterOverlay(overlayID, event) {
        isHoveringOverLookAtOverlay = (lookAtOverlay && overlayID === lookAtOverlay);
    }

    function handleHoverLeaveOverlay(overlayID, event) {
        var wasHoveringOverLookAtOverlay = isHoveringOverLookAtOverlay;
        justStoppedHoveringOverLookAtOverlay = wasHoveringOverLookAtOverlay && (!lookAtOverlay || overlayID === lookAtOverlay);
        if (!overlayIntersection.intersects &&
            ID_currentLookAt !== ID_actionTakenOn &&
            justStoppedHoveringOverLookAtOverlay &&
            !reverseActionTimer) {
            startReverseActionTimer();
        }
        isHoveringOverLookAtOverlay = false;
    }
    /********************************
    // END Hover Handler Functions
    ********************************/

    /********************************
    // START Click Handler Functions
    ********************************/
    function takeMuteButtonAction() {
        if (actionTakenOnAvatar) {
            Users.personalMute(ID_currentLookAt);
        } else {
            Entities.editEntity(ID_currentLookAt, {
                color: {
                    red: 30,
                    green: 30,
                    blue: 30
                }
            });
        }
        reverseAction(true);
    }

    function takeIgnoreButtonAction() {
        if (actionTakenOnAvatar) {
            Users.ignore(ID_currentLookAt);
        } else {
            Entities.editEntity(ID_currentLookAt, {
                color: {
                    red: 255,
                    green: 0,
                    blue: 0
                }
            });
        }
        reverseAction(true);
    }
    /********************************
    // END Click Handler Functions
    ********************************/

    /********************************
    // START Global Detection Start/Stop/Update functions
    ********************************/
    function maybeDeleteOverlays() {
        if (lookAtOverlayObject) {
            lookAtOverlayObject.webEventReceived.disconnect(lookAtOverlayWebEventReceived);
            lookAtOverlayObject = false;
        }

        if (lookAtOverlay) {
            Overlays.deleteOverlay(lookAtOverlay);
            lookAtOverlay = false;
        }

        if (updateLookAtOverlayConnected) {
            Script.update.disconnect(updateLookAtOverlay);
            updateLookAtOverlayConnected = false;
        }

        if (hoverHandlersConnected) {
            Overlays.hoverEnterOverlay.disconnect(handleHoverEnterOverlay);
            Overlays.hoverLeaveOverlay.disconnect(handleHoverLeaveOverlay);
            hoverHandlersConnected = false;
        }
    }

    function lookAtOverlayWebEventReceived(event) {
        event = JSON.parse(event);
        switch (event.method) {
            case 'lookAt-Overlay-Ready':
                lookAtOverlayObject.emitScriptEvent(JSON.stringify({
                    method: 'lookAt-Overlay-initializeUI',
                    entityName: (actionTakenOnAvatar ? AvatarList.getAvatar(ID_actionTakenOn).sessionDisplayName : ID_actionTakenOn)
                }));
                break;
            case 'lookAt-Overlay-Mute':
                takeMuteButtonAction();
                break;
            case 'lookAt-Overlay-Ignore':
                takeIgnoreButtonAction();
                break;
        }
    }

    function updateLookAtOverlay() {
        var editProps = {
            rotation: Camera.orientation
        }

        if (actionTakenOnAvatar && !isHoveringOverLookAtOverlay && !overlayIntersection.intersects) {
            editProps.position = AvatarList.getAvatar(ID_actionTakenOn).getJointPosition("Head");
            editProps.position.y += 0.66;
        }

        Overlays.editOverlay(lookAtOverlay, editProps);
    }

    var ID_actionTakenOn = false;
    var actionTakenOnAvatar = false;
    var lookAtOverlay = false;
    var updateLookAtOverlayConnected = false;
    var lookAtOverlayObject = false;
    var OVERLAY_DIMENSIONS = { x: 1.0, y: 0.4, z: 0.1 };
    var OVERLAY_Y_OFFSET_M = 0.1;
    function takeLookAtAction(isAvatarAction) {
        actionTakenOnAvatar = isAvatarAction;

        var timestamp = new Date();
        sendToQml({
            method: 'tookLookAtAction',
            timestamp: timestamp.getHours() + ":" + timestamp.getMinutes() + ":" + timestamp.getSeconds()
        });
        ID_actionTakenOn = ID_currentLookAt;

        var overlayPosition;
        if (isAvatarAction) {
            overlayPosition = AvatarList.getAvatar(ID_currentLookAt).getJointPosition("Head");
            overlayPosition.y += 0.66;
        } else {
            Entities.editEntity(ID_currentLookAt, {
                color: {
                    red: Math.random() * 255,
                    green: Math.random() * 255,
                    blue: Math.random() * 255
                }
            });

            var lookAtEntityProps = Entities.getEntityProperties(ID_actionTakenOn, ["position", "dimensions"]);
            overlayPosition = lookAtEntityProps.position;
            overlayPosition.y += lookAtEntityProps.dimensions.y / 2 + OVERLAY_DIMENSIONS.y / 2 + OVERLAY_Y_OFFSET_M;
        }

        maybeDeleteOverlays();

        var overlayOrientation = Camera.orientation;

        var lookAtOverlayProps = {
            name: "LookAt Overlay",
            url: "https://hifi-content.s3.amazonaws.com/zfox/lookAtApp/lookAtOverlay.html",
            position: overlayPosition,
            rotation: overlayOrientation,
            dimensions: OVERLAY_DIMENSIONS,
            dpi: 16,
            color: { red: 255, green: 255, blue: 255 },
            alpha: 1.0,
            showKeyboardFocusHighlight: false,
            visible: true,
        }
        lookAtOverlay = Overlays.addOverlay("web3d", lookAtOverlayProps);

        if (!updateLookAtOverlayConnected) {
            Script.update.connect(updateLookAtOverlay);
            updateLookAtOverlayConnected = true;
        }

        if (!hoverHandlersConnected) {
            Overlays.hoverEnterOverlay.connect(handleHoverEnterOverlay);
            Overlays.hoverLeaveOverlay.connect(handleHoverLeaveOverlay);
            hoverHandlersConnected = true;
        }

        lookAtOverlayObject = Overlays.getOverlayObject(lookAtOverlay);
        lookAtOverlayObject.webEventReceived.connect(lookAtOverlayWebEventReceived);
    }

    function reverseAction(force) {
        reverseActionTimer = false;
        if (force || ((!overlayIntersection || !overlayIntersection.intersects) &&
            ID_actionTakenOn &&
            ID_currentLookAt !== ID_actionTakenOn &&
            !isHoveringOverLookAtOverlay)) {
            maybeDeleteOverlays();

            var timestamp = new Date();
            sendToQml({
                method: 'reversedLookAtAction',
                timestamp: timestamp.getHours() + ":" + timestamp.getMinutes() + ":" + timestamp.getSeconds()
            });
            ID_actionTakenOn = false;
            isHoveringOverLookAtOverlay = false;
        }
    }

    var reverseActionTimer = false;
    var REVERSE_ACTION_TIMEOUT_MS = 750;
    function startReverseActionTimer() {
        var timestamp = Date.now();
        sendToQml({
            method: 'reverseActionTimerStarted',
            timestamp: timestamp
        });

        if (reverseActionTimer) {
            Script.clearTimeout(reverseActionTimer);
            reverseActionTimer = false;
        }

        reverseActionTimer = Script.setTimeout(function () {
            reverseAction();
        }, REVERSE_ACTION_TIMEOUT_MS);
    }

    var overlayID_currentLookAt = false;
    var ID_currentLookAt = false;
    var overlayIntersection = false;
    var lookAtTimeout = false;
    var PICK_RAY_MAX_DISTANCE = 5;
    var LOOK_AT_TIMEOUT_MS = 1000;
    function handlePickRay(isAvatarPickRay) {
        var pickRay = {
            origin: MyAvatar.position,
            direction: Quat.getFront(Camera.orientation),
            length: PICK_RAY_MAX_DISTANCE
        }

        var entityIntersection = false;
        var avatarIntersection = false;
        if (isAvatarPickRay) {
            avatarIntersection = AvatarList.findRayIntersection(pickRay, [], [MyAvatar.sessionUUID]);
        } else {
            entityIntersection = Entities.findRayIntersection(pickRay, true);
        }

        var lookAtIDChanged = false;

        if ((entityIntersection && entityIntersection.intersects) ||
            (avatarIntersection && avatarIntersection.intersects)) {
            var intersectID = entityIntersection.entityID || avatarIntersection.avatarID;

            if (ID_currentLookAt !== intersectID) {
                lookAtIDChanged = true;
                if (!isHoveringOverLookAtOverlay && ID_currentLookAt) {
                    startReverseActionTimer();
                }
                ID_currentLookAt = intersectID;

                if (lookAtTimeout) {
                    Script.clearTimeout(lookAtTimeout);
                    lookAtTimeout = false;
                }

                lookAtTimeout = Script.setTimeout(function () {
                    lookAtTimeout = false;
                    if (ID_actionTakenOn !== ID_currentLookAt) {
                        takeLookAtAction(avatarIntersection);
                    }
                }, LOOK_AT_TIMEOUT_MS);
            }
        } else {
            if (ID_currentLookAt) {
                lookAtIDChanged = true;
                startReverseActionTimer();
            }
            ID_currentLookAt = false;

            if (lookAtTimeout) {
                Script.clearTimeout(lookAtTimeout);
                lookAtTimeout = false;
            }
        }

        overlayIntersection = false;
        if (lookAtOverlay) {
            pickRay.origin = Camera.position;
            overlayIntersection = Overlays.findRayIntersection(pickRay, true, [lookAtOverlay]);

            if (!overlayIntersection.intersects &&
                (ID_currentLookAt !== ID_actionTakenOn || !ID_currentLookAt) &&
                !isHoveringOverLookAtOverlay &&
                !reverseActionTimer) {
                startReverseActionTimer();
            }
        }

        if (lookAtIDChanged) {
            sendToQml({
                method: 'lookAtIDChanged',
                id: ID_currentLookAt || "",
            });
        }
    }

    function lookAtDetectionUpdateLoop() {
        handlePickRay(true);
    }

    var isDetectingLookAt = false;
    function startDetectionLoop() {
        if (isDetectingLookAt) {
            Script.update.disconnect(lookAtDetectionUpdateLoop);
            isDetectingLookAt = false;
        }

        isDetectingLookAt = true;
        Script.update.connect(lookAtDetectionUpdateLoop);
    }

    function stopDetectionLoop() {
        if (isDetectingLookAt) {
            Script.update.disconnect(lookAtDetectionUpdateLoop);
            isDetectingLookAt = false;
        }
    }

    var lookAtDetectionStatus = false
    function enableOrDisableLookAtDetection() {
        if (lookAtDetectionStatus) {
            startDetectionLoop();
        } else {
            stopDetectionLoop();
        }
    }

    /********************************
    // END Global Detection Start/Stop/Update functions
    ********************************/

    /********************************
    // START App-Related Functions
    ********************************/


    // Function Name: sendToQml()
    //
    // Description:
    //   -Use this function to send a message to the app's QML (i.e. to change appearances). The "message" argument is what is sent to
    //    the app's QML in the format "{method, params}", like json-rpc. See also fromQml().
    function sendToQml(message) {
        ui.sendMessage(message);
    }

    // Function Name: fromQml()
    //
    // Description:
    //   -Called when a message is received from the app QML. The "message" argument is what is sent from the app QML
    //    in the format "{method, params}", like json-rpc. See also sendToQml().
    function fromQml(message) {
        switch (message.method) {
            case 'masterSwitchChanged':
                lookAtDetectionStatus = message.status;
                Settings.setValue('lookAt/enableDetection', lookAtDetectionStatus);
                enableOrDisableLookAtDetection();
                break;
            default:
                maybePrint('Unrecognized message from LookAt.qml: ' + JSON.stringify(message), DEBUG_URGENT);
        }
    }

    // Function Name: appUiOpened()
    //
    // Description:
    //   - Called when the app's UI is opened
    //
    var APP_INITIALIZE_UI_DELAY = 500; // MS
    function appUiOpened() {
        // In the case of a remote QML app, it takes a bit of time
        // for the event bridge to actually connect, so we have to wait...
        Script.setTimeout(function () {
            sendToQml({
                method: 'initializeUI',
                masterSwitchOn: !!lookAtDetectionStatus,
                timeout: LOOK_AT_TIMEOUT_MS
            });
        }, APP_INITIALIZE_UI_DELAY);
    }

    // Function Name: appUiClosed()
    //
    // Description:
    //   - Called when the app's UI is closed
    //
    function appUiClosed() {
    }

    // Function Name: startup()
    //
    // Description:
    //   -startup() will be called when the script is loaded.
    //
    var ui;
    function startup() {
        ui = new AppUi({
            buttonName: "LOOKAT",
            home: Script.resolvePath('./LookAt.qml'),
            onOpened: appUiOpened,
            onClosed: appUiClosed,
            onMessage: fromQml,
            sortOrder: 15,
            normalButton: Script.resourcesPath() + "icons/tablet-icons/avatar-record-i.svg",
            activeButton: Script.resourcesPath() + "icons/tablet-icons/avatar-record-a.svg"
        });
        lookAtDetectionStatus = Settings.getValue('lookAt/enableDetection', false);
        enableOrDisableLookAtDetection();
    }

    // Function Name: shutdown()
    //
    // Description:
    //   - Called when the script ends (i.e. is stopped).
    //
    function shutdown() {
        appUiClosed();
        maybeDeleteOverlays();
    }

    var SOUND_LOOKAT_DETECTED = SoundCache.getSound(Script.resolvePath("lookAtDetected.wav"));
    startup();
    Script.scriptEnding.connect(shutdown);
    /********************************
    // END App-Related Functions
    ********************************/

}()); // END LOCAL_SCOPE