/**
 * Copyright (c) 2019-2020, JGraph Holdings Ltd
 */
/**
 * Class: mxOrgChartLayout
 * 
 * Extends <mxGraphLayout> to implement organization chart layout algorithm.
 * The vertices need to be connected for this layout to work, vertices
 * with no connections are ignored.
 * 
 * Example:
 * 
 * (code)
 * var layout = new mxOrgChartLayout(graph);
 * layout.execute(graph.getDefaultParent());
 * (end)
 * 
 */
function mxOrgChartLayout(graph, branchOptimizer, parentChildSpacing, siblingSpacing)
{
	mxGraphLayout.call(this, graph);
	this.correctY = false;
	
	switch(parseInt(branchOptimizer))
	{
		case 0:
			this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_LINEAR;
			this.correctY = true;
			break;
		case 1:
			this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_HANGER2;
			this.correctY = true;
			break;
		case 3:
			this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_FISHBONE1;
			break;
		case 4:
			this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_FISHBONE2;
			break;
		case 5:
			this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_1COLUMN_L;
			break;
		case 6:
			this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_1COLUMN_R;
			break;
		case 7:
			this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_SMART;
			break;
		default: //and case 2
			this.branchOptimizer = mxOrgChartLayout.prototype.BRANCH_OPT_HANGER4;
			this.correctY = true;
	}
	
	this.parentChildSpacing = parentChildSpacing > 0 ? parentChildSpacing : 20;
	this.siblingSpacing = siblingSpacing > 0 ? siblingSpacing : 20;
};

/**
 * Extends mxGraphLayout.
 */
mxOrgChartLayout.prototype = new mxGraphLayout();
mxOrgChartLayout.prototype.constructor = mxOrgChartLayout;

//Branch Optimizers
mxOrgChartLayout.prototype.BRANCH_OPT_LINEAR = 'branchOptimizerAllLinear';
mxOrgChartLayout.prototype.BRANCH_OPT_HANGER2 = 'branchOptimizerAllHanger2';
mxOrgChartLayout.prototype.BRANCH_OPT_HANGER4 = 'branchOptimizerAllHanger4';
mxOrgChartLayout.prototype.BRANCH_OPT_FISHBONE1 = 'branchOptimizerAllFishbone1';
mxOrgChartLayout.prototype.BRANCH_OPT_FISHBONE2 = 'branchOptimizerAllFishbone2';
mxOrgChartLayout.prototype.BRANCH_OPT_1COLUMN_L = 'branchOptimizerAllSingleColumnLeft';
mxOrgChartLayout.prototype.BRANCH_OPT_1COLUMN_R = 'branchOptimizerAllSingleColumnRight';
mxOrgChartLayout.prototype.BRANCH_OPT_SMART = 'branchOptimizerSmart';

/**
 * Function: execute
 * 
 * Implements <mxGraphLayout.execute>. This operates on all children of the
 * given parent.
 */
mxOrgChartLayout.prototype.execute = function(parent)
{
	this.graph.model.beginUpdate();
	try
	{
		RPOrgChart.main(this.graph, parent, this.branchOptimizer, this.parentChildSpacing, this.siblingSpacing, this.correctY);		
	}
	finally
	{
		this.graph.model.endUpdate();
	}
}

