<commit_before>// Global variables
var topLayerIdx = 0;
var layerList = new Array();
var stage = undefined;
var connection = new WebSocket('ws://localhost:8080');
var canvas = undefined;

var generadedShapeIdIdx = 0;
var debugOn = false;
var debugTween;
var debugAnnimatingShapes = [];
var debugCanClose = true;

// Event handlers
function connectionDataReceived(event) {

    // Reset mousedrag
    mouseDragFound = false;

    // Clear screen
    layerList[topLayerIdx].destroyChildren();

    var dataObject = jQuery.parseJSON(event.data);

    var shape = parseShapeData(dataObject);

    // Disable mousedrag if event is no longer attached to the shape
    if(!mouseDragFound && mouseDragId!=undefined) {
        mouseDragEndEventHandler(mouseDragId,undefined);
    }

    // Draw on current layer
    layerList[topLayerIdx].add(shape);
    layerList[topLayerIdx].batchDraw();
}

function connectionError(error) {

    printDebugMessage("WebSocket Error " + error);
}

function parseShapeData(data) {

    var shape = shapeFromData(data);
    enableEventHandlers(shape, data);

    return shape;
}

/*
 * Sending Input events
 */

function enableEventHandlers(shape, message) {
    if(message.eventData != undefined && message.eventData != null) {
        if(message.eventData.listen.indexOf("mouseclick") != -1) {
            shape.on('click', clickEventHandler.bind(undefined, message.eventData.eventId));
        }
        if(message.eventData.listen.indexOf("mousedoubleclick") != -1) {
            shape.on('dblclick', doubleClickEventHandler.bind(undefined, message.eventData.eventId));
        }
        if(message.eventData.listen.indexOf("mousedown") != -1) {
            shape.on('mousedown', mouseDownEventHandler.bind(undefined, message.eventData.eventId));
        }
        if(message.eventData.listen.indexOf("mouseup") != -1) {
            shape.on('mouseup', mouseUpEventHandler.bind(undefined, message.eventData.eventId));
        }
        if(message.eventData.listen.indexOf("mouseover") != -1) {
            shape.on('mouseover', mouseOverEventHandler.bind(undefined, message.eventData.eventId));
        }
        if(message.eventData.listen.indexOf("mousemove") != -1) {
            shape.on('mousemove', mouseMoveEventHandler.bind(undefined, message.eventData.eventId));
        }
        if(message.eventData.listen.indexOf("mouseout") != -1) {
            shape.on('mouseout', mouseOutEventHandler.bind(undefined, message.eventData.eventId));
        }
        if(message.eventData.listen.indexOf("mousedrag") != -1) {
            mouseDragFound = mouseDragFound || message.eventData["eventId"]==mouseDragId; // Used to disable mousedrag when no longer this event is requested
            shape.on('mousedown', mouseDragStartEventHandler.bind(undefined, message.eventData.eventId));
        }
    }
}

function clickEventHandler(id, event) { mouseEvent("mouseclick", id, event); }
function doubleClickEventHandler(id, event) { mouseEvent("mousedoubleclick", id, event); }
function mouseDownEventHandler(id, event) { mouseEvent("mousedown", id, event); }
function mouseUpEventHandler(id, event) { mouseEvent("mouseup", id, event); }
function mouseOverEventHandler(id, event) { mouseEvent("mouseover", id, event); }
function mouseOutEventHandler(id, event) { mouseEvent("mouseout", id, event); }
function mouseMoveEventHandler(id, event) { mouseEvent("mousemove", id, event); }
var mouseDragId = undefined;
var mouseDragEndHandler = undefined;
var mouseDragHandler = undefined;
var mouseDragFound = true;
var prevMousePosX = 0;
var prevMousePosY = 0;
function mouseDragStartEventHandler(id, event) {
    mouseDragEndHandler = mouseDragEndEventHandler.bind(undefined, id);
    mouseDragHandler = mouseDragEventHandler.bind(undefined, id);
    mouseDragId = id; // Used to disable mousedrag when mousedrag is no longer attached to the shape
    // Compensate for the position of the canvas
    var canvasPos = $("#canvas").position();
    prevMousePosX = event.pageX-canvasPos.left+450;
    prevMousePosY = event.pageY-canvasPos.top+300;

    canvas.on('mouseout', mouseDragEndHandler);
    canvas.on('mouseup', mouseDragEndHandler);
    canvas.on('mousemove', mouseDragHandler);
    // Cancel event bubbling
    event.cancelBubble = true;
}
function mouseDragEndEventHandler(id, event) {
    canvas.off('mouseout', mouseDragEndHandler);
    canvas.off('mouseup', mouseDragEndHandler);
    canvas.off('mousemove', mouseDragHandler);
    mouseDragId = undefined;
    mouseDragEndHandler = undefined;
    mouseDragHandler = undefined;
    // Cancel event bubbling
    event.cancelBubble = true;
}
function mouseDragEventHandler(id, event) {
    // Compensate for the position of the canvas
    var canvasPos = $("#canvas").position();
    var x1 = prevMousePosX; // Fix me
    var x2 = event.pageX-canvasPos.left+450;
    prevMousePosX = x2;
    var y1 = prevMousePosY; // Fix me
    var y2 = event.pageY-canvasPos.top+300;
    prevMousePosY = y2;

    printDebugMessage("Event mousedrag on " + id + " from (x1:"+x1+" y1:"+y1+") to (x2:"+x2+" y2:"+y2+")" ,0);

    connection.send(JSON.stringify({
        "event":"mousedrag",
        "data":{
            "id1": id,
            "x1": x1,
            "y1": y1,
            "id2": id,
            "x2": x2,
            "y2": y2
        }
    }));
    // Cancel event bubbling
    event.cancelBubble = true;
}
function mouseEvent(eventName, id, event) {
    // Compensate for the position of the canvas
    var canvasPos = $("#canvas").position();
    var x = event.pageX-canvasPos.left+450; // Fix me
    var y = event.pageY-canvasPos.top+300; // Fix me

    printDebugMessage("Event "+eventName+" on " + id + " (x:"+x+" y:"+y+")",0);

    connection.send(JSON.stringify({
        "event":eventName,
        "data":{
            "id": id,
            "x": x,
            "y": y
        }
    }));
    // Cancel event bubbling
    event.cancelBubble = true;
}

function sendKeyEvent(eventName, event) {

    var key = normalizeKeyCode(event);
    var ctrl = event.ctrlKey || event.metaKey;
    var alt = event.altKey;
    var shift = event.shiftKey;

    printDebugMessage("KeyEvent "+key+" "+eventName+" (ctrl:"+ctrl+" alt:"+alt+" shift:"+shift+")",0);

    event.preventDefault(); // Prevent browser default behavior to block ctrl-c for example

    connection.send(JSON.stringify({
        "event":eventName,
        "data":{
            "key": key,
            "control": ctrl,
            "alt": alt,
            "shift": shift
        }
    }));
}

function sendScrollEvent(deltaX,deltaY) {

    printDebugMessage("ScrollEvent (deltaX:"+deltaX+" deltaY:"+deltaY+")",0);

    connection.send(JSON.stringify({
        "event":"scroll",
        "data":{
            "xdelta": deltaX,
            "ydelta": deltaY
        }
    }));
}

/*
 * Drawing shapes
 */

function shapeFromData(message) {

    var shape = null;
    var debugMessage = "";
    var data = message.data;

    // both fill and stroke are in the form {"r": 255, "g": 255, "b": 255, ("a": 1.0)?}
    if(data["fill"]){
        data["fill"] = rgbaDictToColor(data["fill"]);
    }

    if(data["stroke"]){
        data["stroke"] = rgbaDictToColor(data["stroke"]);
    }
    // Debug message
    if(!data["id"] && debugOn) {
        data["id"] = "sid" + generadedShapeIdIdx;
        generadedShapeIdIdx++;
    }

    debugMessage = "Drawing " + message.type + " (" + data["id"] + ") ";

    // Init shape based on type
    switch (message.type) {
        case "line":
            shape = new Kinetic.Line(data);
            debugMessage += "with points: " + data.points;
            break;
        case "polygon":
            shape = new Kinetic.Polygon(data);
            break;
        case "circle":
            shape = new Kinetic.Circle(data);
            debugMessage += "width x:"+data.x+" y:"+data.y+" and radius:"+data.radius;
            break;
        case "rect":
            shape = new Kinetic.Rect(data);
            debugMessage += "width x:"+data.x+" y:"+data.y+" width:"+data.width+" height:"+data.height;
            break;
        case "text":
            shape = new Kinetic.Text(data);
            debugMessage += "width x:"+data.x+" y:"+data.y+" text:"+data.text;
            break;
        case "container":

            data.clip = [0, 0, data.width, data.height];
            shape = new Kinetic.Group(data);
            message.children.forEach(function(child) {
                shape.add(parseShapeData(child));
            });
            break;
        default:
            debugMessage = null;
            printDebugMessage("Unrecognized JSON message received from server.",2);
    }

    if(debugMessage)
        printDebugMessage(debugMessage,0);

    return shape;
}

function rgbaDictToColor(dict){
    var res = "";
    if(dict["a"]){
        res = "rgba({0},{1},{2},{3})".format(dict["r"], dict["g"], dict["b"], dict["a"]);
    } else {
        res = "rgb({0},{1},{2})".format(dict["r"], dict["g"], dict["b"]);
    }
    return res;
}

/*
* Visible debugger
*
* Type 0 = message
* Type 1 = warning
* Type 2 = error
*/

