Enable link functionality in GUI2 Topology View

Change-Id: I1b88080ecdf8c9b6f8a60af4832a12441186d508
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
index 1d6ebf4..25e52ab 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
@@ -24,6 +24,7 @@
     OnInit,
     Output,
     QueryList,
+    SimpleChange,
     SimpleChanges,
     ViewChildren
 } from '@angular/core';
@@ -31,15 +32,24 @@
 import {
     Device,
     ForceDirectedGraph,
-    Host, HostLabelToggle,
+    Host,
+    HostLabelToggle,
     LabelToggle,
     LayerType,
-    Node,
+    Link,
+    LinkHighlight,
+    ModelEventMemo,
+    ModelEventType,
     Region,
     RegionLink,
-    SubRegion
+    SubRegion,
+    UiElement
 } from './models';
-import {DeviceNodeSvgComponent, HostNodeSvgComponent} from './visuals';
+import {
+    DeviceNodeSvgComponent,
+    HostNodeSvgComponent,
+    LinkSvgComponent
+} from './visuals';
 
 
 /**
@@ -58,7 +68,7 @@
     @Input() onosInstMastership: string = '';
     @Input() visibleLayer: LayerType = LayerType.LAYER_DEFAULT;
     @Output() linkSelected = new EventEmitter<RegionLink>();
-    @Output() selectedNodeEvent = new EventEmitter<Node>();
+    @Output() selectedNodeEvent = new EventEmitter<UiElement>();
     @Input() selectedLink: RegionLink = null;
     @Input() showHosts: boolean = false;
     @Input() deviceLabelToggle: LabelToggle = LabelToggle.NONE;
@@ -71,6 +81,7 @@
     // template view with the *ngFor and we get them by a query here
     @ViewChildren(DeviceNodeSvgComponent) devices: QueryList<DeviceNodeSvgComponent>;
     @ViewChildren(HostNodeSvgComponent) hosts: QueryList<HostNodeSvgComponent>;
+    @ViewChildren(LinkSvgComponent) links: QueryList<LinkSvgComponent>;
 
     constructor(
         protected log: LogService,
@@ -115,7 +126,7 @@
      */
     ngOnInit() {
         // Receiving an initialized simulated graph from our custom d3 service
-        this.graph = new ForceDirectedGraph(this.options);
+        this.graph = new ForceDirectedGraph(this.options, this.log);
 
         /** Binding change detection check on each tick
          * This along with an onPush change detection strategy should enforce
@@ -186,6 +197,7 @@
 
             this.graph.initSimulation(this.options);
             this.graph.initNodes();
+            this.graph.initLinks();
             this.log.debug('ForceSvgComponent input changed',
                 this.graph.nodes.length, 'nodes,', this.graph.links.length, 'links');
         }
@@ -245,8 +257,8 @@
      * changed.
      * @param selectedNode the newly selected node
      */
-    updateSelected(selectedNode: Node): void {
-        this.log.debug('Device selected', selectedNode);
+    updateSelected(selectedNode: UiElement): void {
+        this.log.debug('Node or link selected', selectedNode);
         this.devices
             .filter((d) =>
                 selectedNode === undefined || d.device.id !== selectedNode.id)
@@ -256,19 +268,156 @@
                 selectedNode === undefined || h.host.id !== selectedNode.id)
             .forEach((h) => h.deselect());
 
+        this.links
+            .filter((l) =>
+                selectedNode === undefined || l.link.id !== selectedNode.id)
+            .forEach((l) => l.deselect());
+
         this.selectedNodeEvent.emit(selectedNode);
     }
 
     /**
      * We want to filter links to show only those not related to hosts if the
-     * 'showHosts' flag has been switched off. If 'shwoHosts' is true, then
+     * 'showHosts' flag has been switched off. If 'showHosts' is true, then
      * display all links.
      */
-    filteredLinks() {
+    filteredLinks(): Link[] {
         return this.regionData.links.filter((h) =>
             this.showHosts ||
             ((<Host>h.source).nodeType !== 'host' &&
             (<Host>h.target).nodeType !== 'host'));
     }
+
+    /**
+     * When changes happen in the model, then model events are sent up through the
+     * Web Socket
+     * @param type - the type of the change
+     * @param memo - a qualifier on the type
+     * @param subject - the item that the update is for
+     * @param data - the new definition of the item
+     */
+    handleModelEvent(type: ModelEventType, memo: ModelEventMemo, subject: string, data: UiElement): void {
+        switch (type) {
+            case ModelEventType.DEVICE_ADDED_OR_UPDATED:
+                if (memo === ModelEventMemo.ADDED) {
+                    this.regionData.devices[this.visibleLayerIdx()].push(<Device>data);
+                } else if (memo === ModelEventMemo.UPDATED) {
+                    const oldDevice: Device =
+                        this.regionData.devices[this.visibleLayerIdx()]
+                            .find((d) => d.id === subject);
+                    this.compareDevice(oldDevice, <Device>data);
+                } else {
+                    this.log.warn('Device ', memo, ' - not yet implemented', data);
+                }
+                this.log.warn('Device ', memo, ' - not yet implemented', data);
+                break;
+            case ModelEventType.HOST_ADDED_OR_UPDATED:
+                if (memo === ModelEventMemo.ADDED) {
+                    this.regionData.hosts[this.visibleLayerIdx()].push(<Host>data);
+                    this.log.warn('Host added - not yet implemented', data);
+                } else if (memo === ModelEventMemo.UPDATED) {
+                    const oldHost: Host = this.regionData.hosts[this.visibleLayerIdx()]
+                        .find((h) => h.id === subject);
+                    this.compareHost(oldHost, <Host>data);
+                    this.log.warn('Host updated - not yet implemented', data);
+                } else {
+                    this.log.warn('Host change', memo, ' - unexpected');
+                }
+                break;
+            case ModelEventType.DEVICE_REMOVED:
+                if (memo === ModelEventMemo.REMOVED || memo === undefined) {
+                    const removeIdx: number =
+                        this.regionData.devices[this.visibleLayerIdx()]
+                            .findIndex((d) => d.id === subject);
+                    const removeCmpt: DeviceNodeSvgComponent =
+                        this.devices.find((dc) => dc.device.id === subject);
+                    this.log.warn('Device ', subject, 'removed - not yet implemented', removeIdx, removeCmpt.device.id);
+                } else {
+                    this.log.warn('Device removed - unexpected memo', memo);
+                }
+                break;
+            case ModelEventType.HOST_REMOVED:
+                if (memo === ModelEventMemo.REMOVED || memo === undefined) {
+                    const removeIdx: number =
+                        this.regionData.hosts[this.visibleLayerIdx()]
+                            .findIndex((h) => h.id === subject);
+                    const removeCmpt: HostNodeSvgComponent =
+                        this.hosts.find((hc) => hc.host.id === subject);
+                    this.log.warn('Host ', subject, 'removed - not yet implemented', removeIdx, removeCmpt.host.id);
+                } else {
+                    this.log.warn('Host removed - unexpected memo', memo);
+                }
+                break;
+            case ModelEventType.LINK_ADDED_OR_UPDATED:
+                this.log.warn('link added or updated - not yet implemented', subject);
+                break;
+            default:
+                this.log.error('Unexpected model event', type, 'for', subject);
+        }
+    }
+
+    private compareDevice(oldDevice: Device, updatedDevice: Device) {
+        if (oldDevice.master !== updatedDevice.master) {
+            this.log.debug('Mastership has changed for', updatedDevice.id, 'to', updatedDevice.master);
+        }
+        if (oldDevice.online !== updatedDevice.online) {
+            this.log.debug('Status has changed for', updatedDevice.id, 'to', updatedDevice.online);
+        }
+    }
+
+    private compareHost(oldHost: Host, updatedHost: Host) {
+        if (oldHost.configured !== updatedHost.configured) {
+            this.log.debug('Configured has changed for', updatedHost.id, 'to', updatedHost.configured);
+        }
+    }
+
+    /**
+     * When traffic monitoring is turned on (A key) highlights will be sent back
+     * from the WebSocket through the Traffic Service
+     * @param devices - an array of device highlights
+     * @param hosts - an array of host highlights
+     * @param links - an array of link highlights
+     */
+    handleHighlights(devices: Device[], hosts: Host[], links: LinkHighlight[]): void {
+
+        if (devices.length > 0) {
+            this.log.debug(devices.length, 'Devices highlighted');
+            devices.forEach((dh) => {
+                const deviceComponent: DeviceNodeSvgComponent = this.devices.find((d) => d.device.id === dh.id );
+                if (deviceComponent) {
+                    deviceComponent.ngOnChanges(
+                        {'deviceHighlight': new SimpleChange(<Device>{}, dh, true)}
+                    );
+                    this.log.debug('Highlighting device', deviceComponent.device.id);
+                } else {
+                    this.log.warn('Device component not found', dh.id);
+                }
+            });
+        }
+        if (hosts.length > 0) {
+            this.log.debug(hosts.length, 'Hosts highlighted');
+            hosts.forEach((hh) => {
+                const hostComponent: HostNodeSvgComponent = this.hosts.find((h) => h.host.id === hh.id );
+                if (hostComponent) {
+                    hostComponent.ngOnChanges(
+                        {'hostHighlight': new SimpleChange(<Host>{}, hh, true)}
+                    );
+                    this.log.debug('Highlighting host', hostComponent.host.id);
+                }
+            });
+        }
+        if (links.length > 0) {
+            this.log.debug(links.length, 'Links highlighted');
+            links.forEach((lh) => {
+                const linkComponent: LinkSvgComponent = this.links.find((l) => l.link.id === lh.id );
+                if (linkComponent) { // A link might not be present is hosts viewing is switched off
+                    linkComponent.ngOnChanges(
+                        {'linkHighlight': new SimpleChange(<LinkHighlight>{}, lh, true)}
+                    );
+                    // this.log.debug('Highlighting link', linkComponent.link.id, lh.css, lh.label);
+                }
+            });
+        }
+    }
 }