"use strict";
//
// radar.js
// scripts/system/+android/
//
// Created by Cristian Duarte & Gabriel Calero on 31 Jan 2018
// 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
//

var radarModeInterface = {};

var logEnabled = false;
function printd(str) {
    if (logEnabled) {
        print("[radar.js] " + str);
    }
}

var radar = false;
var RADAR_HEIGHT_INIT_DELTA = 10;
var radarHeight = MyAvatar.position.y + RADAR_HEIGHT_INIT_DELTA; // camera position (absolute y)
var tablet;

var RADAR_CAMERA_OFFSET = -1; // 1 meter below the avatar
var ABOVE_GROUND_DROP = 2;
var MOVE_BY = 1;

// Swipe/Drag vars
var PINCH_INCREMENT_FIRST = 0.4; // 0.1 meters zoom in - out
var PINCH_INCREMENT = 0.4; // 0.1 meters zoom in - out
var RADAR_HEIGHT_MAX_PLUS_AVATAR = 80;
var RADAR_HEIGHT_MIN_PLUS_AVATAR = 2;
var RADAR_CAMERA_DISTANCE_TO_ICONS = 1.5; // Icons are near the camera to prevent the LOD manager dismissing them
var RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE = 1; // How much above the avatar base should the icon appear
var AVATAR_DISPLAY_NAME_HEIGHT = 106;
var AVATAR_DISPLAY_NAME_CHAR_WIDTH = 48;
var AVATAR_DISPLAY_NAME_FONT_SIZE = 50;
var lastDragAt;
var lastDeltaDrag;

var uniqueColor;

function moveTo(position) {
    if (radar) {
        MyAvatar.goToLocation(position, false);
        Camera.position = {
            x : position.x,
            y : radarHeight,
            z : position.z
        };
    }
}

function keyPressEvent(event) {
    if (radar) {
        switch (event.text) {
        case "UP":
            moveTo(Vec3.sum(MyAvatar.position, {
                x : 0.0,
                y : 0,
                z : -1 * MOVE_BY
            }));
            break;
        case "DOWN":
            moveTo(Vec3.sum(MyAvatar.position, {
                x : 0,
                y : 0,
                z : MOVE_BY
            }));
            break;
        case "LEFT":
            moveTo(Vec3.sum(MyAvatar.position, {
                x : -1 * MOVE_BY,
                y : 0,
                z : 0
            }));
            break;
        case "RIGHT":
            moveTo(Vec3.sum(MyAvatar.position, {
                x : MOVE_BY,
                y : 0,
                z : 0
            }));
            break;
        }
    }
}

function toggleRadarMode() {
    if (radar) {
        endRadar();
    } else {
        startRadar();
    }
}

function fakeDoubleTap(event) {
    // CLD - temporarily disable toggling mode through double tap
    // * As we have a new UI for toggling between modes, it may be discarded
    // completely in the future.
    // toggleRadarMode();
    teleporter.dragTeleportUpdate(event);
    teleporter.dragTeleportRelease(event);
}

var DOUBLE_TAP_TIME = 300;
var fakeDoubleTapStart = Date.now();
var touchEndCount = 0;

/*
 * Counts touchEnds and if there were 2 in the DOUBLE_TAP_TIME lapse, it
 * triggers a fakeDoubleTap and returns true. Otherwise, returns false (no
 * double tap yet)
 */
function analyzeDoubleTap(event) {
    var fakeDoubleTapEnd = Date.now();
    var elapsed = fakeDoubleTapEnd - fakeDoubleTapStart;
    if (elapsed > DOUBLE_TAP_TIME) {
        touchEndCount = 0;
    }

    // if this is our first "up" then record time so we can
    // later determine if second "up" is a double tap
    if (touchEndCount == 0) {
        fakeDoubleTapStart = Date.now();
    }
    touchEndCount++;

    if (touchEndCount >= 2) {
        var fakeDoubleTapEnd = Date.now();
        var elapsed = fakeDoubleTapEnd - fakeDoubleTapStart;
        printd("-- fakeDoubleTapEnd:" + fakeDoubleTapEnd + "-- elapsed:"
                + elapsed)
        if (elapsed <= DOUBLE_TAP_TIME) {
            touchEndCount = 0;
            fakeDoubleTap(event);
            return true; // don't do the normal touch end processing
        }

        touchEndCount = 0;
    }
    return false;
}