function printDebugMessage(message, type) {
    if(debugOn) {
        var now = new Date(),
            now = now.getHours()+':'+now.getMinutes()+':'+now.getSeconds();

        if(type == 1)
        {
            console.warn(message);
        }
        else if(type == 2)
        {
            console.error(message);
        }
        else
        {
            console.log(message);
        }

        $("#debug").prepend("
["+now+"] "+message+"
");

    }
}

var hideDebugConsole = function(){
        if(debugCanClose) {
            if($(this).width() == 20)
            {
                $(this).animate({
                    width: 350
                },300);
            }
            else
            {
                $(this).animate({
                    width: 20
                },300);
            }
        }
    };
// For flashing certain shapes while debugging
var debugSelector = function () {
    debugCanClose = false;
    var sid = $(this).data("sid"); // The shape id in an extended data attribute

    var shape = stage.find('#' + sid)[0];
    var alreadyAnnimatingIdx = $.inArray(shape, debugAnnimatingShapes);

    if(alreadyAnnimatingIdx == -1)
    {
        debugAnnimatingShapes.push(shape);

        var fill = shape.getFill();

        shape.setFill("red");
        layerList[topLayerIdx].draw();

        var interval = setInterval(function(){

            shape.setFill(fill);
            layerList[topLayerIdx].draw();

            clearInterval(interval);
            debugAnnimatingShapes.splice( alreadyAnnimatingIdx ,1 );
            debugCanClose = true;
        },250);
    }
};
function initDebug() {
    // Enable debug
    debugOn = true;
    // Debug selector for flashing shapes
    $("#debug").delegate( ".debugSelector", "click", debugSelector);
    // For hiding the debug console
    $("#debug").click(hideDebugConsole);
    $("#debug").show();
}
function debugOff() {
    debugOn = false;
    // Remove debug selector
    $("#debug").undelegate( ".debugSelector", "click", debugSelector);
    // Remove click event for hiding the console
    $("#debug").off('click',hideDebugConsole);
    // Clear debug screen and hide
    $("#debug").hide().html("");
}

/*
 * Canvas setup
 */

function initCanvas(container, width, height) {
    // Only init canvas when there is a container
    // Container is provided for testing purposes and extesibility
    if(container.exists()) {
        container.css( "width", width+"px" );
        container.css( "height", height+"px" );
        container.css( "margin-top", "-"+height/2+"px" );
        container.css( "margin-left", "-"+width/2+"px" );

        stage = new Kinetic.Stage({
            container: container.attr('id'),
            width: width, // Default width and height
            height: height // Can be changed with the resize canvas function
        });

        // Create new layer to draw on
        newDefaultLayer();

    }
}
function initWrapper(wrapper, width, height) {

    wrapper.css( "min-width", width+"px" );
    wrapper.css( "height", $( window ).height()+"px" );
}

function newDefaultLayer() {

    topLayerIdx++;
    layerList[topLayerIdx] = new Kinetic.Layer();
    stage.add(layerList[topLayerIdx]);
}

/*
 * On document ready
 */
$(document).ready(function() {

    var width = 900; // defined here because the container also needs these proportions
    var height = 600;

    // Init canvas
    initCanvas($('#canvas'),width,height);
    initWrapper($('#wrapper'),width,height);

    canvas = $("#canvas canvas");

    // Start listening for scroll events using mousewheel.js
    canvas.mousewheel(function(event) {
        sendScrollEvent(event.deltaX,event.deltaY);
        return false; // Prevent browser default behavior
    });

    if(debugOn) {
        initDebug();
    }
    else {
        debugOff();
    }

    // When the connection is open, send some data to the server
    connection.onopen = function () {
        printDebugMessage("Connection opened",0);
    };
    // Log errors
    connection.onerror = connectionError;
    // Callback for recieving data
    connection.onmessage = connectionDataReceived;

    // Begin to listen for keys
    window.addEventListener('keydown', sendKeyEvent.bind(this,'keydown'));

    window.addEventListener('keyup', sendKeyEvent.bind(this,'keyup'));
});
<commit_msg>#43 rate limit drag events<commit_after>// Global variables
var topLayerIdx = 0;
var layerList = new Array();
var stage = undefined;
var connection = new WebSocket('ws://localhost:8080');
var canvas = undefined;

var generadedShapeIdIdx = 0;
var debugOn = false;
var debugTween;
var debugAnnimatingShapes = [];
var debugCanClose = true;

// Event handlers
function connectionDataReceived(event) {

    // Reset mousedrag
    mouseDragFound = false;

    // Clear screen
    layerList[topLayerIdx].destroyChildren();

    var dataObject = jQuery.parseJSON(event.data);

    var shape = parseShapeData(dataObject);

    // Disable mousedrag if event is no longer attached to the shape
    if(!mouseDragFound && mouseDragId!=undefined) {
        mouseDragEndEventHandler(mouseDragId,undefined);
    }

    // Draw on current layer
    layerList[topLayerIdx].add(shape);
    layerList[topLayerIdx].batchDraw();
}

function connectionError(error) {

    printDebugMessage("WebSocket Error " + error);
}

function parseShapeData(data) {

    var shape = shapeFromData(data);
    enableEventHandlers(shape, data);

    return shape;
}

/*
 * Sending Input events
 */

function enableEventHandlers(shape, message) {
    if(message.eventData != undefined && message.eventData != null) {
        if(message.eventData.listen.indexOf("mouseclick") != -1) {
            shape.on('click', clickEventHandler.bind(undefined, message.eventData.eventId));
        }
        if(message.eventData.listen.indexOf("mousedoubleclick") != -1) {
            shape.on('dblclick', doubleClickEventHandler.bind(undefined, message.eventData.eventId));
        }
        if(message.eventData.listen.indexOf("mousedown") != -1) {
            shape.on('mousedown', mouseDownEventHandler.bind(undefined, message.eventData.eventId));
        }
        if(message.eventData.listen.indexOf("mouseup") != -1) {
            shape.on('mouseup', mouseUpEventHandler.bind(undefined, message.eventData.eventId));
        }
        if(message.eventData.listen.indexOf("mouseover") != -1) {
            shape.on('mouseover', mouseOverEventHandler.bind(undefined, message.eventData.eventId));
        }
        if(message.eventData.listen.indexOf("mousemove") != -1) {
            shape.on('mousemove', mouseMoveEventHandler.bind(undefined, message.eventData.eventId));
        }
        if(message.eventData.listen.indexOf("mouseout") != -1) {
            shape.on('mouseout', mouseOutEventHandler.bind(undefined, message.eventData.eventId));
        }
        if(message.eventData.listen.indexOf("mousedrag") != -1) {
            mouseDragFound = mouseDragFound || message.eventData["eventId"]==mouseDragId; // Used to disable mousedrag when no longer this event is requested
            shape.on('mousedown', mouseDragStartEventHandler.bind(undefined, message.eventData.eventId));
        }
    }
}

function clickEventHandler(id, event) { mouseEvent("mouseclick", id, event); }
function doubleClickEventHandler(id, event) { mouseEvent("mousedoubleclick", id, event); }
function mouseDownEventHandler(id, event) { mouseEvent("mousedown", id, event); }
function mouseUpEventHandler(id, event) { mouseEvent("mouseup", id, event); }
function mouseOverEventHandler(id, event) { mouseEvent("mouseover", id, event); }
function mouseOutEventHandler(id, event) { mouseEvent("mouseout", id, event); }
function mouseMoveEventHandler(id, event) { mouseEvent("mousemove", id, event); }
var mouseDragId = undefined;
var mouseDragEndHandler = undefined;
var mouseDragHandler = undefined;
var dragEventRateLimiter = undefined;
var mouseDragFound = true;
var enableDragHandler = true;
var prevMousePosX = 0;
var prevMousePosY = 0;
var mouseMoveRateLimit = 100; // The mousemove interval limit
function mouseDragStartEventHandler(id, event) {
    mouseDragEndHandler = mouseDragEndEventHandler.bind(undefined, id);
    mouseDragHandler = mouseDragEventHandler.bind(undefined, id);
    mouseDragId = id; // Used to disable mousedrag when mousedrag is no longer attached to the shape
    // Compensate for the position of the canvas
    var canvasPos = $("#canvas").position();
    prevMousePosX = event.pageX-canvasPos.left+450;
    prevMousePosY = event.pageY-canvasPos.top+300;

    canvas.on('mouseout', mouseDragEndHandler);
    canvas.on('mouseup', mouseDragEndHandler);
    canvas.on('mousemove', mouseDragHandler);
    // Limit mousemoves from firing by a specified interval
    dragEventRateLimiter = true;
    dragEventRateLimiter = window.setInterval(function(){
        enableDragHandler = true;
    }, mouseMoveRateLimit);
    // Cancel event bubbling
    event.cancelBubble = true;
}
function mouseDragEndEventHandler(id, event) {
    canvas.off('mouseout', mouseDragEndHandler);
    canvas.off('mouseup', mouseDragEndHandler);
    canvas.off('mousemove', mouseDragHandler);
    mouseDragId = undefined;
    mouseDragEndHandler = undefined;
    mouseDragHandler = undefined;
    if(mouseMoveRateLimit != undefined) {
        clearInterval(mouseMoveRateLimit);
        mouseMoveRateLimit = undefined;
    }
    // Cancel event bubbling
    event.cancelBubble = true;
}
function mouseDragEventHandler(id, event) {
    if (enableDragHandler) {
        // Compensate for the position of the canvas
        var canvasPos = $("#canvas").position();
        var x1 = prevMousePosX; // Fix me
        var x2 = event.pageX-canvasPos.left+450;
        prevMousePosX = x2;
        var y1 = prevMousePosY; // Fix me
        var y2 = event.pageY-canvasPos.top+300;
        prevMousePosY = y2;

        printDebugMessage("Event mousedrag on " + id + " from (x1:"+x1+" y1:"+y1+") to (x2:"+x2+" y2:"+y2+")" ,0);

        connection.send(JSON.stringify({
            "event":"mousedrag",
            "data":{
                "id1": id,
                "x1": x1,
                "y1": y1,
                "id2": id,
                "x2": x2,
                "y2": y2
            }
        }));
        // Wait for the next interval before firing again
        enableDragHandler = false;
    }
    // Cancel event bubbling
    event.cancelBubble = true;
}
function mouseEvent(eventName, id, event) {
    // Compensate for the position of the canvas
    var canvasPos = $("#canvas").position();
    var x = event.pageX-canvasPos.left+450; // Fix me
    var y = event.pageY-canvasPos.top+300; // Fix me

    printDebugMessage("Event "+eventName+" on " + id + " (x:"+x+" y:"+y+")",0);

    connection.send(JSON.stringify({
        "event":eventName,
        "data":{
            "id": id,
            "x": x,
            "y": y
        }
    }));
    // Cancel event bubbling
    event.cancelBubble = true;
}

function sendKeyEvent(eventName, event) {

    var key = normalizeKeyCode(event);
    var ctrl = event.ctrlKey || event.metaKey;
    var alt = event.altKey;
    var shift = event.shiftKey;

    printDebugMessage("KeyEvent "+key+" "+eventName+" (ctrl:"+ctrl+" alt:"+alt+" shift:"+shift+")",0);

    event.preventDefault(); // Prevent browser default behavior to block ctrl-c for example

    connection.send(JSON.stringify({
        "event":eventName,
        "data":{
            "key": key,
            "control": ctrl,
            "alt": alt,
            "shift": shift
        }
    }));
}

function sendScrollEvent(deltaX,deltaY) {

    printDebugMessage("ScrollEvent (deltaX:"+deltaX+" deltaY:"+deltaY+")",0);

    connection.send(JSON.stringify({
        "event":"scroll",
        "data":{
            "xdelta": deltaX,
            "ydelta": deltaY
        }
    }));
}

/*
 * Drawing shapes
 */

function shapeFromData(message) {

    var shape = null;
    var debugMessage = "";
    var data = message.data;

    // both fill and stroke are in the form {"r": 255, "g": 255, "b": 255, ("a": 1.0)?}
    if(data["fill"]){
        data["fill"] = rgbaDictToColor(data["fill"]);
    }

    if(data["stroke"]){
        data["stroke"] = rgbaDictToColor(data["stroke"]);
    }
    // Debug message
    if(!data["id"] && debugOn) {
        data["id"] = "sid" + generadedShapeIdIdx;
        generadedShapeIdIdx++;
    }

    debugMessage = "Drawing " + message.type + " (" + data["id"] + ") ";

    // Init shape based on type
    switch (message.type) {
        case "line":
            shape = new Kinetic.Line(data);
            debugMessage += "with points: " + data.points;
            break;
        case "polygon":
            shape = new Kinetic.Polygon(data);
            break;
        case "circle":
            shape = new Kinetic.Circle(data);
            debugMessage += "width x:"+data.x+" y:"+data.y+" and radius:"+data.radius;
            break;
        case "rect":
            shape = new Kinetic.Rect(data);
            debugMessage += "width x:"+data.x+" y:"+data.y+" width:"+data.width+" height:"+data.height;
            break;
        case "text":
            shape = new Kinetic.Text(data);
            debugMessage += "width x:"+data.x+" y:"+data.y+" text:"+data.text;
            break;
        case "container":

            data.clip = [0, 0, data.width, data.height];
            shape = new Kinetic.Group(data);
            message.children.forEach(function(child) {
                shape.add(parseShapeData(child));
            });
            break;
        default:
            debugMessage = null;
            printDebugMessage("Unrecognized JSON message received from server.",2);
    }

    if(debugMessage)
        printDebugMessage(debugMessage,0);

    return shape;
}

function rgbaDictToColor(dict){
    var res = "";
    if(dict["a"]){
        res = "rgba({0},{1},{2},{3})".format(dict["r"], dict["g"], dict["b"], dict["a"]);
    } else {
        res = "rgb({0},{1},{2})".format(dict["r"], dict["g"], dict["b"]);
    }
    return res;
}

/*
* Visible debugger
*
* Type 0 = message
* Type 1 = warning
* Type 2 = error
*/

function printDebugMessage(message, type) {
    if(debugOn) {
        var now = new Date(),
            now = now.getHours()+':'+now.getMinutes()+':'+now.getSeconds();

        if(type == 1)
        {
            console.warn(message);
        }
        else if(type == 2)
        {
            console.error(message);
        }
        else
        {
            console.log(message);
        }

        $("#debug").prepend("
["+now+"] "+message+"
");

    }
}

var hideDebugConsole = function(){
        if(debugCanClose) {
            if($(this).width() == 20)
            {
                $(this).animate({
                    width: 350
                },300);
            }
            else
            {
                $(this).animate({
                    width: 20
                },300);
            }
        }
    };
// For flashing certain shapes while debugging
var debugSelector = function () {
    debugCanClose = false;
    var sid = $(this).data("sid"); // The shape id in an extended data attribute

    var shape = stage.find('#' + sid)[0];
    var alreadyAnnimatingIdx = $.inArray(shape, debugAnnimatingShapes);

    if(alreadyAnnimatingIdx == -1)
    {
        debugAnnimatingShapes.push(shape);

        var fill = shape.getFill();

        shape.setFill("red");
        layerList[topLayerIdx].draw();

        var interval = setInterval(function(){

            shape.setFill(fill);
            layerList[topLayerIdx].draw();

            clearInterval(interval);
            debugAnnimatingShapes.splice( alreadyAnnimatingIdx ,1 );
            debugCanClose = true;
        },250);
    }
};
function initDebug() {
    // Enable debug
    debugOn = true;
    // Debug selector for flashing shapes
    $("#debug").delegate( ".debugSelector", "click", debugSelector);
    // For hiding the debug console
    $("#debug").click(hideDebugConsole);
    $("#debug").show();
}
function debugOff() {
    debugOn = false;
    // Remove debug selector
    $("#debug").undelegate( ".debugSelector", "click", debugSelector);
    // Remove click event for hiding the console
    $("#debug").off('click',hideDebugConsole);
    // Clear debug screen and hide
    $("#debug").hide().html("");
}

/*
 * Canvas setup
 */

function initCanvas(container, width, height) {
    // Only init canvas when there is a container
    // Container is provided for testing purposes and extesibility
    if(container.exists()) {
        container.css( "width", width+"px" );
        container.css( "height", height+"px" );
        container.css( "margin-top", "-"+height/2+"px" );
        container.css( "margin-left", "-"+width/2+"px" );

        stage = new Kinetic.Stage({
            container: container.attr('id'),
            width: width, // Default width and height
            height: height // Can be changed with the resize canvas function
        });

        // Create new layer to draw on
        newDefaultLayer();

    }
}
function initWrapper(wrapper, width, height) {

    wrapper.css( "min-width", width+"px" );
    wrapper.css( "height", $( window ).height()+"px" );
}

function newDefaultLayer() {

    topLayerIdx++;
    layerList[topLayerIdx] = new Kinetic.Layer();
    stage.add(layerList[topLayerIdx]);
}

/*
 * On document ready
 */
$(document).ready(function() {

    var width = 900; // defined here because the container also needs these proportions
    var height = 600;

    // Init canvas
    initCanvas($('#canvas'),width,height);
    initWrapper($('#wrapper'),width,height);

    canvas = $("#canvas canvas");

    // Start listening for scroll events using mousewheel.js
    canvas.mousewheel(function(event) {
        sendScrollEvent(event.deltaX,event.deltaY);
        return false; // Prevent browser default behavior
    });

    if(debugOn) {
        initDebug();
    }
    else {
        debugOff();
    }

    // When the connection is open, send some data to the server
    connection.onopen = function () {
        printDebugMessage("Connection opened",0);
    };
    // Log errors
    connection.onerror = connectionError;
    // Callback for recieving data
    connection.onmessage = connectionDataReceived;

    // Begin to listen for keys
    window.addEventListener('keydown', sendKeyEvent.bind(this,'keydown'));

    window.addEventListener('keyup', sendKeyEvent.bind(this,'keyup'));
});
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import { layout } from 'd3';
import clone from 'clone';
import uuid from 'uuid';

import Node from '../Node';
import Link from '../Link';
import './style.css';

export default class Tree extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      initialRender: true,
      data: this.assignCustomProperties(clone(this.props.data)),
    };
    this.findTargetNode = this.findTargetNode.bind(this);
    this.collapseNode = this.collapseNode.bind(this);
    this.handleNodeToggle = this.handleNodeToggle.bind(this);
  }

  componentDidMount() {
    // TODO find better way of setting initialDepth, re-render here is suboptimal
    this.setState({ initialRender: false }); // eslint-disable-line
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.data !== nextProps.data) {
      this.setState({ data: this.assignCustomProperties(clone(nextProps.data)) });
    }
  }

  setInitialTreeDepth(nodeSet, initialDepth) {
    console.log('setInitialTreeDepth: ', initialDepth);
    nodeSet.forEach((n) => {
      n._collapsed = n.depth >= initialDepth;
    });
  }

  assignCustomProperties(data) {
    return data.map((node) => {
      node.id = uuid.v4();
      node._collapsed = false;
      // if there are children, recursively assign properties to them too
      if (node.children && node.children.length > 0) {
        node.children = this.assignCustomProperties(node.children);
        node._children = node.children;
      }
      return node;
    });
  }

  // TODO Refactor this into a more readable/reasonable recursive depth-first walk.
  findTargetNode(nodeId, nodeSet) {
    const hits = nodeSet.filter((node) => node.id === nodeId);

    if (hits.length > 0) {
      const targetNode = hits[0];
      return targetNode;
    }

    return nodeSet.map((node) => {
      if (node._children && node._children.length > 0) {
        return this.findTargetNode(nodeId, node._children);
      }
      return null;
    })[0];
  }

  collapseNode(node) {
    node._collapsed = true;
    if (node._children && node._children.length > 0) {
      node._children.forEach((child) => {
        this.collapseNode(child);
      });
    }
  }

  expandNode(node) {
    node._collapsed = false;
  }

  handleNodeToggle(nodeId) {
    if (this.props.collapsible) {
      const data = clone(this.state.data);
      const targetNode = this.findTargetNode(nodeId, data);
      targetNode._collapsed
        ? this.expandNode(targetNode)
        : this.collapseNode(targetNode);
      this.setState({ data });
    }
  }

  generateTree() {
    const { initialDepth } = this.props;
    const tree = layout.tree()
      .nodeSize([100 + 40, 100 + 40])
      .separation((d) => d._children ? 1.2 : 0.9)
      .children((d) => d._collapsed ? null : d._children);

    const rootNode = this.state.data[0];
    const nodes = tree.nodes(rootNode);
    const links = tree.links(nodes);

    // set `initialDepth` on first render if specified
    if (initialDepth !== undefined && this.state.initialRender) {
      this.setInitialTreeDepth(nodes, initialDepth);
    }

    return { nodes, links };
  }

  render() {
    const { orientation, translate, pathFunc } = this.props;
    const { nodes, links } = this.generateTree();
    return (

        <svg width="100%" height="100%">
          <g transform={`translate(${translate.x},${translate.y})`}>

            {nodes.map((nodeData) =>
              <Node
                key={nodeData.id}
                orientation={orientation}
                textAnchor="start"
                nodeData={nodeData}
                primaryLabel={nodeData.name}
                secondaryLabels={nodeData.attributes}
                onClick={this.handleNodeToggle}
              />
            )}

            {links.map((linkData) =>
              <Link
                key={uuid.v4()}
                orientation={orientation}
                pathFunc={pathFunc}
                linkData={linkData}
              />
            )}

          </g>
        </svg>

    );
  }
}

