Topo2 - Added Multilink support

Change-Id: Ic3a4955b415495578d592ae666b95bf23b24523d
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
index 15c5463..d1747a3 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
@@ -75,15 +75,6 @@
         return true;
     }
 
-    function getDefaultPosition(link) {
-        return {
-            x1: link.get('source').x,
-            y1: link.get('source').y,
-            x2: link.get('target').x,
-            y2: link.get('target').y
-        };
-    }
-
     angular.module('ovTopo2')
     .factory('Topo2LayoutService',
         [
@@ -273,15 +264,8 @@
                     },
                     calcPosition: function () {
                         var lines = this;
-
                         lines.each(function (d) {
-                            if (d.get('type') === 'hostLink') {
-                                d.set('position', getDefaultPosition(d));
-                            }
-                        });
-
-                        lines.each(function (d) {
-                            d.set('position', getDefaultPosition(d));
+                            d.setPosition.bind(d)();
                         });
                     },
                     sendUpdateMeta: function (d, clearPos) {
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
index f19f2df..350b80a 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
@@ -212,15 +212,41 @@
                 this.set('enhanced', false);
                 d3.select('.topo2-portLabels').selectAll('.portLabel').remove();
             },
+            amt: function (numLinks, index) {
+                var gap = 6;
+                return (index - ((numLinks - 1) / 2)) * gap;
+            },
+            defaultPosition: function () {
+                return {
+                    x1: this.get('source').x,
+                    y1: this.get('source').y,
+                    x2: this.get('target').x,
+                    y2: this.get('target').y
+                }
+            },
+            calcMovement: function (amt, flipped) {
+                var pos = this.defaultPosition(),
+                    mult = flipped ? -amt : amt,
+                    dx = pos.x2 - pos.x1,
+                    dy = pos.y2 - pos.y1,
+                    length = Math.sqrt((dx * dx) + (dy * dy));
+
+                return {
+                    x1: pos.x1 + (mult * dy / length),
+                    y1: pos.y1 + (mult * -dx / length),
+                    x2: pos.x2 + (mult * dy / length),
+                    y2: pos.y2 + (mult * -dx / length)
+                };
+            },
             setPosition: function () {
-                this.set({
-                    position: {
-                        x1: this.get('source').x,
-                        y1: this.get('source').y,
-                        x2: this.get('target').x,
-                        y2: this.get('target').y
-                    }
-                });
+
+                var multiline = this.get('multiline');
+                if (multiline) {
+                    var offsetAmt = this.amt(multiline.deviceLinks, multiline.index);
+                    this.set('position', this.calcMovement(offsetAmt));
+                } else {
+                    this.set('position', this.defaultPosition());
+                }
 
                 if (this.get('enhanced')) {
                     this.updatePortPosition();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Region.js b/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
index 9e5b9e9..c0e18a7 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
@@ -83,6 +83,8 @@
                         layerOrder: this.regionData.layerOrder
                     });
 
+                    this.sortMultiLinks();
+
                     this.model.set({ subregions: t2sr.createSubRegionCollection(this.regionData.subregions, this) });
                     this.model.set({ devices: t2ds.createDeviceCollection(this.regionData.devices, this) });
                     this.model.set({ hosts: t2hs.createHostCollection(this.regionData.hosts, this) });
@@ -101,14 +103,44 @@
                     this.regionData = null;
                     this.createEmptyModel();
                 },
+                removePort: function (key) {
+                    var regex = new RegExp('^[^/]*');
+                    return regex.exec(key)[0];
+                },
+                sortMultiLinks: function () {
+                    var _this = this,
+                        deviceConnections = {};
+
+                    _.each(this.regionData.links, function (link) {
+                        var devA = _this.removePort(link.epA),
+                            devB = _this.removePort(link.epB),
+                            key = devA + '~' + devB;
+
+                        if (!deviceConnections[key]) {
+                            deviceConnections[key] = [];
+                        }
+
+                        deviceConnections[key].push(link);
+                    });
+
+                    _.each(deviceConnections, function (connection) {
+                        if (connection.length > 1) {
+                            _.orderBy(connection, ['portA']);
+                            _.each(connection, function (link, index) {
+                                link.multiline = {
+                                    deviceLinks: connection.length,
+                                    index: index
+                                };
+                            })
+                        }
+                    });
+                },
                 isRootRegion: function () {
                     return this.model.get('id') === ROOT;
                 },
                 findNodeById: function (link, id) {
                     if (link.get('type') !== 'UiEdgeLink') {
-                        // Remove /{port} from id if needed
-                        var regex = new RegExp('^[^/]*');
-                        id = regex.exec(id)[0];
+                        id = this.removePort(id);
                     }
                     return this.model.get('devices').get(id) ||
                         this.model.get('hosts').get(id) ||