function touchEnd(event) {
    printd("touchEnd received " + JSON.stringify(event));
    // Clean up touch variables
    lastDragAt = null;
    lastDeltaDrag = null;
    touchStartingCoordinates = null; // maybe in special cases it should be
                                        // setup later?
    startedDraggingCamera = false;
    prevTouchPinchRadius = null;
    draggingCamera = false;

    if (movingCamera) {
        // if camera was indeed moving, we should not further process, it was
        // just dragging
        movingCamera = false;
        dragModeFunc = null;
        return;
    }

    // teleport release analysis
    if (teleporter && teleporter.dragTeleportUpdate == dragModeFunc) {
        teleporter.dragTeleportRelease(event);
        dragModeFunc = null;
        return;
    }
    dragModeFunc = null;

    // if pinching or moving is still detected, cancel
    if (event.isPinching) {
        printd("touchEnd fail because isPinching");
        return;
    }
    if (event.isPinchOpening) {
        printd("touchEnd fail because isPinchingOpening");
        return;
    }
    if (event.isMoved) {
        printd("touchEnd fail because isMoved");
        return;
    }

    if (analyzeDoubleTap(event))
        return; // double tap detected, finish

}

/**
 * Polyfill for sign(x)
 */
if (!Math.sign) {
    Math.sign = function(x) {
        // If x is NaN, the result is NaN.
        // If x is -0, the result is -0.
        // If x is +0, the result is +0.
        // If x is negative and not -0, the result is -1.
        // If x is positive and not +0, the result is +1.
        x = +x; // convert to a number
        if (x === 0 || isNaN(x)) {
            return Number(x);
        }
        return x > 0 ? 1 : -1;
    };
}

/*******************************************************************************
 * Line and Plane intersection methods
 ******************************************************************************/

/**
 * findLinePlaneIntersection Given points p {x: y: z:} and q that define a line,
 * and the plane of formula ax+by+cz+d = 0, returns the intersection point or
 * null if none.
 */
function findLinePlaneIntersection(p, q, a, b, c, d) {
    return findLinePlaneIntersectionCoords(p.x, p.y, p.z, q.x, q.y, q.z, a, b,
            c, d);
}

/**
 * findLineToHeightIntersection Given points p {x: y: z:} and q that define a
 * line, and a planeY value that defines a plane paralel to 'the floor' xz
 * plane, returns the intersection to that plane or null if none.
 */
function findLineToHeightIntersection(p, q, planeY) {
    return findLinePlaneIntersection(p, q, 0, 1, 0, -planeY);
}

/**
 * findLinePlaneIntersectionCoords (to avoid requiring unnecessary
 * instantiation) Given points p with px py pz and q that define a line, and the
 * plane of formula ax+by+cz+d = 0, returns the intersection point or null if
 * none.
 */
function findLinePlaneIntersectionCoords(px, py, pz, qx, qy, qz, a, b, c, d) {
    var tDenom = a * (qx - px) + b * (qy - py) + c * (qz - pz);
    if (tDenom == 0)
        return null;

    var t = -(a * px + b * py + c * pz + d) / tDenom;

    return {
        x : (px + t * (qx - px)),
        y : (py + t * (qy - py)),
        z : (pz + t * (qz - pz))
    };
}

/**
 * findLineToHeightIntersection Given points p with px py pz and q that define a
 * line, and a planeY value that defines a plane paralel to 'the floor' xz
 * plane, returns the intersection to that plane or null if none.
 */
function findLineToHeightIntersectionCoords(px, py, pz, qx, qy, qz, planeY) {
    return findLinePlaneIntersectionCoords(px, py, pz, qx, qy, qz, 0, 1, 0,
            -planeY);
}

function findRayIntersection(pickRay) {
    // Check 3D overlays and entities. Argument is an object with origin and
    // direction.
    var result = Overlays.findRayIntersection(pickRay);
    if (!result.intersects) {
        result = Entities.findRayIntersection(pickRay, true);
    }
    return result;
}

/**
 * Given a 2d point (x,y) this function returns the intersection (x, y, z) of
 * the computedPickRay for that point with the plane y = py
 */
function computePointAtPlaneY(x, y, py) {
    var ray = Camera.computePickRay(x, y);
    var p1 = ray.origin;
    var p2 = Vec3.sum(p1, Vec3.multiply(ray.direction, 1));
    return findLineToHeightIntersectionCoords(p1.x, p1.y, p1.z, p2.x, p2.y,
            p2.z, py);
}

/*******************************************************************************
 * 
 ******************************************************************************/

var touchStartingCoordinates = null;

var KEEP_PRESSED_FOR_TELEPORT_MODE_TIME = 750;
var touchBeginTime;

function touchBegin(event) {
    var coords = {
        x : event.x,
        y : event.y
    };
    touchStartingCoordinates = coords;
    touchBeginTime = Date.now();
}

var startedDraggingCamera = false; // first time
var draggingCamera = false; // is trying
var movingCamera = false; // definitive

var MIN_DRAG_DISTANCE_TO_CONSIDER = 100; // distance by axis, not real
                                            // distance

var prevTouchPinchRadius = null;