Tree.defaultProps = {
  orientation: 'horizontal',
  translate: { x: 0, y: 0 },
  pathFunc: 'diagonal',
  collapsible: true,
  initialDepth: undefined,
};

Tree.propTypes = {
  data: PropTypes.array.isRequired,
  orientation: PropTypes.oneOf([
    'horizontal',
    'vertical',
  ]),
  translate: PropTypes.shape({
    x: PropTypes.number,
    y: PropTypes.number,
  }),
  pathFunc: PropTypes.oneOf([
    'diagonal',
    'elbow',
  ]),
  collapsible: PropTypes.bool,
  initialDepth: PropTypes.number,
};
<commit_msg>Refactors `findTargetNode()` -> `findNodesById()`, enable recursive matching to arbitrary depth (#2)<commit_after>import React, { PropTypes } from 'react';
import { layout } from 'd3';
import clone from 'clone';
import uuid from 'uuid';

import Node from '../Node';
import Link from '../Link';
import './style.css';

export default class Tree extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      initialRender: true,
      data: this.assignCustomProperties(clone(this.props.data)),
    };
    this.findNodesById = this.findNodesById.bind(this);
    this.collapseNode = this.collapseNode.bind(this);
    this.handleNodeToggle = this.handleNodeToggle.bind(this);
  }

  componentDidMount() {
    // TODO find better way of setting initialDepth, re-render here is suboptimal
    this.setState({ initialRender: false }); // eslint-disable-line
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.data !== nextProps.data) {
      this.setState({ data: this.assignCustomProperties(clone(nextProps.data)) });
    }
  }

  setInitialTreeDepth(nodeSet, initialDepth) {
    console.log('setInitialTreeDepth: ', initialDepth);
    nodeSet.forEach((n) => {
      n._collapsed = n.depth >= initialDepth;
    });
  }

  assignCustomProperties(data) {
    return data.map((node) => {
      node.id = uuid.v4();
      node._collapsed = false;
      // if there are children, recursively assign properties to them too
      if (node.children && node.children.length > 0) {
        node.children = this.assignCustomProperties(node.children);
        node._children = node.children;
      }
      return node;
    });
  }

  // TODO Refactor this into a more readable/reasonable recursive depth-first walk.
  findNodesById(nodeId, nodeSet, hits) {
    if (hits.length > 0) {
      return hits;
    }

    hits = hits.concat(nodeSet.filter((node) => node.id === nodeId));

    nodeSet.forEach((node) => {
      if (node._children && node._children.length > 0) {
        hits = this.findNodesById(nodeId, node._children, hits);
        return hits;
      }
      return hits;
    });

    return hits;
  }

  collapseNode(node) {
    node._collapsed = true;
    if (node._children && node._children.length > 0) {
      node._children.forEach((child) => {
        this.collapseNode(child);
      });
    }
  }

  expandNode(node) {
    node._collapsed = false;
  }

  handleNodeToggle(nodeId) {
    if (this.props.collapsible) {
      const data = clone(this.state.data);
      const matches = this.findNodesById(nodeId, data, []);
      const targetNode = matches[0];
      targetNode._collapsed
        ? this.expandNode(targetNode)
        : this.collapseNode(targetNode);
      this.setState({ data });
    }
  }

  generateTree() {
    const { initialDepth } = this.props;
    const tree = layout.tree()
      .nodeSize([100 + 40, 100 + 40])
      .separation((d) => d._children ? 1.2 : 0.9)
      .children((d) => d._collapsed ? null : d._children);

    const rootNode = this.state.data[0];
    const nodes = tree.nodes(rootNode);
    const links = tree.links(nodes);

    // set `initialDepth` on first render if specified
    if (initialDepth !== undefined && this.state.initialRender) {
      this.setInitialTreeDepth(nodes, initialDepth);
    }

    return { nodes, links };
  }

  render() {
    const { orientation, translate, pathFunc } = this.props;
    const { nodes, links } = this.generateTree();
    return (

        <svg width="100%" height="100%">
          <g transform={`translate(${translate.x},${translate.y})`}>

            {nodes.map((nodeData) =>
              <Node
                key={nodeData.id}
                orientation={orientation}
                textAnchor="start"
                nodeData={nodeData}
                primaryLabel={nodeData.name}
                secondaryLabels={nodeData.attributes}
                onClick={this.handleNodeToggle}
              />
            )}

            {links.map((linkData) =>
              <Link
                key={uuid.v4()}
                orientation={orientation}
                pathFunc={pathFunc}
                linkData={linkData}
              />
            )}

          </g>
        </svg>

    );
  }
}

