Updated fn-spec to include classNames
Removed Classnames file and added code to fn.js
Fixed typo dimentions to dimensions
Moved Device/Link logic from Topo2D3 into the model
Model now calls onChange when any property is changed via the set Method

WIP - Added d3 force layout for devices and lines

Change-Id: I4d1afd3cd4cecf2f719e27f4be5d1e874bd9e342
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 5f2b6b7..44c5ec9 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
@@ -22,12 +22,162 @@
 (function () {
     'use strict';
 
-    var Collection, Model;
+    var Collection, Model, region, ts;
 
-    function createLinkCollection(data, region) {
+    var widthRatio = 1.4,
+        linkScale = d3.scale.linear()
+            .domain([1, 12])
+            .range([widthRatio, 12 * widthRatio])
+            .clamp(true),
+        allLinkTypes = 'direct indirect optical tunnel UiDeviceLink',
+        allLinkSubTypes = 'inactive not-permitted';
+
+    // configuration
+    var linkConfig = {
+        light: {
+            baseColor: '#939598',
+            inColor: '#66f',
+            outColor: '#f00'
+        },
+        dark: {
+            // TODO : theme
+            baseColor: '#939598',
+            inColor: '#66f',
+            outColor: '#f00'
+        },
+        inWidth: 12,
+        outWidth: 10
+    };
+
+    var defaultLinkType = 'direct',
+        nearDist = 15;
+
+    function createLink() {
+
+        var linkPoints = this.linkEndPoints(this.get('epA'), this.get('epB'));
+        console.log(this);
+
+        var attrs = angular.extend({}, linkPoints, {
+            key: this.get('id'),
+            class: 'link',
+            weight: 1,
+            srcPort: this.get('srcPort'),
+            tgtPort: this.get('dstPort'),
+            position: {
+                x1: 0,
+                y1: 0,
+                x2: 0,
+                y2: 0
+            }
+            // functions to aggregate dual link state
+//            extra: link.extra
+        });
+
+        this.set(attrs);
+    }
+
+    function linkEndPoints(srcId, dstId) {
+
+        var sourceNode = this.region.get('devices').get(srcId.substring(0, srcId.length -2));
+        var targetNode = this.region.get('devices').get(dstId.substring(0, dstId.length -2));
+
+//        var srcNode = lu[srcId],
+//            dstNode = lu[dstId],
+//            sMiss = !srcNode ? missMsg('src', srcId) : '',
+//            dMiss = !dstNode ? missMsg('dst', dstId) : '';
+//
+//        if (sMiss || dMiss) {
+//            $log.error('Node(s) not on map for link:' + sMiss + dMiss);
+//            //logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
+//            return null;
+//        }
+
+        this.source = sourceNode.toJSON();
+        this.target = targetNode.toJSON();
+
+        return {
+            source: sourceNode,
+            target: targetNode
+        };
+    }
+
+    function createLinkCollection(data, _region) {
+
+        var LinkModel = Model.extend({
+            region: _region,
+            createLink: createLink,
+            linkEndPoints: linkEndPoints,
+            type: function () {
+                return this.get('type');
+            },
+            expected: function () {
+                //TODO: original code is: (s && s.expected) && (t && t.expected);
+                return true;
+            },
+            online: function () {
+                return true;
+                return both && (s && s.online) && (t && t.online);
+            },
+            linkWidth: function () {
+                var s = this.get('fromSource'),
+                    t = this.get('fromTarget'),
+                    ws = (s && s.linkWidth) || 0,
+                    wt = (t && t.linkWidth) || 0;
+
+                    // console.log(s);
+                // TODO: Current json is missing linkWidth
+                return 1.2;
+                return this.get('position').multiLink ? 5 : Math.max(ws, wt);
+            },
+
+            restyleLinkElement: function (immediate) {
+                // this fn's job is to look at raw links and decide what svg classes
+                // need to be applied to the line element in the DOM
+                var th = ts.theme(),
+                    el = this.el,
+                    type = this.get('type'),
+                    lw = this.linkWidth(),
+                    online = this.online(),
+                    modeCls = this.expected() ? 'inactive' : 'not-permitted',
+                    delay = immediate ? 0 : 1000;
+
+                console.log(type);
+
+                // NOTE: understand why el is sometimes undefined on addLink events...
+                // Investigated:
+                // el is undefined when it's a reverse link that is being added.
+                // updateLinks (which sets ldata.el) isn't called before this is called.
+                // Calling _updateLinks in addLinkUpdate fixes it, but there might be
+                // a more efficient way to fix it.
+                if (el && !el.empty()) {
+                    el.classed('link', true);
+                    el.classed(allLinkSubTypes, false);
+                    el.classed(modeCls, !online);
+                    el.classed(allLinkTypes, false);
+                    if (type) {
+                        el.classed(type, true);
+                    }
+                    el.transition()
+                        .duration(delay)
+                        .attr('stroke-width', linkScale(lw))
+                        .attr('stroke', linkConfig[th].baseColor);
+                }
+            },
+
+            onEnter: function (el) {
+                var link = d3.select(el);
+                this.el = link;
+
+                this.restyleLinkElement();
+
+                if (this.get('type') === 'hostLink') {
+                    sus.visible(link, api.showHosts());
+                }
+            }
+        });
 
         var LinkCollection = Collection.extend({
-            model: Model
+            model: LinkModel,
         });
 
         return new LinkCollection(data);
@@ -35,12 +185,13 @@
 
     angular.module('ovTopo2')
     .factory('Topo2LinkService',
-        ['Topo2Collection', 'Topo2Model',
+        ['Topo2Collection', 'Topo2Model', 'ThemeService',
 
-            function (_Collection_, _Model_) {
+            function (_Collection_, _Model_, _ts_) {
 
+                ts = _ts_;
                 Collection = _Collection_;
-                Model = _Model_.extend({});
+                Model = _Model_;
 
                 return {
                     createLinkCollection: createLinkCollection