function pinchUpdate(event) {
    if (!event.isMoved)
        return;
    if (event.radius <= 0)
        return;

    // pinch management
    var avatarY = MyAvatar.position.y;
    var pinchIncrement;
    if (!!prevTouchPinchRadius) {
        // no prev value
        pinchIncrement = PINCH_INCREMENT
                * Math.abs(event.radius - prevTouchPinchRadius) * 0.1;
    } else {
        pinchIncrement = PINCH_INCREMENT_FIRST;
    }

    if (event.isPinching) {
        if (radarHeight + pinchIncrement > RADAR_HEIGHT_MAX_PLUS_AVATAR
                + avatarY) {
            radarHeight = RADAR_HEIGHT_MAX_PLUS_AVATAR + avatarY;
        } else {
            radarHeight += pinchIncrement;
        }
    } else if (event.isPinchOpening) {
        if (radarHeight - pinchIncrement < RADAR_HEIGHT_MIN_PLUS_AVATAR
                + avatarY) {
            radarHeight = RADAR_HEIGHT_MIN_PLUS_AVATAR + avatarY;
        } else {
            radarHeight -= pinchIncrement;
        }
    }

    Camera.position = { 
        x : Camera.position.x, 
        y : radarHeight, 
        z : Camera.position.z 
    };

    if (!draggingCamera) {
        startedDraggingCamera = true;
        draggingCamera = true;
    }

    prevTouchPinchRadius = event.radius;
}

function isInsideSquare(coords0, coords1, halfside) {
    return coords0 != undefined && coords1 != undefined &&
            Math.abs(coords0.x - coords1.x) <= halfside
            && Math.abs(coords0.y - coords1.y) <= halfside;
}

function dragScrollUpdate(event) {
    if (!event.isMoved)
        return;

    // drag management
    var pickRay = Camera.computePickRay(event.x, event.y);
    var dragAt = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction,
            radarHeight - MyAvatar.position.y));

    if (lastDragAt === undefined || lastDragAt === null) {
        lastDragAt = dragAt;
        return;
    }

    var deltaDrag = {
        x : (lastDragAt.x - dragAt.x),
        y : 0,
        z : (lastDragAt.z - dragAt.z)
    };

    lastDragAt = dragAt;
    if (lastDeltaDrag === undefined || lastDeltaDrag === null) {
        lastDeltaDrag = deltaDrag;
        return;
    }

    if (!draggingCamera) {
        startedDraggingCamera = true;
        draggingCamera = true;
    } else {
        if (!movingCamera) {
            if (!isInsideSquare(touchStartingCoordinates, event,
                    MIN_DRAG_DISTANCE_TO_CONSIDER)) {
                movingCamera = true;
            }
        }

        if (movingCamera) {
            if (Math.sign(deltaDrag.x) == Math.sign(lastDeltaDrag.x)
                    && Math.sign(deltaDrag.z) == Math.sign(lastDeltaDrag.z)) {
                // Process movement if direction of the movement is the same
                // than the previous frame
                // process delta
                var moveCameraTo = Vec3.sum(Camera.position, deltaDrag);
                // move camera
                Camera.position = moveCameraTo;
            } else {
                // Do not move camera if it's changing direction in this case,
                // wait until the next direction confirmation..
            }
            lastDeltaDrag = deltaDrag; // save last
        }
    }
}

/*******************************************************************************
 * Teleport feature
 ******************************************************************************/