Bridge.define('RPOrgChart',
{
    statics: {
        config: {
            init: function() {

            }
        },
        main: function (graph, parent, branchOptimizer, parentChildSpacing, siblingSpacing, correctY) {
            Bridge.Console.log = console.log;
            Bridge.Console.error = console.error;
            Bridge.Console.debug = console.debug;

            RPOrgChart.graph = graph;
            RPOrgChart.parent = parent;
			RPOrgChart.dx = 0;
			RPOrgChart.dy = 0;
			
			if (parent.style == 'group' && parent.geometry)
			{
				RPOrgChart.dx = parent.geometry.x;
				RPOrgChart.dy = parent.geometry.y;
			}
			
            RPOrgChart.branchOptimizer = branchOptimizer;
			RPOrgChart.correctY = correctY;
            RPOrgChart.parentChildSpacing = parseInt(parentChildSpacing);
            RPOrgChart.siblingSpacing = parseInt(siblingSpacing);
            RPOrgChart.buildChart(true);
        },

        diagram: {},
        dataSource: {},

        buildChart: function (initData) {
            if (initData) {
                RPOrgChart.initDiagram();
            }
            RPOrgChart.positionBoxes();
        },

        collapseAllBoxes: function(boxContainer, isCollapsed) {
            var en = boxContainer.getBoxesById().getValues().getEnumerator();
            while (en.moveNext()) {
                var box = en.getCurrent();
                if (!box.IsSpecial) {
                    box.IsCollapsed = isCollapsed;
                }
            }
        },

        generateData: function () 
        {
        	var dataSource = new OrgChart.Test.TestDataSource();
        	
            var graph = RPOrgChart.graph;
            var cells = graph.getChildVertices(RPOrgChart.parent);
            
            for (var i = 0; i < cells.length; i++)
        	{
            	var cell = cells[i];
            	
            	if (cell.geometry != null && cell.vertex && cell.parent == RPOrgChart.parent) //Vertices and first level children only
            	{
            		// Find cell parent. If it has more than one parent, take first parent (should be an error?)
            		var parentId = null;
            		
            		var incomingEdge = graph.getIncomingEdges(cell)[0];
            		
            		if (incomingEdge != null && incomingEdge.source != null)
           			{
            			parentId = incomingEdge.source.id;
           			}
            		
            		var item = new OrgChart.Test.TestDataItem();
            		item.Id = cell.id;
            		item.ParentId = parentId;
            		dataSource.Items.add(item.getId(), item);
            	}
           	}
            
            return dataSource;
        },

        initDiagram: function () {
            var dataSource = RPOrgChart.generateData();

            RPOrgChart.dataSource = dataSource;

            var boxContainer = new OrgChart.Layout.BoxContainer.$ctor1(dataSource);
            RPOrgChart.diagram = new OrgChart.Layout.Diagram();

            var diagram = RPOrgChart.diagram;
            diagram.setBoxes(boxContainer);

            var linearLayoutStrategy = new OrgChart.Layout.LinearLayoutStrategy();
            linearLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
            linearLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
            linearLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
            diagram.LayoutSettings.LayoutStrategies.add("linear", linearLayoutStrategy);

            var multiLineHangerLayoutStrategy = new OrgChart.Layout.MultiLineHangerLayoutStrategy();
            multiLineHangerLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
            multiLineHangerLayoutStrategy.MaxSiblingsPerRow = 2;
            multiLineHangerLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
            multiLineHangerLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
            diagram.LayoutSettings.LayoutStrategies.add("hanger2", multiLineHangerLayoutStrategy);

            multiLineHangerLayoutStrategy = new OrgChart.Layout.MultiLineHangerLayoutStrategy();
            multiLineHangerLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
            multiLineHangerLayoutStrategy.MaxSiblingsPerRow = 4;
            multiLineHangerLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
            multiLineHangerLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
            diagram.LayoutSettings.LayoutStrategies.add("hanger4", multiLineHangerLayoutStrategy);

            var singleColumnLayoutStrategy = new OrgChart.Layout.SingleColumnLayoutStrategy();
            singleColumnLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Right;
            singleColumnLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
            singleColumnLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
            diagram.LayoutSettings.LayoutStrategies.add("singleColumnRight", singleColumnLayoutStrategy);

            singleColumnLayoutStrategy = new OrgChart.Layout.SingleColumnLayoutStrategy();
            singleColumnLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Left;
            singleColumnLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
            singleColumnLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
            diagram.LayoutSettings.LayoutStrategies.add("singleColumnLeft", singleColumnLayoutStrategy);

            var fishboneLayoutStrategy = new OrgChart.Layout.MultiLineFishboneLayoutStrategy();
            fishboneLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
            fishboneLayoutStrategy.MaxGroups = 1;
            fishboneLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
            fishboneLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
            diagram.LayoutSettings.LayoutStrategies.add("fishbone1", fishboneLayoutStrategy);

            fishboneLayoutStrategy = new OrgChart.Layout.MultiLineFishboneLayoutStrategy();
            fishboneLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
            fishboneLayoutStrategy.MaxGroups = 2;
            fishboneLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
            fishboneLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
            diagram.LayoutSettings.LayoutStrategies.add("fishbone2", fishboneLayoutStrategy);

            var hstackLayoutStrategy = new OrgChart.Layout.StackingLayoutStrategy();
            hstackLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.InvalidValue;
            hstackLayoutStrategy.Orientation = OrgChart.Layout.StackOrientation.SingleRowHorizontal;
            hstackLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
            hstackLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
            diagram.LayoutSettings.LayoutStrategies.add("hstack", hstackLayoutStrategy);

            var vstackLayoutStrategy = new OrgChart.Layout.StackingLayoutStrategy();
            vstackLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.InvalidValue;
            vstackLayoutStrategy.Orientation = OrgChart.Layout.StackOrientation.SingleColumnVertical;
            vstackLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
            vstackLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
            diagram.LayoutSettings.LayoutStrategies.add("vstack", vstackLayoutStrategy);

            vstackLayoutStrategy = new OrgChart.Layout.StackingLayoutStrategy();
            vstackLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.InvalidValue;
            vstackLayoutStrategy.Orientation = OrgChart.Layout.StackOrientation.SingleColumnVertical;
            vstackLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
            vstackLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
            diagram.LayoutSettings.LayoutStrategies.add("vstackMiddle", vstackLayoutStrategy);

            vstackLayoutStrategy = new OrgChart.Layout.StackingLayoutStrategy();
            vstackLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.InvalidValue;
            vstackLayoutStrategy.Orientation = OrgChart.Layout.StackOrientation.SingleColumnVertical;
            vstackLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
            vstackLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
            diagram.LayoutSettings.LayoutStrategies.add("vstackTop", vstackLayoutStrategy);

            var assistantsLayoutStrategy = new OrgChart.Layout.FishboneAssistantsLayoutStrategy();
            assistantsLayoutStrategy.ParentAlignment = OrgChart.Layout.BranchParentAlignment.Center;
            assistantsLayoutStrategy.ParentChildSpacing = RPOrgChart.parentChildSpacing;
            assistantsLayoutStrategy.SiblingSpacing = RPOrgChart.siblingSpacing;
            diagram.LayoutSettings.LayoutStrategies.add("assistants", assistantsLayoutStrategy);

            diagram.LayoutSettings.DefaultLayoutStrategyId = "vstack";
            diagram.LayoutSettings.DefaultAssistantLayoutStrategyId = "assistants";
            //diagram.LayoutSettings.setBranchSpacing(5);
        },

        getBoxLevel: function(boxContainer, box) {
            var level = 0;
            var obj = {};
            while (box.ParentId > 0) {
                if (!boxContainer.getBoxesById().tryGetValue(box.ParentId, obj)) {
                    break;
                }
                box = obj.v;
                level++;
            }

            return level;
        },

        onLayoutStateChanged: function (sender, args) {
            if (args.State.getCurrentOperation() === OrgChart.Layout.LayoutState.Operation.PreprocessVisualTree) {
                // When layout algorithm is ready to preprocess the tree,
                // we need to have box sizes ready -> hence have to render visible boxes in HTML.
                // Rendering can happen at earlier time, but it's just more convenient to do it here,
                // to utilize some readily available information about visual tree.
                RPOrgChart.renderBoxes();
            }
        },
        
        renderBoxes: function () {
            var visitorFunc = function (node) {
                var box = node.Element;

                if (box.getIsDataBound()) {
                    // we're being run when nodes have already been marked as visible or hidden,
                    // based on IsCollapsed attribute of each Box
                    // so use this knowledge to prevent unnecessary rendering of invisible branches
                    if (node.State.IsHidden) {
                        return true;
                    }

                    box.Size = RPOrgChart.getBoxElementSize(box.DataId);
                }

                return true;
            }

            RPOrgChart.diagram.getVisualTree().IterateParentFirst(visitorFunc);
        },

        getBranchOptimizerFunc: function () {
            return RPOrgChart[RPOrgChart.branchOptimizer];
        },

        branchOptimizerAllLinear: function(node) {
            return node.getIsAssistantRoot() ? null : "linear";
        },
        
        branchOptimizerAllHanger2: function(node) {
            return node.getIsAssistantRoot() ? null : "hanger2";
        },
        
        branchOptimizerAllHanger4: function(node) {
            return node.getIsAssistantRoot() ? null : "hanger4";
        },
        
        branchOptimizerAllFishbone1: function(node) {
            return node.getIsAssistantRoot() ? null : "fishbone1";
        },

        branchOptimizerAllFishbone2: function (node) {
            return node.getIsAssistantRoot() ? null : "fishbone2";
        },

        branchOptimizerAllSingleColumnLeft: function (node) {
            return node.getIsAssistantRoot() ? null : "singleColumnRight";
        },

        branchOptimizerAllSingleColumnRight: function (node) {
            return node.getIsAssistantRoot() ? null : "singleColumnLeft";
        },

        branchOptimizerStackers: function(node) {
            if (node.getIsAssistantRoot()) {
                return null;
            }
            return node.Level === 0 // this is Node for boxContainer.SystemRoot, which is not visible itself
                ? "vstackTop"
                : node.Level === 1 // this is children of SystemRoot - they appear as roots in the diagram
                ? "vstackMiddle"
                : "hstack";

        },

        branchOptimizerSmart: function(node) {
            if (node.getIsAssistantRoot()) {
                return null;
            }

            var childCount = node.getChildCount();

            if (childCount <= 1) {
                return "vstack";
            }

            var nonLeafChildren = 0;
            for (var i = 0; i < childCount; i++) {
                if (node.Children.getItem(i).getChildCount() > 0) {
                    nonLeafChildren++;
                }
            }

            if (nonLeafChildren <= 1) {
                if (childCount <= 4) {
                    return "vstack";
                }
                if (childCount <= 8) {
                    return "fishbone1";
                }
                return "fishbone2";
            }

            return "hanger4";
        },

        boxSizeFunc: function (dataId) {
            // ChartLayoutAlgorithm requires this function to accept data ID
            // so have to convert it to Box ID first, to get rendered visual element
            var boxId = RPOrgChart.diagram.getBoxes().getBoxesByDataId().getItem(dataId).Id;
            return RPOrgChart.diagram.getBoxes().getBoxesById().getItem(boxId).Size;
        },

        getBoxElementSize: function (boxId) {
    		var geo = RPOrgChart.graph.model.cells[boxId].geometry;
    		return new OrgChart.Layout.Size.$ctor1(geo.width, geo.height);
        },

        positionBoxes: function () {
            var diagram = RPOrgChart.diagram;

            var state = new OrgChart.Layout.LayoutState(diagram);

            state.addOperationChanged(RPOrgChart.onLayoutStateChanged);
            state.BoxSizeFunc = Bridge.fn.bind(this, RPOrgChart.boxSizeFunc, null, true);
            state.LayoutOptimizerFunc = Bridge.fn.bind(this, RPOrgChart.getBranchOptimizerFunc(), null, true);

            OrgChart.Layout.LayoutAlgorithm.Apply(state);

            var diagramBoundary = OrgChart.Layout.LayoutAlgorithm.ComputeBranchVisualBoundingRect(diagram.getVisualTree());

			var offsetx = -diagramBoundary.getLeft() + diagramBoundary.getTop();

			var graph = RPOrgChart.graph;
            var cells = graph.model.cells;
			var pointsList = [];
            
            var visitorVertexFunc = function (node) 
            {
                if (node.State.IsHidden) {
                    return false;
                }

                var box = node.Element;

                if (box.getIsDataBound()) {
                    var cell = cells[box.DataId];
                    var geo = cell.geometry.clone();
                    geo.x = node.State.TopLeft.X + offsetx;
                    geo.y = node.State.TopLeft.Y;
                    graph.model.setGeometry(cell, geo);
                }
                
                return true;
            }
            
            var visitorEdgeFunc = function (node) 
            {
				//The algorithm default is 5 px only above the node, this centers it
				var yCorrection = RPOrgChart.correctY? Math.min(0, -(RPOrgChart.parentChildSpacing / 2) + 5) : 0;
                // Render connectors
                if (node.State.Connector != null) {
                	
                    var cell = cells[node.Element.DataId];
                         
                	var outgoingEdge = graph.getOutgoingEdges(cell);

                	var uniquePoints = {};
                	
                	//Sort segments points from top to bottom or left to right + add offset
                	for (var ix = 0; ix < node.State.Connector.Segments.length; ix++) 
                    {
                		var edge = node.State.Connector.Segments[ix];
                		edge.mark = 1 << ix; //TODO Support up to 31 segments. In this a limit?
                        edge.From.X += offsetx;
                        edge.To.X += offsetx;
                        var fx = edge.From.X, fy = edge.From.Y, tx = edge.To.X, ty = edge.To.Y;
                        
                        if ((fx == tx && fy > ty) || (fy == ty && fx > tx))
                    	{
                        	var tmp = edge.From;
                        	edge.From = edge.To;
                        	edge.To = tmp;
                    	}
                    }
                	
                	//Collecting points including intersection of segments
                    for (var ix = 0; ix < node.State.Connector.Segments.length; ix++) 
                    {
                    	var edge = node.State.Connector.Segments[ix];
                        var fx = edge.From.X, fy = edge.From.Y, tx = edge.To.X, ty = edge.To.Y;
                        var fp = new mxPoint(fx, fy);
						pointsList.push(fp);
                        fp.mark = edge.mark;
                        var up = uniquePoints[fx + ',' + fy];
                        
                        if (up != null)
                    	{
                        	up.mark |= fp.mark;
                    	}
                        else
                        {
                        	uniquePoints[fx + ',' + fy] = fp;
                        }
                        
                        var tp = new mxPoint(tx, ty);
						pointsList.push(tp);
                        tp.mark = edge.mark;
                        var up = uniquePoints[tx + ',' + ty];
                        
                        if (up != null)
                    	{
                        	up.mark |= tp.mark;
                    	}
                        else
                        {
                        	uniquePoints[tx + ',' + ty] = tp; 
                        }
                        
                        //Find intersections
                        for (var j = ix + 1; j < node.State.Connector.Segments.length; j++) 
                        {
                        	var e2 = node.State.Connector.Segments[j];
                        	var fx2 = e2.From.X, fy2 = e2.From.Y, tx2 = e2.To.X, ty2 = e2.To.Y;
                        	
                        	if (fx == tx && fy <= fy2 && ty >= fy2 && fx2 <= fx && tx2 >= fx) //Ver |_ Hor
                    		{
                        		var ip = new mxPoint(fx, fy2);
								pointsList.push(ip);
                        		ip.mark = edge.mark | e2.mark;
                        		var up = uniquePoints[fx + ',' + fy2];
                                
                                if (up != null)
                            	{
                                	up.mark |= ip.mark;
                            	}
                                else
                                {
                                	uniquePoints[fx + ',' + fy2] = ip;
                                }
                    		}
                        	else if (fy == ty && fx <= fx2 && tx >= fx2 && fy2 <= fy && ty2 >= fy) //Hor _| Ver
                    		{
                        		var ip = new mxPoint(fx2, fy);
								pointsList.push(ip);
                        		ip.mark = edge.mark | e2.mark;
                        		var up = uniquePoints[fx2 + ',' + fy]
                                
                                if (up != null)
                            	{
                                	up.mark |= ip.mark;
                            	}
                                else
                                {
                                	uniquePoints[fx2 + ',' + fy] = ip;
                                }
                    		}
                        }
                    }
                    
                    //Sort points on y then x
                    var pointsArr = [];
                    
                    for (var k in uniquePoints)
                	{
                    	pointsArr.push(uniquePoints[k]);
                	}
                    
                    pointsArr.sort(function(a, b)
                   	{
                    	var dy = a.y - b.y;
                    	
                    	return dy == 0? a.x - b.x : dy; 
                    });
                    
                    function pointOnCell(geo, p)
                    {
                    	return p.x >= geo.x && p.x <= geo.x + geo.width && p.y >= geo.y && p.y <= geo.y + geo.height;
                    };
                    
                    function adjustEdgeGeoAndStyle(edge, edgePoints)
                    {
                        var eGeo = edge.geometry.clone();
						
						for (var i = 0; edgePoints && i < edgePoints.length; i++)
						{
							if (!edgePoints[i].corrected)
							{
								edgePoints[i].y += yCorrection;
								edgePoints[i].corrected = true
							}
						}
						
                        eGeo.points = edgePoints;
                        graph.model.setGeometry(edge, eGeo);

                        //Remove entry and exit points
                        graph.setCellStyles('entryX', null, [edge]);
                        graph.setCellStyles('entryY', null, [edge]);
                        graph.setCellStyles('exitX', null, [edge]);
                        graph.setCellStyles('exitY', null, [edge]);
                        //Set type orthogonal
                        graph.setCellStyles('edgeStyle', 'orthogonalEdgeStyle', [edge]);
                    };
                    
                    var outgoingEdge = graph.getOutgoingEdges(cell);
                    
                    //Simple case of a single segment. TODO Handle this case earlier
                    if (pointsArr.length == 2 && outgoingEdge.length == 1)
                	{
                        adjustEdgeGeoAndStyle(outgoingEdge[0], pointsArr);
                	}
                    else
                	{
                        var srcGeo = cell.geometry;
                        var srcP;
                        
                        //Find src starting point //TODO It should be first point always? 
                        for (var i = 0; i < pointsArr.length; i++)
                    	{
                        	if (pointOnCell(srcGeo, pointsArr[i]))
                    		{
                        		srcP = pointsArr[i];
                        		break;
                    		}
                    	}
                        
                        var selected;
                        
                        function getNextPoint(lp)
                        {
                        	for (var i = 0; i < pointsArr.length; i++)
                        	{
                        		var p = pointsArr[i];
                        		if (selected[p.x + ',' + p.y]) continue;
                        		
                        		if (p.mark & lp.mark)
                    			{
                        			selected[p.x + ',' + p.y] = true;
                        			return p;
                                }
                        	}
                        }
                        
                    	for (var j = 0; j < outgoingEdge.length; j++)
                		{
                    		if (outgoingEdge[j].target != null)
                			{
                    			selected = {};
                    			selected[srcP.x + ',' + srcP.y] = true;
                                var trgGeo = outgoingEdge[j].target.geometry;
                                
                                var edgePoints = [srcP];
                                var lp = srcP;
                                var safeGuard = 0;
                                //Is BFS better?
                                while (safeGuard < 1000)
                            	{
                                	safeGuard++;
                                	var np = getNextPoint(lp);

                                	//retract, then remove this point
                                	if (np == null) 
                            		{
                                		edgePoints.pop();
                                		lp = edgePoints[edgePoints.length - 1];
                            		}
                                	else 
                            		{
                                		edgePoints.push(np);
                                		lp = np;
                                		if (pointOnCell(trgGeo, np)) break;
                            		}
                            	}
                                
                                //Remove retracted points TODO can we do it in a better way?
                                if (edgePoints.length > 2)
                                {
	                                var spX = edgePoints[0].x;
	                                var lpX = edgePoints[edgePoints.length - 1].x;
	                                
	                                for (var i = edgePoints.length - 2; i > 0; i--)
	                            	{
	                                	if ((spX > lpX && edgePoints[i].x < lpX) || (spX < lpX && edgePoints[i].x < spX))
	                            		{
	                                		edgePoints.splice(i, 1);
	                            		}
	                            	}
                                }
                                
                                var eGeo = outgoingEdge[j].geometry.clone();
                                eGeo.points = edgePoints;
                                RPOrgChart.graph.model.setGeometry(outgoingEdge[j], eGeo);
                                
                                //Fix edge points and style
                                adjustEdgeGeoAndStyle(outgoingEdge[j], edgePoints);
                			}
                		}                            
            		}
                }

                return true;
            }

            diagram.getVisualTree().IterateParentFirst(visitorVertexFunc);
            diagram.getVisualTree().IterateParentFirst(visitorEdgeFunc);
			
			//Cleanup
			for (var i = 0; i < pointsList.length; i++)
			{
				delete pointsList[i].mark;
				delete pointsList[i].corrected;
			}
        }

    }
});

Bridge.init();
