/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

RED.view.tools = (function() {

    function selectConnected(type) {
        var selection = RED.view.selection();
        var visited = new Set();
        if (selection.nodes && selection.nodes.length > 0) {
            selection.nodes.forEach(function(n) {
                if (!visited.has(n)) {
                    var connected;
                    if (type === 'all') {
                        connected = RED.nodes.getAllFlowNodes(n);
                    } else if (type === 'up') {
                        connected = [n].concat(RED.nodes.getAllUpstreamNodes(n));
                    } else if (type === 'down') {
                        connected = [n].concat(RED.nodes.getAllDownstreamNodes(n));
                    }
                    connected.forEach(function(nn) { visited.add(nn) })
                }
            });
            RED.view.select({nodes:Array.from(visited)});
        }

    }

    function alignToGrid() {
        var selection = RED.view.selection();
        if (selection.nodes) {
            var changedNodes = [];
            selection.nodes.forEach(function(n) {
                var x = n.w/2 + Math.round((n.x-n.w/2)/RED.view.gridSize())*RED.view.gridSize();
                var y = Math.round(n.y/RED.view.gridSize())*RED.view.gridSize();
                if (n.x !== x || n.y !== y) {
                    changedNodes.push({
                        n:n,
                        ox: n.x,
                        oy: n.y,
                        moved: n.moved
                    });
                    n.x = x;
                    n.y = y;
                    n.dirty = true;
                    n.moved = true;
                }
            });
            if (changedNodes.length > 0) {
                RED.history.push({t:"move",nodes:changedNodes,dirty:RED.nodes.dirty()});
                RED.nodes.dirty(true);
                RED.view.redraw(true);
            }
        }
    }

    var moving_set = null;
    var endMoveSet = false;
    function endKeyboardMove() {
        endMoveSet = false;
        if (moving_set.length > 0) {
            var ns = [];
            for (var i=0;i<moving_set.length;i++) {
                ns.push({n:moving_set[i].n,ox:moving_set[i].ox,oy:moving_set[i].oy,moved:moving_set[i].moved});
                moving_set[i].n.moved = true;
                moving_set[i].n.dirty = true;
                delete moving_set[i].ox;
                delete moving_set[i].oy;
            }
            RED.view.redraw();
            RED.history.push({t:"move",nodes:ns,dirty:RED.nodes.dirty()});
            RED.nodes.dirty(true);
            moving_set = null;
        }
    }

    function moveSelection(dx,dy) {
        if (moving_set === null) {
            moving_set = [];
            var selection = RED.view.selection();
            if (selection.nodes) {
                while (selection.nodes.length > 0) {
                    var n = selection.nodes.shift();
                    moving_set.push({n:n});
                    if (n.type === "group") {
                        selection.nodes = selection.nodes.concat(n.nodes);
                    }
                }
            }
        }
        if (moving_set && moving_set.length > 0) {
            if (!endMoveSet) {
                $(document).one('keyup',endKeyboardMove);
                endMoveSet = true;
            }
            var minX = 0;
            var minY = 0;
            var node;

            for (var i=0;i<moving_set.length;i++) {
                node = moving_set[i];
                if (node.ox == null && node.oy == null) {
                    node.ox = node.n.x;
                    node.oy = node.n.y;
                    node.moved = node.n.moved;
                }
                node.n.moved = true;
                node.n.dirty = true;
                node.n.x += dx;
                node.n.y += dy;
                node.n.dirty = true;
                if (node.n.type === "group") {
                    RED.group.markDirty(node.n);
                    minX = Math.min(node.n.x - 5,minX);
                    minY = Math.min(node.n.y - 5,minY);
                } else {
                    minX = Math.min(node.n.x-node.n.w/2-5,minX);
                    minY = Math.min(node.n.y-node.n.h/2-5,minY);
                }
            }
            if (minX !== 0 || minY !== 0) {
                for (var n = 0; n<moving_set.length; n++) {
                    node = moving_set[n];
                    node.n.x -= minX;
                    node.n.y -= minY;
                }
            }
            RED.view.redraw();
        } else {
            RED.view.scroll(dx*10,dy*10);
        }
    }

    function setSelectedNodeLabelState(labelShown) {
        var selection = RED.view.selection();
        var historyEvents = [];
        var nodes = [];
        if (selection.nodes) {
            selection.nodes.forEach(function(n) {
                if (n.type !== 'subflow' && n.type !== 'group') {
                    nodes.push(n);
                } else if (n.type === 'group') {
                    nodes = nodes.concat( RED.group.getNodes(n,true));
                }
            });
        }
        nodes.forEach(function(n) {
            var modified = false;
            var oldValue = n.l === undefined?true:n.l;
            var showLabel = n._def.hasOwnProperty("showLabel")?n._def.showLabel:true;

            if (labelShown) {
                if (n.l === false || (!showLabel && !n.hasOwnProperty('l'))) {
                    n.l = true;
                    modified = true;
                }
            } else {
                if ((showLabel && (!n.hasOwnProperty('l') || n.l === true)) || (!showLabel && n.l === true) ) {
                    n.l = false;
                    modified = true;
                }
            }
            if (modified) {
                historyEvents.push({
                    t: "edit",
                    node: n,
                    changed: n.changed,
                    changes: {
                        l: oldValue
                    }
                })
                n.changed = true;
                n.dirty = true;
                n.resize = true;
            }
        })

        if (historyEvents.length > 0) {
            RED.history.push({
                t: "multi",
                events: historyEvents,
                dirty: RED.nodes.dirty()
            })
            RED.nodes.dirty(true);
        }

        RED.view.redraw();


    }

    function selectFirstNode() {
        var canidates;
        var origin = {x:0, y:0};

        var activeGroup = RED.view.getActiveGroup();

        if (!activeGroup) {
            candidates = RED.view.getActiveNodes();
        } else {
            candidates = RED.group.getNodes(activeGroup,false);
            origin = activeGroup;
        }

        var distances = [];
        candidates.forEach(function(node) {
            var deltaX = node.x - origin.x;
            var deltaY = node.y - origin.x;
            var delta = deltaY*deltaY + deltaX*deltaX;
            distances.push({node: node, delta: delta})
        });
        if (distances.length > 0) {
            distances.sort(function(A,B) {
                return A.delta - B.delta
            })
            var newNode = distances[0].node;
            if (newNode) {
                RED.view.select({nodes:[newNode]});
                RED.view.reveal(newNode.id,false);
            }
        }
    }

    function gotoNextNode() {
        var selection = RED.view.selection();
        if (selection.nodes && selection.nodes.length === 1) {
            var origin = selection.nodes[0];
            var links = RED.nodes.filterLinks({source:origin});
            if (links.length > 0) {
                links.sort(function(A,B) {
                    return Math.abs(A.target.y - origin.y) - Math.abs(B.target.y - origin.y)
                })
                var newNode = links[0].target;
                if (newNode) {
                    RED.view.select({nodes:[newNode]});
                    RED.view.reveal(newNode.id,false);
                }
            }
        } else if (RED.workspaces.selection().length === 0) {
            selectFirstNode();
        }
    }
    function gotoPreviousNode() {
        var selection = RED.view.selection();
        if (selection.nodes && selection.nodes.length === 1) {
            var origin = selection.nodes[0];
            var links = RED.nodes.filterLinks({target:origin});
            if (links.length > 0) {
                links.sort(function(A,B) {
                    return Math.abs(A.source.y - origin.y) - Math.abs(B.source.y - origin.y)
                })
                var newNode = links[0].source;
                if (newNode) {
                    RED.view.select({nodes:[newNode]});
                    RED.view.reveal(newNode.id,false);
                }
            }
        } else if (RED.workspaces.selection().length === 0) {
            selectFirstNode();
        }
    }

    function getChildren(node) {
        return RED.nodes.filterLinks({source:node}).map(function(l) { return l.target})
    }
    function getParents(node) {
        return RED.nodes.filterLinks({target:node}).map(function(l) { return l.source})
    }

    function getSiblings(node) {
        var siblings = new Set();
        var parents = getParents(node);
        parents.forEach(function(p) {
            getChildren(p).forEach(function(c) { siblings.add(c) })
        });
        var children = getChildren(node);
        children.forEach(function(p) {
            getParents(p).forEach(function(c) { siblings.add(c) })
        });
        siblings.delete(node);
        return Array.from(siblings);
    }
    function gotoNextSibling() {
        // 'next' defined as nearest on the y-axis below this node
        var selection = RED.view.selection();
        if (selection.nodes && selection.nodes.length === 1) {
            var origin = selection.nodes[0];
            var siblings = getSiblings(origin);
            if (siblings.length > 0) {
                siblings = siblings.filter(function(n) { return n.y > origin. y})
                siblings.sort(function(A,B) {
                    return Math.abs(A.y - origin.y) - Math.abs(B.y - origin.y)
                })
                var newNode = siblings[0];
                if (newNode) {
                    RED.view.select({nodes:[newNode]});
                    RED.view.reveal(newNode.id,false);
                }
            }
        } else if (RED.workspaces.selection().length === 0) {
            selectFirstNode();
        }
    }
    function gotoPreviousSibling() {
        // 'next' defined as nearest on the y-axis above this node
        var selection = RED.view.selection();
        if (selection.nodes && selection.nodes.length === 1) {
            var origin = selection.nodes[0];
            var siblings = getSiblings(origin);
            if (siblings.length > 0) {
                siblings = siblings.filter(function(n) { return n.y < origin. y})
                siblings.sort(function(A,B) {
                    return Math.abs(A.y - origin.y) - Math.abs(B.y - origin.y)
                })
                var newNode = siblings[0];
                if (newNode) {
                    RED.view.select({nodes:[newNode]});
                    RED.view.reveal(newNode.id,false);
                }
            }
        } else if (RED.workspaces.selection().length === 0) {
            selectFirstNode();
        }

    }

    function addNode() {
        var selection = RED.view.selection();
        if (selection.nodes && selection.nodes.length === 1 && selection.nodes[0].outputs > 0) {
            var selectedNode = selection.nodes[0];
            RED.view.showQuickAddDialog([
                selectedNode.x + selectedNode.w + 50,selectedNode.y
            ])
        } else {
            RED.view.showQuickAddDialog();
        }
    }


    function gotoNearestNode(direction) {
        var selection = RED.view.selection();
        if (selection.nodes && selection.nodes.length === 1) {
            var origin = selection.nodes[0];

            var candidates = RED.nodes.filterNodes({z:origin.z});
            candidates = candidates.concat(RED.view.getSubflowPorts());
            var distances = [];
            candidates.forEach(function(node) {
                if (node === origin) {
                    return;
                }
                var deltaX = node.x - origin.x;
                var deltaY = node.y - origin.y;
                var delta = deltaY*deltaY + deltaX*deltaX;
                var angle = (180/Math.PI)*Math.atan2(deltaY,deltaX);
                if (angle < 0) { angle += 360 }
                if (angle > 360) { angle -= 360 }

                var weight;

                // 0 - right
                // 270 - above
                // 90 - below
                // 180 - left
                switch(direction) {
                    case 'up': if (angle < 210 || angle > 330) { return }
                        weight = Math.max(Math.abs(270 - angle)/60, 0.2);
                        break;
                    case 'down': if (angle < 30 || angle > 150) { return }
                        weight = Math.max(Math.abs(90 - angle)/60, 0.2);
                        break;
                    case 'left': if (angle < 140 || angle > 220) { return }
                        weight = Math.max(Math.abs(180 - angle)/40, 0.1 );
                        break;
                    case 'right': if (angle > 40 && angle < 320) { return }
                        weight = Math.max(Math.abs(angle)/40, 0.1);
                        break;
                }
                weight = Math.max(weight,0.1);
                distances.push({
                    node: node,
                    d: delta,
                    w: weight,
                    delta: delta*weight
                })
            })
            if (distances.length > 0) {
                distances.sort(function(A,B) {
                    return A.delta - B.delta
                })
                var newNode = distances[0].node;
                if (newNode) {
                    RED.view.select({nodes:[newNode]});
                    RED.view.reveal(newNode.id,false);
                }
            }
        } else if (RED.workspaces.selection().length === 0) {
            var candidates = RED.view.getActiveNodes();

            var distances = [];
            candidates.forEach(function(node) {
                var deltaX = node.x;
                var deltaY = node.y;
                var delta = deltaY*deltaY + deltaX*deltaX;
                distances.push({node: node, delta: delta})
            });
            if (distances.length > 0) {
                distances.sort(function(A,B) {
                    return A.delta - B.delta
                })
                var newNode = distances[0].node;
                if (newNode) {
                    RED.view.select({nodes:[newNode]});
                    RED.view.reveal(newNode.id,false);
                }
            }
        }
    }

    function alignSelectionToEdge(direction) {
        var selection = RED.view.selection();

        if (selection.nodes && selection.nodes.length > 1) {
            var changedNodes = [];
            var bounds = {
                minX: Number.MAX_SAFE_INTEGER,
                minY: Number.MAX_SAFE_INTEGER,
                maxX: Number.MIN_SAFE_INTEGER,
                maxY: Number.MIN_SAFE_INTEGER
            }
            selection.nodes.forEach(function(n) {
                if (n.type === "group") {
                    bounds.minX = Math.min(bounds.minX, n.x);
                    bounds.minY = Math.min(bounds.minY, n.y);
                    bounds.maxX = Math.max(bounds.maxX, n.x + n.w);
                    bounds.maxY = Math.max(bounds.maxY, n.y + n.h);
                } else {
                    bounds.minX = Math.min(bounds.minX, n.x - n.w/2);
                    bounds.minY = Math.min(bounds.minY, n.y - n.h/2);
                    bounds.maxX = Math.max(bounds.maxX, n.x + n.w/2);
                    bounds.maxY = Math.max(bounds.maxY, n.y + n.h/2);
                }
            });

            bounds.midX = bounds.minX + (bounds.maxX - bounds.minX)/2;
            bounds.midY = bounds.minY + (bounds.maxY - bounds.minY)/2;

            selection.nodes.forEach(function(n) {
                var targetX;
                var targetY;
                var isGroup = n.type==="group";
                switch(direction) {
                    case 'top':
                        targetX = n.x;
                        targetY = bounds.minY + (isGroup?0:(n.h/2));
                        break;
                    case 'bottom':
                        targetX = n.x;
                        targetY = bounds.maxY - (isGroup?n.h:(n.h/2));
                        break;
                    case 'left':
                        targetX = bounds.minX + (isGroup?0:(n.w/2));
                        targetY = n.y;
                        break;
                    case 'right':
                        targetX = bounds.maxX - (isGroup?n.w:(n.w/2));
                        targetY = n.y;
                        break;
                    case 'middle':
                        targetX = n.x;
                        targetY = bounds.midY - (isGroup?n.h/2:0)
                        break;
                    case 'center':
                        targetX = bounds.midX - (isGroup?n.w/2:0)
                        targetY = n.y;
                        break;
                }

                if (n.x !== targetX || n.y !== targetY) {
                    if (!isGroup) {
                        changedNodes.push({
                            n:n,
                            ox: n.x,
                            oy: n.y,
                            moved: n.moved
                        });
                        n.x = targetX;
                        n.y = targetY;
                        n.dirty = true;
                        n.moved = true;
                    } else {
                        var groupNodes = RED.group.getNodes(n, true);
                        var deltaX = n.x - targetX;
                        var deltaY = n.y - targetY;
                        groupNodes.forEach(function(gn) {
                            if (gn.type !== "group" ) {
                                changedNodes.push({
                                    n:gn,
                                    ox: gn.x,
                                    oy: gn.y,
                                    moved: gn.moved
                                });
                                gn.x = gn.x - deltaX;
                                gn.y = gn.y - deltaY;
                                gn.dirty = true;
                                gn.moved = true;
                            }
                        })

                    }
                }
            });
            if (changedNodes.length > 0) {
                RED.history.push({t:"move",nodes:changedNodes,dirty:RED.nodes.dirty()});
                RED.nodes.dirty(true);
                RED.view.redraw(true);
            }
        }
    }


    function distributeSelection(direction) {
        var selection = RED.view.selection();

        if (selection.nodes && selection.nodes.length > 2) {
            var changedNodes = [];
            var bounds = {
                minX: Number.MAX_SAFE_INTEGER,
                minY: Number.MAX_SAFE_INTEGER,
                maxX: Number.MIN_SAFE_INTEGER,
                maxY: Number.MIN_SAFE_INTEGER
            }
            var startAnchors = [];
            var endAnchors = [];

            selection.nodes.forEach(function(n) {
                var nx,ny;
                if (n.type === "group") {
                    nx = n.x + n.w/2;
                    ny = n.y + n.h/2;
                } else {
                    nx = n.x;
                    ny = n.y;
                }
                if (direction === "h") {
                    if (nx < bounds.minX) {
                        startAnchors = [];
                        bounds.minX = nx;
                    }
                    if (nx === bounds.minX) {
                        startAnchors.push(n);
                    }
                    if (nx > bounds.maxX) {
                        endAnchors = [];
                        bounds.maxX = nx;
                    }
                    if (nx === bounds.maxX) {
                        endAnchors.push(n);
                    }
                } else {
                    if (ny < bounds.minY) {
                        startAnchors = [];
                        bounds.minY = ny;
                    }
                    if (ny === bounds.minY) {
                        startAnchors.push(n);
                    }
                    if (ny > bounds.maxY) {
                        endAnchors = [];
                        bounds.maxY = ny;
                    }
                    if (ny === bounds.maxY) {
                        endAnchors.push(n);
                    }
                }
            });

            var startAnchor = startAnchors[0];
            var endAnchor = endAnchors[0];

            var nodeSpace = 0;
            var nodesToMove = selection.nodes.filter(function(n) {
                if (n.id !== startAnchor.id && n.id !== endAnchor.id) {
                    nodeSpace += direction === 'h'?n.w:n.h;
                    return true;
                }
                return false;
            }).sort(function(A,B) {
                if (direction === 'h') {
                    return A.x - B.x
                } else {
                    return A.y - B.y
                }
            })

            var saX = startAnchor.x + startAnchor.w/2;
            var saY = startAnchor.y + startAnchor.h/2;
            if (startAnchor.type === "group") {
                saX = startAnchor.x + startAnchor.w;
                saY = startAnchor.y + startAnchor.h;
            }
            var eaX = endAnchor.x;
            var eaY = endAnchor.y;
            if (endAnchor.type !== "group") {
                eaX -= endAnchor.w/2;
                eaY -= endAnchor.h/2;
            }
            var spaceToFill = direction === 'h'?(eaX - saX - nodeSpace): (eaY - saY - nodeSpace);
            var spaceBetweenNodes = spaceToFill / (nodesToMove.length + 1);

            var tx = saX;
            var ty = saY;
            while(nodesToMove.length > 0) {
                if (direction === 'h') {
                    tx += spaceBetweenNodes;
                } else {
                    ty += spaceBetweenNodes;
                }
                var nextNode = nodesToMove.shift();
                var isGroup = nextNode.type==="group";

                var nx = nextNode.x;
                var ny = nextNode.y;
                if (!isGroup) {
                    tx += nextNode.w/2;
                    ty += nextNode.h/2;
                }
                if ((direction === 'h' && nx !== tx) || (direction === 'v' && ny !== ty)) {
                    if (!isGroup) {
                        changedNodes.push({
                            n:nextNode,
                            ox: nextNode.x,
                            oy: nextNode.y,
                            moved: nextNode.moved
                        });
                        if (direction === 'h') {
                            nextNode.x = tx;
                        } else {
                            nextNode.y = ty;
                        }
                        nextNode.dirty = true;
                        nextNode.moved = true;
                    } else {
                        var groupNodes = RED.group.getNodes(nextNode, true);
                        var deltaX = direction === 'h'? nx - tx : 0;
                        var deltaY = direction === 'v'? ny - ty : 0;
                        groupNodes.forEach(function(gn) {
                            if (gn.type !== "group" ) {
                                changedNodes.push({
                                    n:gn,
                                    ox: gn.x,
                                    oy: gn.y,
                                    moved: gn.moved
                                });
                                gn.x = gn.x - deltaX;
                                gn.y = gn.y - deltaY;
                                gn.dirty = true;
                                gn.moved = true;
                            }
                        })
                    }
                }
                if (isGroup) {
                    tx += nextNode.w;
                    ty += nextNode.h;
                } else {
                    tx += nextNode.w/2;
                    ty += nextNode.h/2;
                }
            }

            if (changedNodes.length > 0) {
                RED.history.push({t:"move",nodes:changedNodes,dirty:RED.nodes.dirty()});
                RED.nodes.dirty(true);
                RED.view.redraw(true);
            }
        }
    }

    function reorderSelection(dir) {
        var selection = RED.view.selection();
        if (selection.nodes) {
            var nodesToMove = [];
            selection.nodes.forEach(function(n) {
                if (n.type === "group") {
                    nodesToMove = nodesToMove.concat(RED.group.getNodes(n, true).filter(function(n) {
                        return n.type !== "group";
                    }))
                } else if (n.type !== "subflow"){
                    nodesToMove.push(n);
                }
            })
            if (nodesToMove.length > 0) {
                var z = nodesToMove[0].z;
                var existingOrder = RED.nodes.getNodeOrder(z);
                var movedNodes;
                if (dir === "forwards") {
                    movedNodes = RED.nodes.moveNodesForwards(nodesToMove);
                } else if (dir === "backwards") {
                    movedNodes = RED.nodes.moveNodesBackwards(nodesToMove);
                } else if (dir === "front") {
                    movedNodes = RED.nodes.moveNodesToFront(nodesToMove);
                } else if (dir === "back") {
                    movedNodes = RED.nodes.moveNodesToBack(nodesToMove);
                }
                if (movedNodes.length > 0) {
                    var newOrder = RED.nodes.getNodeOrder(z);
                    RED.history.push({t:"reorder",nodes:{z:z,from:existingOrder,to:newOrder},dirty:RED.nodes.dirty()});
                    RED.nodes.dirty(true);
                    RED.view.redraw(true);
                }
            }
        }
    }


    function wireSeriesOfNodes() {
        var selection = RED.view.selection();
        if (selection.nodes) {
            if (selection.nodes.length > 1) {
                var i = 0;
                var newLinks = [];
                while (i < selection.nodes.length - 1) {
                    var nodeA = selection.nodes[i];
                    var nodeB = selection.nodes[i+1];
                    if (nodeA.outputs > 0 && nodeB.inputs > 0) {
                        var existingLinks = RED.nodes.filterLinks({
                            source: nodeA,
                            target: nodeB,
                            sourcePort: 0
                        })
                        if (existingLinks.length === 0) {
                            var newLink = {
                                source: nodeA,
                                target: nodeB,
                                sourcePort: 0
                            }
                            RED.nodes.addLink(newLink);
                            newLinks.push(newLink);
                        }
                    }
                    i++;
                }
                if (newLinks.length > 0) {
                    RED.history.push({
                        t: 'add',
                        links: newLinks,
                        dirty: RED.nodes.dirty()
                    })
                    RED.nodes.dirty(true);
                    RED.view.redraw(true);
                }
            }
        }
    }

    function wireNodeToMultiple() {
        var selection = RED.view.selection();
        if (selection.nodes) {
            if (selection.nodes.length > 1) {
                var sourceNode = selection.nodes[0];
                if (sourceNode.outputs === 0) {
                    return;
                }
                var i = 1;
                var newLinks = [];
                while (i < selection.nodes.length) {
                    var targetNode = selection.nodes[i];
                    if (targetNode.inputs > 0) {
                        var existingLinks = RED.nodes.filterLinks({
                            source: sourceNode,
                            target: targetNode,
                            sourcePort: Math.min(sourceNode.outputs-1,i-1)
                        })
                        if (existingLinks.length === 0) {
                            var newLink = {
                                source: sourceNode,
                                target: targetNode,
                                sourcePort: Math.min(sourceNode.outputs-1,i-1)
                            }
                            RED.nodes.addLink(newLink);
                            newLinks.push(newLink);
                        }
                    }
                    i++;
                }
                if (newLinks.length > 0) {
                    RED.history.push({
                        t: 'add',
                        links: newLinks,
                        dirty: RED.nodes.dirty()
                    })
                    RED.nodes.dirty(true);
                    RED.view.redraw(true);
                }
            }
        }
    }

    return {
        init: function() {
            RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); })
            RED.actions.add("core:hide-selected-node-labels", function() { setSelectedNodeLabelState(false); })

            RED.actions.add("core:scroll-view-up", function() { RED.view.scroll(0,-RED.view.gridSize());});
            RED.actions.add("core:scroll-view-right", function() { RED.view.scroll(RED.view.gridSize(),0);});
            RED.actions.add("core:scroll-view-down", function() { RED.view.scroll(0,RED.view.gridSize());});
            RED.actions.add("core:scroll-view-left", function() { RED.view.scroll(-RED.view.gridSize(),0);});

            RED.actions.add("core:step-view-up", function() { RED.view.scroll(0,-5*RED.view.gridSize());});
            RED.actions.add("core:step-view-right", function() { RED.view.scroll(5*RED.view.gridSize(),0);});
            RED.actions.add("core:step-view-down", function() { RED.view.scroll(0,5*RED.view.gridSize());});
            RED.actions.add("core:step-view-left", function() { RED.view.scroll(-5*RED.view.gridSize(),0);});

            RED.actions.add("core:move-selection-up", function() { moveSelection(0,-1);});
            RED.actions.add("core:move-selection-right", function() { moveSelection(1,0);});
            RED.actions.add("core:move-selection-down", function() { moveSelection(0,1);});
            RED.actions.add("core:move-selection-left", function() { moveSelection(-1,0);});

            RED.actions.add("core:move-selection-forwards", function() { reorderSelection('forwards') })
            RED.actions.add("core:move-selection-backwards", function() { reorderSelection('backwards') })
            RED.actions.add("core:move-selection-to-front", function() { reorderSelection('front') })
            RED.actions.add("core:move-selection-to-back", function() { reorderSelection('back') })


            RED.actions.add("core:step-selection-up", function() { moveSelection(0,-RED.view.gridSize());});
            RED.actions.add("core:step-selection-right", function() { moveSelection(RED.view.gridSize(),0);});
            RED.actions.add("core:step-selection-down", function() { moveSelection(0,RED.view.gridSize());});
            RED.actions.add("core:step-selection-left", function() { moveSelection(-RED.view.gridSize(),0);});

            RED.actions.add("core:select-connected-nodes", function() { selectConnected("all") });
            RED.actions.add("core:select-downstream-nodes", function() { selectConnected("down") });
            RED.actions.add("core:select-upstream-nodes", function() { selectConnected("up") });


            RED.actions.add("core:go-to-next-node", function() { gotoNextNode() })
            RED.actions.add("core:go-to-previous-node", function() { gotoPreviousNode() })
            RED.actions.add("core:go-to-next-sibling", function() { gotoNextSibling() })
            RED.actions.add("core:go-to-previous-sibling", function() { gotoPreviousSibling() })


            RED.actions.add("core:go-to-nearest-node-on-left", function() { gotoNearestNode('left')})
            RED.actions.add("core:go-to-nearest-node-on-right", function() { gotoNearestNode('right')})
            RED.actions.add("core:go-to-nearest-node-above", function() { gotoNearestNode('up') })
            RED.actions.add("core:go-to-nearest-node-below", function() { gotoNearestNode('down') })

            RED.actions.add("core:align-selection-to-grid", alignToGrid);
            RED.actions.add("core:align-selection-to-left", function() { alignSelectionToEdge('left') })
            RED.actions.add("core:align-selection-to-right", function() { alignSelectionToEdge('right') })
            RED.actions.add("core:align-selection-to-top", function() { alignSelectionToEdge('top') })
            RED.actions.add("core:align-selection-to-bottom", function() { alignSelectionToEdge('bottom') })
            RED.actions.add("core:align-selection-to-middle", function() { alignSelectionToEdge('middle') })
            RED.actions.add("core:align-selection-to-center", function() { alignSelectionToEdge('center') })

            RED.actions.add("core:distribute-selection-horizontally", function() { distributeSelection('h') })
            RED.actions.add("core:distribute-selection-vertically", function() { distributeSelection('v') })

            RED.actions.add("core:wire-series-of-nodes", function() { wireSeriesOfNodes() })
            RED.actions.add("core:wire-node-to-multiple", function() { wireNodeToMultiple() })

            // RED.actions.add("core:add-node", function() { addNode() })
        },
        /**
         * Aligns all selected nodes to the current grid
         */
        alignSelectionToGrid: alignToGrid,
        /**
         * Moves all of the selected nodes by the specified amount
         * @param  {Number} dx
         * @param  {Number} dy
         */
        moveSelection: moveSelection
    }

})();