function Teleporter() {

    var SURFACE_DETECTION_FOR_TELEPORT = true; // true if uses teleport.js
                                                // similar logic to detect
                                                // surfaces. false if uses plain
                                                // teleport to avatar same
                                                // height.

    var TELEPORT_TARGET_MODEL_URL = Script
            .resolvePath("../assets/models/teleport-destination.fbx");
    var TELEPORT_TOO_CLOSE_MODEL_URL = Script
            .resolvePath("../assets/models/teleport-cancel.fbx");

    var TELEPORT_MODEL_DEFAULT_DIMENSIONS = {
        x : 0.10,
        y : 0.00001,
        z : 0.10
    };

    var teleportOverlay = Overlays.addOverlay("model", {
        url : TELEPORT_TARGET_MODEL_URL,
        dimensions : TELEPORT_MODEL_DEFAULT_DIMENSIONS,
        orientation : Quat.fromPitchYawRollDegrees(0, 180, 0),
        visible : false
    });

    var teleportCancelOverlay = Overlays.addOverlay("model", {
        url : TELEPORT_TOO_CLOSE_MODEL_URL,
        dimensions : TELEPORT_MODEL_DEFAULT_DIMENSIONS,
        orientation : Quat.fromPitchYawRollDegrees(0, 180, 0),
        visible : false
    });

    var TELEPORT_COLOR = {
        red : 0,
        green : 255,
        blue : 255
    };
    var TELEPORT_CANCEL_COLOR = {
        red : 255,
        green : 255,
        blue : 0
    };

    var teleportLine = Overlays.addOverlay("line3d", {
        start : {
            x : 0,
            y : 0,
            z : 0
        },
        end : {
            x : 0,
            y : 0,
            z : 0
        },
        color : TELEPORT_COLOR,
        alpha : 1,
        lineWidth : 2,
        dashed : false,
        visible : false
    });

    // code from teleport.js
    var TELEPORT_TARGET = {
        NONE : 'none', // Not currently targetting anything
        INVISIBLE : 'invisible', // The current target is an invvsible
                                    // surface
        INVALID : 'invalid', // The current target is invalid (wall, ceiling,
                                // etc.)
        SURFACE : 'surface', // The current target is a valid surface
        SEAT : 'seat', // The current target is a seat
    }

    var TELEPORT_CANCEL_RANGE = 1;
    var teleportTargetType = TELEPORT_TARGET.NONE;

    function parseJSON(json) {
        try {
            return JSON.parse(json);
        } catch (e) {
            return undefined;
        }
    }

    /*
     * Enhanced with intersection with terrain instead of using current avatar y
     * position if SURFACE_DETECTION_FOR_TELEPORT is true
     */
    function computeDestination(touchEventPos, avatarPosition, cameraPosition,
            radarH) {
        if (SURFACE_DETECTION_FOR_TELEPORT) {
            var pickRay = Camera.computePickRay(touchEventPos.x,
                    touchEventPos.y);
            printd("newTeleportDetect - pickRay " + JSON.stringify(pickRay));
            var destination = Entities.findRayIntersection(pickRay, true, [],
                    [], false, true);
            printd("newTeleportDetect - destination "
                    + JSON.stringify(destination));
            return destination;
        } else {
            var pickRay = Camera.computePickRay(touchEventPos.x,
                    touchEventPos.y);
            var pointingAt = Vec3.sum(pickRay.origin, Vec3.multiply(
                    pickRay.direction, radarH));
            var destination = {
                x : pointingAt.x,
                y : avatarPosition.y,
                z : pointingAt.z
            };
            return destination;
        }
    }

    function renderTeleportOverlays(destination) {
        var overlayPosition = findLineToHeightIntersection(destination,
                Camera.position, Camera.position.y
                        - RADAR_CAMERA_DISTANCE_TO_ICONS);
        printd("[newTeleport] TELEPORT ! render overlay at "
                + JSON.stringify(overlayPosition));

        // CLD note Oct 11, 2017
        // Version of teleport.js 3c109f294f88ba7573bd1221f907f2605893c509
        // doesn't allow invisible surfaces, let's allow it for now
        if (teleportTargetType == TELEPORT_TARGET.SURFACE
                || teleportTargetType == TELEPORT_TARGET.INVISIBLE) {
            Overlays.editOverlay(teleportOverlay, {
                visible : true,
                position : overlayPosition
            });
            Overlays.editOverlay(teleportCancelOverlay, {
                visible : false
            });
            Overlays.editOverlay(teleportLine, {
                start : MyAvatar.position,
                end : destination,
                color : TELEPORT_COLOR,
                visible : true
            });
        } else if (teleportTargetType == TELEPORT_TARGET.INVALID) {
            Overlays.editOverlay(teleportOverlay, {
                visible : false
            });
            Overlays.editOverlay(teleportCancelOverlay, {
                visible : true,
                position : overlayPosition
            });
            Overlays.editOverlay(teleportLine, {
                start : MyAvatar.position,
                end : destination,
                color : TELEPORT_CANCEL_COLOR,
                visible : true
            });
        } else { // TELEPORT_TARGET:NONE?
            Overlays.editOverlay(teleportOverlay, {
                visible : false
            });
            Overlays.editOverlay(teleportCancelOverlay, {
                visible : false
            });
            Overlays.editOverlay(teleportLine, {
                visible : false
            });
        }
    }

    var BORDER_DISTANCE_PX = 100;
    var border_top = 0;
    var border_left = 0;
    var border_right = Window.innerWidth;
    var border_bottom = Window.innerHeight;

    function moveOnBorders(event) {
        var xDelta = 0;
        var zDelta = 0;

        if (event.y <= border_top + BORDER_DISTANCE_PX) {
            zDelta = -0.1;
        } else if (event.y >= border_bottom - BORDER_DISTANCE_PX) {
            zDelta = 0.1;
        }
        if (event.x <= border_left + BORDER_DISTANCE_PX) {
            xDelta = -0.1;
        } else if (event.x >= border_right - BORDER_DISTANCE_PX) {
            xDelta = 0.1;
        }
        if (xDelta == 0 && zDelta == 0) {
            draggingCamera = false;
            return;
        }


        Camera.position = Vec3.sum(Camera.position, {
            x : xDelta,
            y : 0,
            z : zDelta
        });
        draggingCamera = true;
    }

    // When determininig whether you can teleport to a location, the normal of
    // the
    // point that is being intersected with is looked at. If this normal is more
    // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up),
    // then
    // you can't teleport there.
    const MAX_ANGLE_FROM_UP_TO_TELEPORT = 70;
    function getTeleportTargetType(intersection) {
        if (SURFACE_DETECTION_FOR_TELEPORT) {
            if (!intersection.intersects) {
                return TELEPORT_TARGET.NONE;
            }
            var props = Entities.getEntityProperties(intersection.entityID, [
                    'userData', 'visible' ]);
            var data = parseJSON(props.userData);
            if (data !== undefined && data.seat !== undefined) {
                return TELEPORT_TARGET.SEAT;
            }

            if (!props.visible) {
                return TELEPORT_TARGET.INVISIBLE;
            }

            var surfaceNormal = intersection.surfaceNormal;
            var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x
                    + surfaceNormal.z * surfaceNormal.z);
            var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI);

            if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT)
                    || angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT)
                    || Vec3.distance(MyAvatar.position,
                            intersection.intersection) <= TELEPORT_CANCEL_RANGE) {
                return TELEPORT_TARGET.INVALID;
            } else {
                return TELEPORT_TARGET.SURFACE;
            }
        } else {
            var destination = intersection;
            if (Vec3.distance(MyAvatar.position, destination) <= TELEPORT_CANCEL_RANGE) {
                return TELEPORT_TARGET.INVALID;
            } else {
                return TELEPORT_TARGET.SURFACE;
            }
        }
    }
    ;

    function moveToFromEvent(event) {
        var destination = computeDestination(event, MyAvatar.position,
                Camera.position, radarHeight);
        moveTo(SURFACE_DETECTION_FOR_TELEPORT ? Vec3.sum(
                destination.intersection, {
                    y : 1
                }) : destination);
        return true;
    }

    return {
        dragTeleportBegin : function(event) {
            printd("[newTeleport] TELEPORT began");
            var overlayDimensions = teleportIconModelDimensions(MyAvatar.position.y);
            // var destination = computeDestination(event, MyAvatar.position,
            // Camera.position, radarHeight);
            // Dimension teleport and cancel overlays (not show them yet)
            Overlays.editOverlay(teleportOverlay, {
                dimensions : overlayDimensions
            });
            Overlays.editOverlay(teleportCancelOverlay, {
                dimensions : overlayDimensions
            });
            // Position line
            Overlays.editOverlay(teleportLine, {
                visible : true,
                start : 0,
                end : 0
            });
        },

        dragTeleportUpdate : function(event) {
            // if in border, move camera
            moveOnBorders(event);

            var destination = computeDestination(event, MyAvatar.position,
                    Camera.position, radarHeight);

            teleportTargetType = getTeleportTargetType(destination);
            renderTeleportOverlays(SURFACE_DETECTION_FOR_TELEPORT ? destination.intersection
                    : destination);
        },

        dragTeleportRelease : function(event) {
            printd("[newTeleport] TELEPORT released at "
                    + JSON.stringify(event));
            // CLD note Oct 11, 2017
            // Version of teleport.js 3c109f294f88ba7573bd1221f907f2605893c509
            // doesn't allow invisible surfaces, let's allow it for now
            if (teleportTargetType == TELEPORT_TARGET.SURFACE
                    || teleportTargetType == TELEPORT_TARGET.INVISIBLE) {
                moveToFromEvent(event);
            }
            teleportTargetType = TELEPORT_TARGET.NONE;

            Overlays.editOverlay(teleportOverlay, {
                visible : false
            });
            Overlays.editOverlay(teleportLine, {
                visible : false
            });
            Overlays.editOverlay(teleportCancelOverlay, {
                visible : false
            });
        }
    };

}