Tree.defaultProps = {
  orientation: 'horizontal',
  translate: { x: 0, y: 0 },
  pathFunc: 'diagonal',
  collapsible: true,
  initialDepth: undefined,
};

Tree.propTypes = {
  data: PropTypes.array.isRequired,
  orientation: PropTypes.oneOf([
    'horizontal',
    'vertical',
  ]),
  translate: PropTypes.shape({
    x: PropTypes.number,
    y: PropTypes.number,
  }),
  pathFunc: PropTypes.oneOf([
    'diagonal',
    'elbow',
  ]),
  collapsible: PropTypes.bool,
  initialDepth: PropTypes.number,
};
<|endoftext|>


<commit_before>// commands for math

// NOTE: we use the fallback type for most of these because they can be either numbers or arrays.

DAML.import_models({
  math: {
    desc: "Commands for math",
    methods: {

      add: {
        desc: "What kind of snake is good at math?",
        help: [
          'The value and to parameters can be numbers or arrays of numbers.',
          '',
          'Both numbers: Add the two numbers.',
          'One array, no second parameter: Sum the numbers in the array.',
          'One array, one number: Add the number to each item in the array.',
          'Both arrays: Add elements of the arrays pairwise by key',
          '',
          'You can use "add" as an alias for this command.',
        ],
        examples: [
          '{add 4 to 7}',
          '{7 | add 4}',
          '{add (1 2 3)}',
          '{(1 2 3) | add 3}',
          '{math add value (1 2 3) to (6 5 4)}',
        ],
        params: [
          {
            key: 'value',
            desc: "Augend: a numeric value or array of them",
            type: 'anything',
            required: true
          },
          {
            key: 'to',
            type: 'anything',
            desc: "Addend: a numeric value or array of the same",
          },
        ],
        fun: function(value, to) {
          return DAML.ETC.Math.solver(value, to, function(a, b) {return a + b;});
        },
      },

      multiply: {
        desc: "Go fort hand",
        help: [
          'The value and to parameters can be numbers or arrays of numbers.',
          '',
          'Both numbers: Multiply the two numbers.',
          'One array, no second parameter: Multiply the numbers in the array.',
          'One array, one number: Multiply the number to each item in the array.',
          'Both arrays: Multiply elements of the arrays pairwise by key',
          '',
          'You can use "multiply" as an alias for this command.',
        ],
        examples: [
          '{multiply 4 by 7}',
          '{7 | multiply 4}',
          '{multiply (1 2 3)}',
          '{(1 2 3) | multiply 3}',
          '{math multiply value (1 2 3) by (6 5 4)}',
        ],
        params: [
          {
            key: 'value',
            desc: "Factor the first: a numeric value or array of them",
            type: 'anything',
            required: true
          },
          {
            key: 'by',
            desc: "Factor the first: a numeric value or array of such",
            type: 'anything',
          },
        ],
        fun: function(value, by) {
          return DAML.ETC.Math.solver(value, by, function(a, b) {return a * b;});
        },
      },

      subtract: {
        desc: "Subtract them one from another",
        help: [
          'The value and to parameters can be numbers or arrays of numbers.',
          '',
          'Both numbers: Subtract the two numbers.',
          'One array, no second parameter: Subtract each subsequent item from the first array element.',
          'One array, one number: Subtract the number from each item in the array.',
          'Both arrays: Subtract elements of the second array from the first, pairwise by key',
          '',
          'You can use "subtract" as an alias for this command.',
        ],
        examples: [
          '{subtract 4 from 7}',
          '{7 | subtract 4}',
          '{subtract (100 2 3 4 5)}',
          '{(1 3 5 7) | subtract 3}',
          '{math subtract value (6 5 4) from (1 2 3)}',
        ],
        params: [
          {
            key: 'value',
            desc: "Subtrahend: a numeric value or array of them",
            type: 'anything',
            required: true
          },
          {
            key: 'from',
            desc: "Minuend: a numeric value or array of such",
            type: 'anything',
          },
        ],
        fun: function(value, from) {
          return DAML.ETC.Math.solver(from, value, function(a, b) {return a - b;});
        },
      },

      divide: {
        desc: "A method for conquering",
        help: [
          'The value and to parameters can be numbers or arrays of numbers.',
          '',
          'Both numbers: Divide the two numbers.',
          'One array, no second parameter: Divide the first number in the array by each other number.',
          'One array, one number: Divide each item in the array by the number.',
          'Both arrays: Divide elements of the arrays pairwise by key',
          '',
          'You can use "divide" as an alias for this command. When ambiguous read it as _divides_ instead of _divide by_ -- the second example will be confusing otherwise.',
        ],
        examples: [
          '{divide value 7 by 4}',
          '{7 | divide by 4}',
          '{divide value (1 2 3)}',
          '{(1 2 3) | divide by 3}',
          '{math divide value (1 2 3) by (6 5 4)}',
        ],
        params: [
          {
            key: 'value',
            desc: "Numerator: a numeric value or array of them",
            type: 'anything',
            required: true
          },
          {
            key: 'by',
            desc: "Denominator: a numeric value or array of such",
            type: 'anything',
          },
        ],
        fun: function(value, by) {
          return DAML.ETC.Math.solver(value, by, function(a, b) {
            if(!b) DAML.setError('Division by zero is a crime against nature');
            return a / b;
          });
        },
      },

      mod: {
        desc: "Mod some stuff by some other stuff",
        help: [
          'Take the modulo of a value with respect to another value.'
        ],
        examples: [
          '{math mod value 7 by 2}',
          '{7 | mod 2}',
        ],
        params: [
          {
            key: 'value',
            desc: "A value to be modded",
            type: 'anything',
            required: true
          },
          {
            key: 'by',
            desc: "Value to mod it by",
            type: 'anything',
          },
        ],
        fun: function(value, by) {
          return DAML.ETC.Math.solver(value, by, function(a, b) {return a % b;});
        },
      },

      pow: {
        desc: "A smack in the face to exponents of exponentiation",
        help: [
          'This raises value to the exp. Fractional exponents are fine, so the square root of five is {5 | math pow exp :0.5}.',
        ],
        examples: [
          '{math pow value 2 exp 8}',
          '{5 | math pow exp :3}',
          '{5 | math pow exp :0.5}',
        ],
        params: [
          {
            key: 'value',
            desc: "Base",
            type: "number",
            required: true
          },
          {
            key: 'exp',
            desc: "Exponent",
            type: "number",
            required: true
          },
        ],
        fun: function(value, exp) {
          // THINK: can we solver this?
          return Math.pow(value, exp);
        },
      },

      less: {
        desc: "Is value less than than?",
        params: [
          {
            key: 'value',
            desc: "A value",
            type: "number",
            required: true
          },
          {
            key: 'than',
            desc: "Another value",
            type: "number",
            required: true
          },
        ],
        fun: function(value, than) {
          return value < than;
        },
      },

      random: {
        desc: "There's random, and then there's AYN random",
        params: [
          {
            key: 'max',
            desc: "Maximum value (defaults to 1)",
            type: "number",
          },
        ],
        fun: function(max) {
          if(!max) max = 1;
          return Math.floor(Math.random() * (max + 1));
        },
      },

      sin: {
        desc: "Find out if yours is original",
        params: [
          {
            key: 'value',
            desc: "In degrees -- I know, right?",
            type: "number",
          },
        ],
        fun: function(value) {
          return Math.sin(Math.PI * value / 180);
        },
      },

      cos: {
        desc: "The reason we did it: jus",
        params: [
          {
            key: 'value',
            desc: "In degrees -- I know, right?",
            type: "number",
          },
        ],
        fun: function(value) {
          return Math.cos(Math.PI * value / 180);
        },
      },

      round: {
        desc: "Round yourself out",
        params: [
          {
            key: 'value',
            desc: "A number",
            type: "number",
            required: true
          },
          {
            key: 'to',
            desc: "Significant digits",
            type: "number",
          },
        ],
        fun: function(value, to) {
          // THINK: can we accept an array to round?

          if(!to) return Math.round(value);

          var power = Math.pow(10, to);
          return Math.round(value * power) / power;
        },
      },

    }
  }
});