var teleporter = Teleporter();

/*******************************************************************************
 * 
 ******************************************************************************/

var dragModeFunc = null; // by default is nothing

function oneFingerTouchUpdate(event) {
    if (dragModeFunc) {
        dragModeFunc(event);
    } else {
        if (!isInsideSquare(touchStartingCoordinates, event,
                MIN_DRAG_DISTANCE_TO_CONSIDER)) {
            dragModeFunc = dragScrollUpdate;
            dragModeFunc(event);
        } else {
            var now = Date.now(); // check time
            if (now - touchBeginTime >= KEEP_PRESSED_FOR_TELEPORT_MODE_TIME) {
                teleporter.dragTeleportBegin(event);
                dragModeFunc = teleporter.dragTeleportUpdate;
                dragModeFunc(event);
            } else {
                // not defined yet, let's wait for time or movement to happen
            }
        }
    }
}

function touchUpdate(event) {
    if (event.isPinching || event.isPinchOpening) {
        pinchUpdate(event);
    } else {
        oneFingerTouchUpdate(event);
    }
}

/*******************************************************************************
 * Avatar cache structure for showing avatars markers
 ******************************************************************************/

// by QUuid
var avatarsData = {};
var avatarsIcons = []; // a parallel list of icons (overlays) to easily run
                        // through
var avatarsNames = []; // a parallel list of names (overlays) to easily run
                        // through

function getAvatarIconForUser(uid) {
    var color = uniqueColor.getColor(uid);
    if (color.charAt(0) == '#') {
        color = color.substring(1, color.length);
    }
    // FIXME: this is a temporary solution until we can use circle3d with
    // lineWidth
    return Script.resolvePath("assets/images/circle-" + color + ".svg");
}

var avatarIconDimensionsVal = {
    x : 0,
    y : 0,
    z : 0.00001
};
function avatarIconPlaneDimensions() {
    // given the current height, give a size
    var xy = -0.003531 * (radarHeight - MyAvatar.position.y) + 0.1;
    avatarIconDimensionsVal.x = Math.abs(xy);
    avatarIconDimensionsVal.y = Math.abs(xy);
    // reuse object
    return avatarIconDimensionsVal;
}

function currentOverlayIconForAvatar(QUuid) {
    if (avatarsData[QUuid] != undefined) {
        return avatarsData[QUuid].icon;
    } else {
        return null;
    }
}

function currentOverlayNameForAvatar(QUuid) {
    if (avatarsData[QUuid] != undefined) {
        return avatarsData[QUuid].name;
    } else {
        return null;
    }
}

function saveAvatarData(QUuid) {
    if (QUuid == null)
        return;
    var avat = AvatarList.getAvatar(QUuid);
    printd("avatar added save avatar " + QUuid);

    if (!avat)
        return;

    if (avatarsData[QUuid] != undefined) {
        avatarsData[QUuid].position = avat.position;
    } else {
        var avatarIcon = Overlays.addOverlay("circle3d", {
            color: uniqueColor.convertHexToRGB(uniqueColor.getColor(QUuid)),
            dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS,
            rotation: Quat.fromPitchYawRollDegrees(90, 0, 0),
            innerRadius: 1.8,
            outerRadius: 2,
            isSolid: true,
            visible: false
        });

        var needRefresh = !avat || !avat.displayName;
        var displayName = avat && avat.displayName ? avat.displayName
                : "Unknown";
        var textWidth = displayName.length * AVATAR_DISPLAY_NAME_CHAR_WIDTH;
        var avatarName = Overlays.addOverlay("text", {
            width: textWidth,
            height: AVATAR_DISPLAY_NAME_HEIGHT,
            color: { red: 255, green: 255, blue: 255},
            backgroundAlpha: 0.0,
            textRaiseColor: { red: 0, green: 0, blue: 0},
            font: {size: AVATAR_DISPLAY_NAME_FONT_SIZE, bold: true},
            visible: false,
            text: displayName,
            textAlignCenter: true
        });
        avatarsIcons.push(avatarIcon);
        avatarsNames.push(avatarName);
        avatarsData[QUuid] = {
            position : avat.position,
            icon : avatarIcon,
            name : avatarName,
            textWidth : textWidth,
            needRefresh : needRefresh
        };
    }
}

function removeAvatarData(QUuid) {
    if (QUuid == null)
        return;

    var itsOverlay = currentOverlayIconForAvatar(QUuid);
    if (itsOverlay != null) {
        Overlays.deleteOverlay(itsOverlay);
    }
    var itsNameOverlay = currentOverlayNameForAvatar(QUuid);
    if (itsNameOverlay != null) {
        Overlays.deleteOverlay(itsNameOverlay);
    }

    var idx = avatarsIcons.indexOf(itsOverlay);
    avatarsIcons.splice(idx, 1);
    idx = avatarsNames.indexOf(itsNameOverlay);
    avatarsNames.splice(idx, 1);

    delete avatarsData[QUuid];
}

function saveAllOthersAvatarsData() {
    var avatarIds = AvatarList.getAvatarIdentifiers();
    var len = avatarIds.length;
    for (var i = 0; i < len; i++) {
        if (avatarIds[i]) {
            saveAvatarData(avatarIds[i]);
        }
    }
}

function avatarAdded(QUuid) {
    printd("avatar added " + QUuid);// + " at " +
                                    // JSON.stringify(AvatarList.getAvatar(QUuid).position));
    saveAvatarData(QUuid);
}

function avatarRemoved(QUuid) {
    printd("avatar removed " + QUuid);
    removeAvatarData(QUuid);
}

/*******************************************************************************
 * Avatar Icon/Markers rendering
 ******************************************************************************/