DAML.ETC.Math = {}

DAML.ETC.Math.solver = function(value, to, fun) {
  // are these arrays or numbers?
  // var arrays = (typeof value == 'object') + (typeof to == 'object');
  var arrays = Array.isArray(value) + Array.isArray(to)

  // THINK: maybe wrap these with DAML.ETC.toNumeric to keep out NaNs
  if(arrays == 2) return DAML.ETC.Math.doubleArray(value, to, fun);
  if(arrays == 1) return DAML.ETC.Math.singleArray(value, to, fun);
  if(arrays == 0) return DAML.ETC.Math.naryanArray(value, to, fun);
};

DAML.ETC.Math.doubleArray = function(value, to, fun) {
  return value.map(function(val, key) {
    return fun(DAML.ETC.toNumeric(val), DAML.ETC.toNumeric(to[key]));
  });
};

DAML.ETC.Math.singleArray = function(value, to, fun) {
  // ensure value is the array
  if(typeof value != 'object') {
    var temp = to; to = value; value = temp;
  }

  // one array, one number
  if(DAML.ETC.isNumeric(to)) {
    return value.map(function(val) {
      return fun(DAML.ETC.toNumeric(val), DAML.ETC.toNumeric(to));
    });
  }

  // just the one array
  var total = false;
  value = DAML.toArray(value);
  for(var i=0, l=value.length; i < l; i++) {
    // NOTE: this essentially bypasses identity concerns -- total=0 poisons *, total=1 taints +. it means subtraction and division are relative to the first value in the array, but that's ok.
    if(total === false) total = DAML.ETC.toNumeric(value[i]);
    else total = fun(total, DAML.ETC.toNumeric(value[i]));
  }
  return total;
};

DAML.ETC.Math.naryanArray = function(value, to, fun) {
  if(!DAML.ETC.isNumeric(value)) {
    DAML.setError("That is not a numeric value")
    value = 0
  }
  if(!DAML.ETC.isNumeric(to)) {
    // DAML.ETC.toNumeric(value)
    to = 0
  }
  return fun(DAML.ETC.toNumeric(value), DAML.ETC.toNumeric(to));
};
<commit_msg>math commands now accept keyed lists<commit_after>// commands for math

// NOTE: we use the fallback type for most of these because they can be either numbers or arrays.

DAML.import_models({
  math: {
    desc: "Commands for math",
    methods: {

      add: {
        desc: "What kind of snake is good at math?",
        help: [
          'The value and to parameters can be numbers or arrays of numbers.',
          '',
          'Both numbers: Add the two numbers.',
          'One array, no second parameter: Sum the numbers in the array.',
          'One array, one number: Add the number to each item in the array.',
          'Both arrays: Add elements of the arrays pairwise by key',
          '',
          'You can use "add" as an alias for this command.',
        ],
        examples: [
          '{add 4 to 7}',
          '{7 | add 4}',
          '{add (1 2 3)}',
          '{(1 2 3) | add 3}',
          '{math add value (1 2 3) to (6 5 4)}',
        ],
        params: [
          {
            key: 'value',
            desc: "Augend: a numeric value or array of them",
            type: 'anything',
            required: true
          },
          {
            key: 'to',
            type: 'anything',
            desc: "Addend: a numeric value or array of the same",
          },
        ],
        fun: function(value, to) {
          return DAML.ETC.Math.solver(value, to, function(a, b) {return a + b;});
        },
      },

      multiply: {
        desc: "Go fort hand",
        help: [
          'The value and to parameters can be numbers or arrays of numbers.',
          '',
          'Both numbers: Multiply the two numbers.',
          'One array, no second parameter: Multiply the numbers in the array.',
          'One array, one number: Multiply the number to each item in the array.',
          'Both arrays: Multiply elements of the arrays pairwise by key',
          '',
          'You can use "multiply" as an alias for this command.',
        ],
        examples: [
          '{multiply 4 by 7}',
          '{7 | multiply 4}',
          '{multiply (1 2 3)}',
          '{(1 2 3) | multiply 3}',
          '{math multiply value (1 2 3) by (6 5 4)}',
        ],
        params: [
          {
            key: 'value',
            desc: "Factor the first: a numeric value or array of them",
            type: 'anything',
            required: true
          },
          {
            key: 'by',
            desc: "Factor the first: a numeric value or array of such",
            type: 'anything',
          },
        ],
        fun: function(value, by) {
          return DAML.ETC.Math.solver(value, by, function(a, b) {return a * b;});
        },
      },

      subtract: {
        desc: "Subtract them one from another",
        help: [
          'The value and to parameters can be numbers or arrays of numbers.',
          '',
          'Both numbers: Subtract the two numbers.',
          'One array, no second parameter: Subtract each subsequent item from the first array element.',
          'One array, one number: Subtract the number from each item in the array.',
          'Both arrays: Subtract elements of the second array from the first, pairwise by key',
          '',
          'You can use "subtract" as an alias for this command.',
        ],
        examples: [
          '{subtract 4 from 7}',
          '{7 | subtract 4}',
          '{subtract (100 2 3 4 5)}',
          '{(1 3 5 7) | subtract 3}',
          '{math subtract value (6 5 4) from (1 2 3)}',
        ],
        params: [
          {
            key: 'value',
            desc: "Subtrahend: a numeric value or array of them",
            type: 'anything',
            required: true
          },
          {
            key: 'from',
            desc: "Minuend: a numeric value or array of such",
            type: 'anything',
          },
        ],
        fun: function(value, from) {
          return DAML.ETC.Math.solver(from, value, function(a, b) {return a - b;});
        },
      },

      divide: {
        desc: "A method for conquering",
        help: [
          'The value and to parameters can be numbers or arrays of numbers.',
          '',
          'Both numbers: Divide the two numbers.',
          'One array, no second parameter: Divide the first number in the array by each other number.',
          'One array, one number: Divide each item in the array by the number.',
          'Both arrays: Divide elements of the arrays pairwise by key',
          '',
          'You can use "divide" as an alias for this command. When ambiguous read it as _divides_ instead of _divide by_ -- the second example will be confusing otherwise.',
        ],
        examples: [
          '{divide value 7 by 4}',
          '{7 | divide by 4}',
          '{divide value (1 2 3)}',
          '{(1 2 3) | divide by 3}',
          '{math divide value (1 2 3) by (6 5 4)}',
        ],
        params: [
          {
            key: 'value',
            desc: "Numerator: a numeric value or array of them",
            type: 'anything',
            required: true
          },
          {
            key: 'by',
            desc: "Denominator: a numeric value or array of such",
            type: 'anything',
          },
        ],
        fun: function(value, by) {
          return DAML.ETC.Math.solver(value, by, function(a, b) {
            if(!b) DAML.setError('Division by zero is a crime against nature');
            return a / b;
          });
        },
      },

      mod: {
        desc: "Mod some stuff by some other stuff",
        help: [
          'Take the modulo of a value with respect to another value.'
        ],
        examples: [
          '{math mod value 7 by 2}',
          '{7 | mod 2}',
        ],
        params: [
          {
            key: 'value',
            desc: "A value to be modded",
            type: 'anything',
            required: true
          },
          {
            key: 'by',
            desc: "Value to mod it by",
            type: 'anything',
          },
        ],
        fun: function(value, by) {
          return DAML.ETC.Math.solver(value, by, function(a, b) {return a % b;});
        },
      },

      pow: {
        desc: "A smack in the face to exponents of exponentiation",
        help: [
          'This raises value to the exp. Fractional exponents are fine, so the square root of five is {5 | math pow exp :0.5}.',
        ],
        examples: [
          '{math pow value 2 exp 8}',
          '{5 | math pow exp :3}',
          '{5 | math pow exp :0.5}',
        ],
        params: [
          {
            key: 'value',
            desc: "Base",
            type: "number",
            required: true
          },
          {
            key: 'exp',
            desc: "Exponent",
            type: "number",
            required: true
          },
        ],
        fun: function(value, exp) {
          // THINK: can we solver this?
          return Math.pow(value, exp);
        },
      },

      less: {
        desc: "Is value less than than?",
        params: [
          {
            key: 'value',
            desc: "A value",
            type: "number",
            required: true
          },
          {
            key: 'than',
            desc: "Another value",
            type: "number",
            required: true
          },
        ],
        fun: function(value, than) {
          return value < than;
        },
      },

      random: {
        desc: "There's random, and then there's AYN random",
        params: [
          {
            key: 'max',
            desc: "Maximum value (defaults to 1)",
            type: "number",
          },
        ],
        fun: function(max) {
          if(!max) max = 1;
          return Math.floor(Math.random() * (max + 1));
        },
      },

      sin: {
        desc: "Find out if yours is original",
        params: [
          {
            key: 'value',
            desc: "In degrees -- I know, right?",
            type: "number",
          },
        ],
        fun: function(value) {
          return Math.sin(Math.PI * value / 180);
        },
      },

      cos: {
        desc: "The reason we did it: jus",
        params: [
          {
            key: 'value',
            desc: "In degrees -- I know, right?",
            type: "number",
          },
        ],
        fun: function(value) {
          return Math.cos(Math.PI * value / 180);
        },
      },

      round: {
        desc: "Round yourself out",
        params: [
          {
            key: 'value',
            desc: "A number",
            type: "number",
            required: true
          },
          {
            key: 'to',
            desc: "Significant digits",
            type: "number",
          },
        ],
        fun: function(value, to) {
          // THINK: can we accept an array to round?

          if(!to) return Math.round(value);

          var power = Math.pow(10, to);
          return Math.round(value * power) / power;
        },
      },

    }
  }
});

DAML.ETC.Math = {}

DAML.ETC.Math.solver = function(value, to, fun) {
  // TODO: we don't need this if the type is "array|number"
  value = (typeof value == 'object') ? DAML.toArray(value) : value
  to = (typeof to == 'object') ? DAML.toArray(to) : to
  // var arrays = (typeof value == 'object') + (typeof to == 'object');

  // are these arrays or numbers?
  var arrays = Array.isArray(value) + Array.isArray(to)

  // THINK: maybe wrap these with DAML.ETC.toNumeric to keep out NaNs
  if(arrays == 2) return DAML.ETC.Math.doubleArray(value, to, fun);
  if(arrays == 1) return DAML.ETC.Math.singleArray(value, to, fun);
  if(arrays == 0) return DAML.ETC.Math.naryanArray(value, to, fun);
};

DAML.ETC.Math.doubleArray = function(value, to, fun) {
  return value.map(function(val, key) {
    return fun(DAML.ETC.toNumeric(val), DAML.ETC.toNumeric(to[key]));
  });
};

DAML.ETC.Math.singleArray = function(value, to, fun) {
  // ensure value is the array
  if(typeof value != 'object') {
    var temp = to; to = value; value = temp;
  }

  // one array, one number
  if(DAML.ETC.isNumeric(to)) {
    return value.map(function(val) {
      return fun(DAML.ETC.toNumeric(val), DAML.ETC.toNumeric(to));
    });
  }

  // just the one array
  var total = false;
  value = DAML.toArray(value);
  for(var i=0, l=value.length; i < l; i++) {
    // NOTE: this essentially bypasses identity concerns -- total=0 poisons *, total=1 taints +. it means subtraction and division are relative to the first value in the array, but that's ok.
    if(total === false) total = DAML.ETC.toNumeric(value[i]);
    else total = fun(total, DAML.ETC.toNumeric(value[i]));
  }
  return total;
};

DAML.ETC.Math.naryanArray = function(value, to, fun) {
  if(!DAML.ETC.isNumeric(value)) {
    DAML.setError("That is not a numeric value")
    value = 0
  }
  if(!DAML.ETC.isNumeric(to)) {
    // DAML.ETC.toNumeric(value)
    to = 0
  }
  return fun(DAML.ETC.toNumeric(value), DAML.ETC.toNumeric(to));
};
<|endoftext|>