var myAvatarIcon;
var myAvatarName;
function distanceForCameraHeight(h) {
    if (h < 30) return 1;
    if (h < 40) return 2;
    if (h < 50) return 2.5;
    return 5;
}
function renderMyAvatarIcon() {
    var commonY = Camera.position.y - distanceForCameraHeight(Camera.position.y);
    var iconPos = findLineToHeightIntersectionCoords(   MyAvatar.position.x,
                                                        MyAvatar.position.y + RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE,
                                                        MyAvatar.position.z,
                                                        Camera.position.x, Camera.position.y, Camera.position.z,
                                                        commonY);
    if (!iconPos) { printd("avatarmy icon pos null"); return;}
    var iconDimensions = avatarIconPlaneDimensions();

    var avatarPos = MyAvatar.position;
    var cameraPos = Camera.position;
    var borderPoints = [
            computePointAtPlaneY(0, 0, commonY),
            computePointAtPlaneY(Window.innerWidth, Window.innerHeight, commonY) ];

    var p1 = findLineToHeightIntersectionCoords(avatarPos.x, avatarPos.y,
            avatarPos.z, cameraPos.x, cameraPos.y, cameraPos.z, commonY);
    var x = (p1.x - borderPoints[0].x) * (Window.innerWidth)
            / (borderPoints[1].x - borderPoints[0].x);
    var y = (p1.z - borderPoints[0].z) * (Window.innerHeight)
            / (borderPoints[1].z - borderPoints[0].z);

    if (!myAvatarIcon && MyAvatar.SELF_ID) {
       myAvatarIcon =  Overlays.addOverlay("circle3d", {
            color: uniqueColor.convertHexToRGB(uniqueColor.getColor(MyAvatar.SELF_ID)),
            dimensions: ICON_ENTITY_DEFAULT_DIMENSIONS,
            rotation: Quat.fromPitchYawRollDegrees(90, 0, 0),
            innerRadius: 1.8,
            outerRadius: 2,
            isSolid: true,
            visible: false
        });
    }

    if (!myAvatarName) {
        myAvatarName = Overlays.addOverlay("text", {
                        width: 100,
                        height: AVATAR_DISPLAY_NAME_HEIGHT,
                        textAlignCenter: true,
                        color: { red: 255, green: 255, blue: 255},
                        backgroundAlpha: 0.0,
                        font: {size: AVATAR_DISPLAY_NAME_FONT_SIZE, bold: true},
                        textRaiseColor: { red: 0, green: 0, blue: 0},
                        visible: false,
                        text: "Me"
                       });
    }

    if (myAvatarIcon) {
        Overlays.editOverlay(myAvatarIcon, {
            visible : true,
            dimensions : iconDimensions,
            position : iconPos
        });

    }
    var textSize = (14 + (iconDimensions.y - 0.03) * 15 / 0.06);

    Overlays.editOverlay(myAvatarName, {
        visible : true,
        x : x - 18 + (iconDimensions.y - 0.03) * 2 / 0.06,
        y : y + iconDimensions.y * 550,
        font : {
            size : textSize,
            bold : true
        },
    });

}

function hideAllAvatarIcons() {
    var len = avatarsIcons.length;
    for (var i = 0; i < len; i++) {
        Overlays.editOverlay(avatarsIcons[i], {
            visible : false
        });
    }
    len = avatarsNames.length;
    for (var j = 0; j < len; j++) {
        Overlays.editOverlay(avatarsNames[j], {
            visible : false
        });
    }
    if (myAvatarIcon) {
        Overlays.editOverlay(myAvatarIcon, {
            visible : false
        });
    }
    Overlays.editOverlay(myAvatarName, {
        visible : false
    })
}

function renderAllOthersAvatarIcons() {
    var avatarPos;
    var iconDimensions = avatarIconPlaneDimensions();
    var commonY = Camera.position.y - distanceForCameraHeight(Camera.position.y);
    var borderPoints = [
            computePointAtPlaneY(0, 0, commonY),
            computePointAtPlaneY(Window.innerWidth, Window.innerHeight, commonY) ];

    for ( var QUuid in avatarsData) {
        if (avatarsData.hasOwnProperty(QUuid)) {
            if (AvatarList.getAvatar(QUuid) != null) {
                avatarPos = AvatarList.getAvatar(QUuid).position;

                var cameraPos = Camera.position;
                var p1 = findLineToHeightIntersectionCoords(avatarPos.x, avatarPos.y, avatarPos.z, 
                                                    cameraPos.x, cameraPos.y, cameraPos.z,
                                                    commonY);

                var x = (p1.x - borderPoints[0].x) * (Window.innerWidth) / (borderPoints[1].x - borderPoints[0].x);
                var y = (p1.z - borderPoints[0].z) * (Window.innerHeight) / (borderPoints[1].z - borderPoints[0].z);

                if (avatarsData[QUuid].icon != undefined) {
                    var iconPos = findLineToHeightIntersectionCoords(   avatarPos.x, avatarPos.y + RADAR_ICONS_APPARENT_DISTANCE_TO_AVATAR_BASE, avatarPos.z,
                                                                        Camera.position.x, Camera.position.y, Camera.position.z,
                                                                        commonY);
                    if (!iconPos) { print ("avatar icon pos bad for " + QUuid); continue; }
                    if (avatarsData[QUuid].needRefresh) {
                        var avat = AvatarList.getAvatar(QUuid);
                        if (avat && avat.displayName) {
                            Overlays.editOverlay(avatarsData[QUuid].name, {
                                width : avat.displayName.length
                                        * AVATAR_DISPLAY_NAME_CHAR_WIDTH,
                                text : avat.displayName,
                                textAlignCenter : true
                            });
                            avatarsData[QUuid].needRefresh = false;
                        }
                    }
                    var textSize = (14 + (iconDimensions.y - 0.03) * 15 / 0.06);
                    Overlays.editOverlay(avatarsData[QUuid].icon, {
                        visible : true,
                        dimensions : iconDimensions,
                        position : iconPos
                    });
                    Overlays.editOverlay(avatarsData[QUuid].name, {
                        visible : true,
                        x : x - avatarsData[QUuid].textWidth * 0.5,
                        y : y + iconDimensions.y * 550,
                        font : {
                            size : textSize,
                            bold : true
                        }
                    });
                }
            }
        }
    }
}

var ICON_ENTITY_DEFAULT_DIMENSIONS = {
    x : 0.10,
    y : 0.00001,
    z : 0.10
};


function teleportIconModelDimensions(y) {
    var teleportModelDimensions = ICON_ENTITY_DEFAULT_DIMENSIONS;
    var xz = -0.002831 * (radarHeight - y) + 0.1;
    teleportModelDimensions.x = xz;
    teleportModelDimensions.z = xz;
    // reuse object
    return teleportModelDimensions;
}

/*******************************************************************************
 * 
 ******************************************************************************/

function startRadar() {
    printd("avatar added my avatar is  " + MyAvatar.sessionUUID);
    saveAllOthersAvatarsData();
    Camera.mode = "independent";

    initCameraOverMyAvatar();

    Camera.orientation = Quat.fromPitchYawRollDegrees(-90, 0, 0);
    radar = true;

    Controller.setVPadEnabled(false); // this was done before in CompositeExtra in the DisplayPlugin (Checking for camera not independent, not radar mode)

    connectRadarModeEvents();
}

function endRadar() {
    printd("-- endRadar");
    Camera.mode = "third person";
    radar = false;

    Controller.setVPadEnabled(true);

    disconnectRadarModeEvents();
    hideAllAvatarIcons();
}

function onRadarModeClicked() {
    startRadar();
}

function onMyViewModeClicked() {
    endRadar();
}

radarModeInterface.startRadarMode = function() {
    startRadar();
};

radarModeInterface.endRadarMode = function() {
    endRadar();
};

radarModeInterface.init = function() {
    init();
}

radarModeInterface.setUniqueColor = function(c) {
    uniqueColor = c;
};

module.exports = radarModeInterface;

function updateRadar() {
    // Update avatar icons
    if (startedDraggingCamera) {
        hideAllAvatarIcons();
        startedDraggingCamera = false;
    } else if (!draggingCamera) {
        renderMyAvatarIcon();
        renderAllOthersAvatarIcons();
    }
}

function valueIfDefined(value) {
    return value !== undefined ? value : "";
}

function connectRadarModeEvents() {
    Script.update.connect(updateRadar); // 60Hz loop
    Controller.keyPressEvent.connect(keyPressEvent);
    Controller.touchUpdateEvent.connect(touchUpdate);
    Window.domainChanged.connect(domainChanged);
    MyAvatar.positionGoneTo.connect(positionGoneTo);
}

function initCameraOverMyAvatar() {
   radarHeight = MyAvatar.position.y + RADAR_HEIGHT_INIT_DELTA;
   Camera.position = {
        x : MyAvatar.position.x,
        y : radarHeight,
        z : MyAvatar.position.z
    };
}

function domainChanged() {
    initCameraOverMyAvatar();
}

function positionGoneTo() {
    Camera.position = {
            x : MyAvatar.position.x,
            y : radarHeight,
            z : MyAvatar.position.z
        };
}

function disconnectRadarModeEvents() {
    Script.update.disconnect(updateRadar);
    Controller.keyPressEvent.disconnect(keyPressEvent);
    Controller.touchUpdateEvent.disconnect(touchUpdate);
    MyAvatar.positionGoneTo.disconnect(positionGoneTo);
    Window.domainChanged.disconnect(domainChanged);
}

function init() {
    tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");

    Controller.touchBeginEvent.connect(touchBegin);
    Controller.touchEndEvent.connect(touchEnd);

    AvatarList.avatarAddedEvent.connect(avatarAdded);
    AvatarList.avatarRemovedEvent.connect(avatarRemoved);
}