<commit_before>define(['/common/LogManager.js','/common/EventDispatcher.js', './../../common/CommonUtil.js', '/socket.io/socket.io.js'],function(LogManager, EventDispatcher, commonUtil){
    var logger,
        Client,
        CommandQueue,
        LocalCommander,
        Storage,
        ClientNode,
        ClientNode2,
        Territory;

    logger = LogManager.create("Client");

    Client = function(server){
        var socket = io.connect(server),
            connected = false,
            storage = new Storage(),
            self = this,
            commandqueue = new CommandQueue(self,storage),
            clipboard = {},
            updateStorage,
            updateUsers,
            users ={},
            shootEvent,
            handleMessage,
            selectedObjectId = null;

        /*event functions to relay information between users*/
        $.extend(this, new EventDispatcher());

        this.events = {
            "SELECTEDOBJECT_CHANGED" : "SELECTEDOBJECT_CHANGED"
        };
        this.setSelectedObjectId = function ( objectId ) {
            if ( objectId !== selectedObjectId ) {
                selectedObjectId = objectId;

                self.dispatchEvent( self.events.SELECTEDOBJECT_CHANGED, selectedObjectId );
            }
        };

        /*socket functions*/
        socket.on('connect',function(msg){
            connected = true;
        });
        socket.on('reconnecting',function(msg){
            connected = false;
        });
        socket.on('serverMessage',function(msg){
            socket.emit('serverMessageAck');
            handleMessage(msg);
        });
        socket.on('clientMessageAck',function(msg){

        });
        socket.on('clientMessageNack',function(msg){

        });

        /*MGA like functions*/
        this.setAttributes = function(id,name,value){
            var data,
                attributes,
                command;

            data = storage.get(id);
            if(data){
                attributes = commonUtil.copy(data.attributes);
                attributes[name] = value;
                commandqueue.push({type:"modify",cid:"ezminek",id:id,attributes:attributes});
            }
        };
        this.setRegistry = function(id,name,value){
            var data,
                registry,
                command;

            data = storage.get(id);
            if(data){
                registry = commonUtil.copy(data.registry);
                registry[name] = value;
                commandqueue.push({type:"modify",cid:"ezminek",id:id,registry:registry});
            }
        };
        this.copyNodes = function(ids){
            clipboard = ids;
            commandqueue.push({type:"copy",ids:clipboard,cid:"tenyleg kell??"});
        };
        this.pasteNodes = function(id){
            commandqueue.push({type:"paste",id:id,cid:"commandid"});
        };
        this.deleteNode = function(id){
            commandqueue.push({type:"delete",id:id,cid:"tuti nem kell"});
        };
        this.delMoreNodes = function(ids){
            var i;
            for(i=0;i<ids.length;i++){
                self.deleteNode(ids[i]);
            }
        };
        this.createChild = function(parent,base){
            commandqueue.push({type:"createChild",baseId:base,parentId:parent,cid:"mondom nem kell"});
        };
        this.createSubType = function(parent,base){
            commandqueue.push({type:"createSubType",baseId:base,parentId:parent,cid:"mondom nem kell"});
        };
        this.makePointer = function(id,name,to){
            commandqueue.push({type:"point",id:id,to:to,name:name});
        };
        this.delPointer = function(id,name){
            commandqueue.push({type:"point",id:id,to:null,name:name});
        };
        this.makeConnection = function(parameters){
            var baseId,
                guid;
            if(parameters.parentId && parameters.sourceId && parameters.targetId){
                baseId = parameters.baseId || "connection";
                guid = commonUtil.guid();
                commandqueue.push({type:"createChild",baseId:baseId,parentId:parameters.parentId,newguid:guid,cid:"connnection 001"});
                commandqueue.push({type:"point",id:guid,name:"source",to:parameters.sourceId,cid:"connection 002"});
                commandqueue.push({type:"point",id:guid,name:"target",to:parameters.targetId,cid:"connection 003"});
                if(parameters.directed !== null parameters.directed !== undefined){
                    self.setAttributes(guid,"directed",parameters.directed);
                }
            }
            else{
                logger.error("fraudulent connection creation: "+JSON.stringify(parameters));
            }
        };

        /*UserInterface handling*/
        this.addUI = function(ui){
            var guid;
            guid = commonUtil.guid();
            users[guid] = new User(self,ui,storage);
            return guid;
        };
        this.removeUI = function(guid){
            delete users[guid];
        };
        this.updateTerritory = function(guid,patterns){
            if(users[guid]){
                users[guid].updatePatterns(patterns);
                commandqueue.push({type:"territory",cid:"nemkollll",id:guid,patterns:users[guid].getPatterns()});
            }
        };

        /*getting a node*/
        this.getNode = function(id){
            if(storage.get(id)){
                return new ClientNode(self,id,storage);
            }
            else{
                return storage.get(id);
            }
        };

        /*socket like functions*/
        this.sendMessage = function(msg){
            socket.emit('clientMessage',msg);
        };
        /*private functions*/
        shootEvent = function(type,id){
            var i,
                data;
            data = storage.get(id);
            for(i in users){
                users[i].shootEvent(id,type);
            }

            if(data && type === "modify"){
                if(data.relations.inheritorIds){
                    for(i=0;i<data.relations.inheritorIds.length;i++){
                        shootEvent(type,data.relations.inheritorIds[i]);
                    }
                }
            }
        };
        handleMessage = function(msg){
            var i;

            /*main*/
            /*if the transaction failed, there is nothing to do*/
            for(i=0;i<msg.length;i++){
                if(msg[i].type === "command"){
                    commandqueue.commandResult(msg[i].cid,msg[i].success);
                    if(msg[i].success === "false"){
                        return;
                    }
                }
            }

            /*first we shoot the unload events*/
            for(i=0;i<msg.length;i++){
                if(msg[i].type === "unload"){
                    shootEvent("unload",msg[i].id);
                }
            }
            /*then we update the storage*/
            for(i=0;i<msg.length;i++){
                if(msg[i].type !== "command"){
                    storage.set(msg[i].id,msg[i].object);
                }
            }
            /*then we update the users*/
            for(i in users){
                users[i].updateTerritory();
            }
            /*then we shoot all other events*/
            for(i=0;i<msg.length;i++){
                if(msg[i].type === "load" || msg[i].type === "update" || msg[i].type === "create" || msg[i].type === "delete"){
                    shootEvent(msg[i].type,msg[i].id);
                }
            }
        };
    };
    User = function(client,ui,storage){
        var patterns = {},
            ids=[];

        /*public functions*/
        this.getPatterns = function(){
            return patterns;
        };
        this.updatePatterns = function(newpatterns){
            patterns = newpatterns;
        };
        this.shootEvent = function(id,type){
            if(ids.indexOf(id)!== -1){
                ui.onEvent(type,id);
            }
        };
        this.updateTerritory = function(){
            var i,
                updatePattern,
                updateRule;

            updateRule = function(id,rulename,rulevalue,itemssofar){
                var i,
                    data = storage.get(id);

                if(data === null || data === undefined){
                    return;
                }

                if(commonUtil.insertIntoArray(itemssofar,id)){
                    if(rulename === "pointer"){
                        if(rulevalue.value === 0){
                            return;
                        }
                        if(data.pointers[rulevalue.name]){
                            if(data.pointers[rulevalue.name].to){
                                updateRule(data.pointers[rulevalue.name].to,rulename,{name:rulevalue.name,value:rulevalue.value-1},itemssofar);
                            }
                        }
                        return;
                    }
                    else{
                        if(rulevalue === 0){
                            return;
                        }
                        switch(rulename){
                            case "parent":
                                if(data.relations.parentId){
                                    updateRule(data.relations.parentId,rulename,rulevalue-1,itemssofar);
                                }
                                break;
                            case "children":
                                if(data.relations.childrenIds){
                                    for(i=0;i<data.relations.childrenIds.length;i++){
                                        updateRule(data.relations.childrenIds[i],rulename,rulevalue-1,itemssofar);
                                    }
                                }
                                break;
                            case "inheritor":
                                if(data.relations.inheritorIds){
                                    for(i=0;i<data.relations.inheritorIds.length;i++){
                                        updateRule(data.relations.inheritorIds[i],rulename,rulevalue-1,itemssofar);
                                    }
                                }
                                break;
                        }
                    }
                }
                else{
                    return; /*we found a loop*/
                }
            };
            updatePattern = function(id,pattern){
                var i,
                    rulechain;
                for(i in pattern){
                    rulechain = [];
                    updateRule(id,i,commonUtil.copy(pattern[i]),rulechain);
                    ids = commonUtil.mergeArrays(ids,rulechain);
                }
            };
            /*main*/
            ids = [];
            for(i in patterns){
                updatePattern(i,patterns[i]);
            }
        };
    };
    CommandQueue = function(client,storage){
        var queue = {},
            sent = {},
            timer = 10000;
            cansend = true;

        /*public functions*/
        this.push = function(command){
            if(command.type === "territory"){
                /*these can go paralelly without cid*/
                client.sendMessage({commands:[command]});
                return;
            }
            queue[command.cid] = command;
            setTimeout(function(){
                commandTimeout(command.cid);
            },timer);
            sendNextClientMessage();
        };
        this.commandResult = function(cid,success){
            /*TODO success*/
            delete sent[cid];

            if(isSentEmpty()){
                cansend = true;
                sendNextClientMessage();
            }
        };
        var isSentEmpty = function(){
            var counter = 0;
            for(var i in sent){
                counter++;
            }
            if(counter === 0){
                return true;
            }
            return false;
        };
        var sendNextClientMessage = function(){
            logger.debug("trying to send next command message to server (cansend="+cansend+",sentisempty="+isSentEmpty());
            if(cansend ===true || isSentEmpty()){
                cansend = true;
                var msg = {commands:[]};
                for(var i in queue){
                    msg.commands.push(queue[i]);
                }
                logger.debug("next command message to server "+JSON.stringify(msg));
                if(msg.commands.length>0){
                    client.sendMessage(msg);
                    cansend = false;
                    sent = queue;
                    queue = {};
                }
            }
        };
        var commandTimeout = function(cid){
            if(queue[cid] || sent[cid]){
                console.log("command timeout "+cid);
            }
            delete queue[cid];
            delete sent[cid];

            if(isSentEmpty()){
                cansend = true;
                sendNextClientMessage();
            }
        };
    };
    Storage = function(client){
        var objects = {};

        /*public functions*/
        this.get = function(id){
            logger.debug("Storage.get "+id);
            return objects[id];
        };
        this.set = function(id,object){
            logger.debug("Storage.set "+id+","+JSON.stringify(object));
            objects[id] = object;
        };
    };
    ClientNode = function(client,id,storage){
        var selfdata = storage.get(id),
            rGetAttribute,
            rGetRegistry;
        /*public funcitons*/
        this.isDeleted = function(){
            return selfdata === null;
        };
        this.getParentId = function(){
            if(selfdata){
                return selfdata.relations.parentId;
            }
            else{
                return selfdata;
            }
        };
        this.getId = function(){
            if(selfdata){
                return selfdata._id;
            }
            else{
                return selfdata;
            }
        };
        this.getChildrenIds = function(){
            if(selfdata){
                return selfdata.relations.childrenIds;
            }
            else{
                return selfdata;
            }
        };
        this.getBaseId = function(){
            if(selfdata){
                return selfdata.relations.baseId;
            }
            else{
                return selfdata;
            }
        };
        this.getInheritorIds = function(){
            if(selfdata){
                return selfdata.relations.inheritorIds;
            }
            else{
                return selfdata
            }
        };
        this.getAttribute = function(name){
            if(selfdata){
                return rGetAttribute(id,name);
            }
            else{
                return selfdata;
            }
        };
        this.getRegistry = function(name){
            if(selfdata){
                return rGetRegistry(id,name);
            }
            else{
                return selfdata;
            }
        };

        /*private functions*/
        rGetAttribute = function(id,name){
            var data;
            data = storage.get(id);
            if(data){
                if(data.attributes[name]){
                    return data.attributes[name];
                }
                else{
                    if(data.relations.baseId){
                        return rGetAttribute(data.relations.baseId,name);
                    }
                    else{
                        return null;
                    }
                }
            }
            else{
                return data;
            }
        };
        rGetRegistry = function(id,name){
            var data;
            data = storage.get(id);
            if(data){
                if(data.registry[name]){
                    return data.registry[name];
                }
                else{
                    if(data.relations.baseId){
                        return rGetRegistry(data.relations.baseId,name);
                    }
                    else{
                        return null;
                    }
                }
            }
            else{
                return data;
            }

        };
    };
    return Client;
});
<commit_msg>syntax error<commit_after>define(['/common/LogManager.js','/common/EventDispatcher.js', './../../common/CommonUtil.js', '/socket.io/socket.io.js'],function(LogManager, EventDispatcher, commonUtil){
    var logger,
        Client,
        CommandQueue,
        LocalCommander,
        Storage,
        ClientNode,
        ClientNode2,
        Territory;

    logger = LogManager.create("Client");

    Client = function(server){
        var socket = io.connect(server),
            connected = false,
            storage = new Storage(),
            self = this,
            commandqueue = new CommandQueue(self,storage),
            clipboard = {},
            updateStorage,
            updateUsers,
            users ={},
            shootEvent,
            handleMessage,
            selectedObjectId = null;

        /*event functions to relay information between users*/
        $.extend(this, new EventDispatcher());

        this.events = {
            "SELECTEDOBJECT_CHANGED" : "SELECTEDOBJECT_CHANGED"
        };
        this.setSelectedObjectId = function ( objectId ) {
            if ( objectId !== selectedObjectId ) {
                selectedObjectId = objectId;

                self.dispatchEvent( self.events.SELECTEDOBJECT_CHANGED, selectedObjectId );
            }
        };

        /*socket functions*/
        socket.on('connect',function(msg){
            connected = true;
        });
        socket.on('reconnecting',function(msg){
            connected = false;
        });
        socket.on('serverMessage',function(msg){
            socket.emit('serverMessageAck');
            handleMessage(msg);
        });
        socket.on('clientMessageAck',function(msg){

        });
        socket.on('clientMessageNack',function(msg){

        });

        /*MGA like functions*/
        this.setAttributes = function(id,name,value){
            var data,
                attributes,
                command;

            data = storage.get(id);
            if(data){
                attributes = commonUtil.copy(data.attributes);
                attributes[name] = value;
                commandqueue.push({type:"modify",cid:"ezminek",id:id,attributes:attributes});
            }
        };
        this.setRegistry = function(id,name,value){
            var data,
                registry,
                command;

            data = storage.get(id);
            if(data){
                registry = commonUtil.copy(data.registry);
                registry[name] = value;
                commandqueue.push({type:"modify",cid:"ezminek",id:id,registry:registry});
            }
        };
        this.copyNodes = function(ids){
            clipboard = ids;
            commandqueue.push({type:"copy",ids:clipboard,cid:"tenyleg kell??"});
        };
        this.pasteNodes = function(id){
            commandqueue.push({type:"paste",id:id,cid:"commandid"});
        };
        this.deleteNode = function(id){
            commandqueue.push({type:"delete",id:id,cid:"tuti nem kell"});
        };
        this.delMoreNodes = function(ids){
            var i;
            for(i=0;i<ids.length;i++){
                self.deleteNode(ids[i]);
            }
        };
        this.createChild = function(parent,base){
            commandqueue.push({type:"createChild",baseId:base,parentId:parent,cid:"mondom nem kell"});
        };
        this.createSubType = function(parent,base){
            commandqueue.push({type:"createSubType",baseId:base,parentId:parent,cid:"mondom nem kell"});
        };
        this.makePointer = function(id,name,to){
            commandqueue.push({type:"point",id:id,to:to,name:name});
        };
        this.delPointer = function(id,name){
            commandqueue.push({type:"point",id:id,to:null,name:name});
        };
        this.makeConnection = function(parameters){
            var baseId,
                guid;
            if(parameters.parentId && parameters.sourceId && parameters.targetId){
                baseId = parameters.baseId || "connection";
                guid = commonUtil.guid();
                commandqueue.push({type:"createChild",baseId:baseId,parentId:parameters.parentId,newguid:guid,cid:"connnection 001"});
                commandqueue.push({type:"point",id:guid,name:"source",to:parameters.sourceId,cid:"connection 002"});
                commandqueue.push({type:"point",id:guid,name:"target",to:parameters.targetId,cid:"connection 003"});
                if(parameters.directed !== null && parameters.directed !== undefined){
                    self.setAttributes(guid,"directed",parameters.directed);
                }
            }
            else{
                logger.error("fraudulent connection creation: "+JSON.stringify(parameters));
            }
        };

        /*UserInterface handling*/
        this.addUI = function(ui){
            var guid;
            guid = commonUtil.guid();
            users[guid] = new User(self,ui,storage);
            return guid;
        };
        this.removeUI = function(guid){
            delete users[guid];
        };
        this.updateTerritory = function(guid,patterns){
            if(users[guid]){
                users[guid].updatePatterns(patterns);
                commandqueue.push({type:"territory",cid:"nemkollll",id:guid,patterns:users[guid].getPatterns()});
            }
        };

        /*getting a node*/
        this.getNode = function(id){
            if(storage.get(id)){
                return new ClientNode(self,id,storage);
            }
            else{
                return storage.get(id);
            }
        };

        /*socket like functions*/
        this.sendMessage = function(msg){
            socket.emit('clientMessage',msg);
        };
        /*private functions*/
        shootEvent = function(type,id){
            var i,
                data;
            data = storage.get(id);
            for(i in users){
                users[i].shootEvent(id,type);
            }

            if(data && type === "modify"){
                if(data.relations.inheritorIds){
                    for(i=0;i<data.relations.inheritorIds.length;i++){
                        shootEvent(type,data.relations.inheritorIds[i]);
                    }
                }
            }
        };
        handleMessage = function(msg){
            var i;

            /*main*/
            /*if the transaction failed, there is nothing to do*/
            for(i=0;i<msg.length;i++){
                if(msg[i].type === "command"){
                    commandqueue.commandResult(msg[i].cid,msg[i].success);
                    if(msg[i].success === "false"){
                        return;
                    }
                }
            }

            /*first we shoot the unload events*/
            for(i=0;i<msg.length;i++){
                if(msg[i].type === "unload"){
                    shootEvent("unload",msg[i].id);
                }
            }
            /*then we update the storage*/
            for(i=0;i<msg.length;i++){
                if(msg[i].type !== "command"){
                    storage.set(msg[i].id,msg[i].object);
                }
            }
            /*then we update the users*/
            for(i in users){
                users[i].updateTerritory();
            }
            /*then we shoot all other events*/
            for(i=0;i<msg.length;i++){
                if(msg[i].type === "load" || msg[i].type === "update" || msg[i].type === "create" || msg[i].type === "delete"){
                    shootEvent(msg[i].type,msg[i].id);
                }
            }
        };
    };
    User = function(client,ui,storage){
        var patterns = {},
            ids=[];

        /*public functions*/
        this.getPatterns = function(){
            return patterns;
        };
        this.updatePatterns = function(newpatterns){
            patterns = newpatterns;
        };
        this.shootEvent = function(id,type){
            if(ids.indexOf(id)!== -1){
                ui.onEvent(type,id);
            }
        };
        this.updateTerritory = function(){
            var i,
                updatePattern,
                updateRule;

            updateRule = function(id,rulename,rulevalue,itemssofar){
                var i,
                    data = storage.get(id);

                if(data === null || data === undefined){
                    return;
                }

                if(commonUtil.insertIntoArray(itemssofar,id)){
                    if(rulename === "pointer"){
                        if(rulevalue.value === 0){
                            return;
                        }
                        if(data.pointers[rulevalue.name]){
                            if(data.pointers[rulevalue.name].to){
                                updateRule(data.pointers[rulevalue.name].to,rulename,{name:rulevalue.name,value:rulevalue.value-1},itemssofar);
                            }
                        }
                        return;
                    }
                    else{
                        if(rulevalue === 0){
                            return;
                        }
                        switch(rulename){
                            case "parent":
                                if(data.relations.parentId){
                                    updateRule(data.relations.parentId,rulename,rulevalue-1,itemssofar);
                                }
                                break;
                            case "children":
                                if(data.relations.childrenIds){
                                    for(i=0;i<data.relations.childrenIds.length;i++){
                                        updateRule(data.relations.childrenIds[i],rulename,rulevalue-1,itemssofar);
                                    }
                                }
                                break;
                            case "inheritor":
                                if(data.relations.inheritorIds){
                                    for(i=0;i<data.relations.inheritorIds.length;i++){
                                        updateRule(data.relations.inheritorIds[i],rulename,rulevalue-1,itemssofar);
                                    }
                                }
                                break;
                        }
                    }
                }
                else{
                    return; /*we found a loop*/
                }
            };
            updatePattern = function(id,pattern){
                var i,
                    rulechain;
                for(i in pattern){
                    rulechain = [];
                    updateRule(id,i,commonUtil.copy(pattern[i]),rulechain);
                    ids = commonUtil.mergeArrays(ids,rulechain);
                }
            };
            /*main*/
            ids = [];
            for(i in patterns){
                updatePattern(i,patterns[i]);
            }
        };
    };
    CommandQueue = function(client,storage){
        var queue = {},
            sent = {},
            timer = 10000;
            cansend = true;

        /*public functions*/
        this.push = function(command){
            if(command.type === "territory"){
                /*these can go paralelly without cid*/
                client.sendMessage({commands:[command]});
                return;
            }
            queue[command.cid] = command;
            setTimeout(function(){
                commandTimeout(command.cid);
            },timer);
            sendNextClientMessage();
        };
        this.commandResult = function(cid,success){
            /*TODO success*/
            delete sent[cid];

            if(isSentEmpty()){
                cansend = true;
                sendNextClientMessage();
            }
        };
        var isSentEmpty = function(){
            var counter = 0;
            for(var i in sent){
                counter++;
            }
            if(counter === 0){
                return true;
            }
            return false;
        };
        var sendNextClientMessage = function(){
            logger.debug("trying to send next command message to server (cansend="+cansend+",sentisempty="+isSentEmpty());
            if(cansend ===true || isSentEmpty()){
                cansend = true;
                var msg = {commands:[]};
                for(var i in queue){
                    msg.commands.push(queue[i]);
                }
                logger.debug("next command message to server "+JSON.stringify(msg));
                if(msg.commands.length>0){
                    client.sendMessage(msg);
                    cansend = false;
                    sent = queue;
                    queue = {};
                }
            }
        };
        var commandTimeout = function(cid){
            if(queue[cid] || sent[cid]){
                console.log("command timeout "+cid);
            }
            delete queue[cid];
            delete sent[cid];

            if(isSentEmpty()){
                cansend = true;
                sendNextClientMessage();
            }
        };
    };
    Storage = function(client){
        var objects = {};

        /*public functions*/
        this.get = function(id){
            logger.debug("Storage.get "+id);
            return objects[id];
        };
        this.set = function(id,object){
            logger.debug("Storage.set "+id+","+JSON.stringify(object));
            objects[id] = object;
        };
    };
    ClientNode = function(client,id,storage){
        var selfdata = storage.get(id),
            rGetAttribute,
            rGetRegistry;
        /*public funcitons*/
        this.isDeleted = function(){
            return selfdata === null;
        };
        this.getParentId = function(){
            if(selfdata){
                return selfdata.relations.parentId;
            }
            else{
                return selfdata;
            }
        };
        this.getId = function(){
            if(selfdata){
                return selfdata._id;
            }
            else{
                return selfdata;
            }
        };
        this.getChildrenIds = function(){
            if(selfdata){
                return selfdata.relations.childrenIds;
            }
            else{
                return selfdata;
            }
        };
        this.getBaseId = function(){
            if(selfdata){
                return selfdata.relations.baseId;
            }
            else{
                return selfdata;
            }
        };
        this.getInheritorIds = function(){
            if(selfdata){
                return selfdata.relations.inheritorIds;
            }
            else{
                return selfdata
            }
        };
        this.getAttribute = function(name){
            if(selfdata){
                return rGetAttribute(id,name);
            }
            else{
                return selfdata;
            }
        };
        this.getRegistry = function(name){
            if(selfdata){
                return rGetRegistry(id,name);
            }
            else{
                return selfdata;
            }
        };

        /*private functions*/
        rGetAttribute = function(id,name){
            var data;
            data = storage.get(id);
            if(data){
                if(data.attributes[name]){
                    return data.attributes[name];
                }
                else{
                    if(data.relations.baseId){
                        return rGetAttribute(data.relations.baseId,name);
                    }
                    else{
                        return null;
                    }
                }
            }
            else{
                return data;
            }
        };
        rGetRegistry = function(id,name){
            var data;
            data = storage.get(id);
            if(data){
                if(data.registry[name]){
                    return data.registry[name];
                }
                else{
                    if(data.relations.baseId){
                        return rGetRegistry(data.relations.baseId,name);
                    }
                    else{
                        return null;
                    }
                }
            }
            else{
                return data;
            }

        };
    };
    return Client;
});
<|endoftext|>