/**
 * 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.
 */

/* global Hogan */
/* Namespace for core functionality related to Network Topology. */

function Network(data) {
  for (var key in data) {
    if ({}.hasOwnProperty.call(data, key)) {
      this[key] = data[key];
    }
  }
  this.iconType = 'text';
  this.icon = '\uf0c2'; // Cloud
  this.collapsed = false;
  this.type = 'network';
  this.instances = 0;
}

function ExternalNetwork(data) {
  for (var key in data) {
    if ({}.hasOwnProperty.call(data, key)) {
      this[key] = data[key];
    }
  }
  this.collapsed = false;
  this.iconType = 'text';
  this.icon = '\uf0ac'; // Globe
  this.instances = 0;
}

function Router(data) {
  for (var key in data) {
    if ({}.hasOwnProperty.call(data, key)) {
      this[key] = data[key];
    }
  }
  this.iconType = 'path';
  this.svg = 'router';
  this.networks = [];
  this.ports = [];
  this.type = 'router';
}

function Server(data) {
  for (var key in data) {
    if ({}.hasOwnProperty.call(data, key)) {
      this[key] = data[key];
    }
  }
  this.iconType = 'text';
  this.icon = '\uf108'; // Server
  this.networks = [];
  this.type = 'instance';
  this.ip_addresses = [];
}

function listContains(obj, list) {
  // Function to help checking if an object is present on a list
  for (var i = 0; i < list.length; i++) {
    if (angular.equals(list[i], obj)) {
      return true;
    }
  }
  return false;
}

horizon.network_topology = {
  fa_globe_glyph: '\uf0ac',
  fa_globe_glyph_width: 15,
  svg:'#topology_canvas',
  nodes: [],
  links: [],
  data: [],
  zoom: d3.behavior.zoom(),
  data_loaded: false,
  svg_container:'#topologyCanvasContainer',
  balloonTmpl : null,
  balloon_deviceTmpl : null,
  balloon_portTmpl : null,
  balloon_netTmpl : null,
  balloon_instanceTmpl : null,
  network_index: {},
  balloonID:null,
  network_height : 0,

  init:function() {
    var self = this;

    self.$loading_template = horizon.networktopologyloader.setup_loader($(self.svg_container));

    if (angular.element('#networktopology').length === 0) {
      return;
    }

    self.data = {};
    self.data.networks = {};
    self.data.routers = {};
    self.data.servers = {};
    self.data.ports = {};

    // Setup balloon popups
    self.balloonTmpl = "<span>balloonTmpl</span>"
    self.balloon_deviceTmpl = "<span>balloon_deviceTmpl</span>"
    self.balloon_portTmpl = "<span>balloon_portTmpl</span>"
    self.balloon_netTmpl = "<span>balloon_netTmpl</span>"
    self.balloon_instanceTmpl = "<span>balloon_instanceTmpl</span>"

    angular.element(document)
      .on('click', 'a.closeTopologyBalloon', function(e) {
        e.preventDefault();
        self.delete_balloon();
      })
      .on('click', '.topologyBalloon', function(e) {
        e.stopPropagation();
      })
      .on('click', 'a.vnc_window', function(e) {
        e.preventDefault();
        var vncWindow = window.open(angular.element(this).attr('href'), vncWindow, 'width=760,height=560');
        self.delete_balloon();
      });

    angular.element('#toggle_labels').change(function() {
      horizon.cookies.put('show_labels', this.checked);
      self.refresh_labels();
    });

    angular.element('#toggle_networks').change(function() {
      horizon.cookies.put('are_networks_collapsed', this.checked);
      self.refresh_networks();
      self.refresh_labels();
    });

    // set up loader first thing
    self.$loading_template.show();

    self.create_vis();
    self.force_direction(0.05,70,-700);
    if(horizon.networktopologyloader.model !== null) {
      self.retrieve_network_info(true);
    }

    d3.select(window).on('resize', function() {
      var width = angular.element('#topologyCanvasContainer').width();
      var height = angular.element('#topologyCanvasContainer').height();
      self.force.size([width, height]).resume();
    });

    angular.element('#networktopology').on('change', function() {
      self.retrieve_network_info(true);
      if(angular.equals(self.data.networks,{}) && angular.equals(self.data.routers,{}) &&
         angular.equals(self.data.servers,{})){
        $('.loader-inline').remove();
        angular.element('#topologyCanvasContainer').find('svg').remove();
        $(self.svg_container).addClass('noinfo');
        return;
      }
    });

    // register for message notifications
    horizon.networktopologymessager.addMessageHandler(
        this.handleMessage, this
    );
  },

  // Shows/Hides graph labels
  refresh_labels: function() {
    var show_labels = horizon.cookies.get('show_labels') == true;
    angular.element('.nodeLabel').toggle(show_labels);
  },

  // Collapses/Uncollapses networks in the graph
  refresh_networks: function() {
    var self = this;
    var are_collapsed = horizon.cookies.get('are_networks_collapsed') == 'true';
    for (var n in self.nodes) {
      if ({}.hasOwnProperty.call(self.nodes, n)) {
        if (self.nodes[n].data instanceof Network || self.nodes[n].data instanceof ExternalNetwork) {
            self.collapse_network(self.nodes[n], are_collapsed);
        }
      }
    }
  },

  // Load config from cookie
  load_config: function() {
    var self = this;

    var labels = horizon.cookies.get('show_labels') == 'true';
    var networks = horizon.cookies.get('are_networks_collapsed') == 'true';

    if(networks) {
      angular.element('#toggle_networks_label').addClass('active');
      angular.element('#toggle_networks').prop('checked', networks);
      self.refresh_networks();
    }

    if(labels) {
      angular.element('#toggle_labels_label').addClass('active');
      angular.element('#toggle_labels').prop('checked', labels);
      self.refresh_labels();
    }
  },

  handleMessage:function(message) {
    var self = this;
    var deleteData = horizon.networktopologymessager.delete_data;
    horizon.modals.spinner.modal('hide');
    if (message.type == 'success') {
      self.remove_node_on_delete(deleteData);
    }
  },

  // Get the json data about the current deployment
  retrieve_network_info: function(force_start) {
    var self = this;
    self.data_loaded = true;
    self.load_topology(horizon.networktopologyloader.model);
    if (force_start) {
      var i = 0;
      self.force.start();
      while (i <= 100) {
        self.force.tick();
        i++;
      }
    }
  },

  getScreenCoords: function(x, y) {
    var self = this;
    if (self.translate) {
      var xn = self.translate[0] + x * self.zoom.scale();
      var yn = self.translate[1] + y * self.zoom.scale();
      return { x: xn, y: yn };
    } else {
      return { x: x, y: y };
    }
  },

  // Setup the main visualisation
  create_vis: function() {
    var self = this;
    angular.element('#topologyCanvasContainer').find('svg').remove();

    // Main svg
    self.outer_group = d3.select('#topologyCanvasContainer').append('svg')
      .attr('width', '100%')
      .attr('height', angular.element(document).height() - 270 + "px")
      .attr('pointer-events', 'all')
      .append('g')
      .call(self.zoom
        .scaleExtent([0.1,1.5])
        .on('zoom', function() {
            self.delete_balloon();
            self.vis.attr('transform', 'translate(' + d3.event.translate + ')scale(' +
              self.zoom.scale() + ')');
            self.translate = d3.event.translate;
          })
        )
      .on('dblclick.zoom', null);

    // Background for capturing mouse events
    self.outer_group.append('rect')
      .attr('width', '100%')
      .attr('height', '100%')
      .attr('fill', 'white')
      .on('click', function() {
        self.delete_balloon();
      });

    // svg wrapper for nodes to sit on
    self.vis = self.outer_group.append('g');
  },

  // Calculate the hulls that surround networks
  convex_hulls: function(nodes) {
    var net, _i, _len, _ref, _h, i;
    var hulls = {};
    var networkids = {};
    var k = 0;
    var offset = 40;

    while (k < nodes.length) {
      var n = nodes[k];
      if (n.data !== undefined) {
        if (n.data instanceof Server) {
          _ref = n.data.networks;
          for (_i = 0, _len = _ref.length; _i < _len; _i++) {
            net = _ref[_i];
            if (net instanceof Network) {
              _h = hulls[net.id] || (hulls[net.id] = []);
              _h.push([n.x - offset, n.y - offset]);
              _h.push([n.x - offset, n.y + offset]);
              _h.push([n.x + offset, n.y - offset]);
              _h.push([n.x + offset, n.y + offset]);
            }
          }
        } else if (n.data instanceof Network) {
          net = n.data;
          networkids[net.id] = n;
          _h = hulls[net.id] || (hulls[net.id] = []);
          _h.push([n.x - offset, n.y - offset]);
          _h.push([n.x - offset, n.y + offset]);
          _h.push([n.x + offset, n.y - offset]);
          _h.push([n.x + offset, n.y + offset]);

        }
      }
      ++k;
    }
    var hullset = [];
    for (i in hulls) {
      if ({}.hasOwnProperty.call(hulls, i)) {
        hullset.push({group: i, network: networkids[i], path: d3.geom.hull(hulls[i])});
      }
    }

    return hullset;
  },

  // Setup the force direction
  force_direction: function(grav, dist, ch) {
    var self = this;

    angular.element('[data-toggle="tooltip"]').tooltip({container: 'body'});
    self.curve = d3.svg.line()
      .interpolate('cardinal-closed')
      .tension(0.85);
    self.fill = d3.scale.category10();

    self.force = d3.layout.force()
      .gravity(grav)
      .linkDistance(function(d) {
        if (d.source.data instanceof Server || d.target.data instanceof Server) {
          if (d.source.data.networks) {
            return (dist * d.source.data.networks.length) + (5 * d.target.data.instances) + 20;
          } else if (d.target.data.networks) {
            return (dist * d.target.data.networks.length) + (5 * d.source.data.instances) + 20;
          }
        } else if (d.source.data instanceof Router || d.target.data instanceof Router) {
          if (d.source.data.networks) {
            if (d.source.data.networks.length === 0) {
              return dist + 20;
            } else if (d.target.data.instances) {
              return dist * d.source.data.networks.length + (10 * d.target.data.instances) + 20;
            }
            return dist * d.source.data.networks.length + 20;
          } else if (d.target.data.networks) {
            if (d.target.data.networks.length === 0) {
              return dist + 20;
            } else if (d.source.data.instances) {
              return dist * d.target.data.networks.length + (10 * d.source.data.instances) + 20;
            }
            return dist * d.source.data.networks.length + 20;
          }
        } else {
          return dist;
        }
      })
      .charge(ch)
      .size([angular.element('#topologyCanvasContainer').width(),
             angular.element('#topologyCanvasContainer').height()])
      .nodes(self.nodes)
      .links(self.links)
      .on('tick', function() {
        self.vis.selectAll('g.node')
          .attr('transform', function(d) {
            return 'translate(' + d.x + ',' + d.y + ')';
          });

        self.vis.selectAll('line.link')
          .attr('x1', function(d) { return d.source.x; })
          .attr('y1', function(d) { return d.source.y; })
          .attr('x2', function(d) { return d.target.x; })
          .attr('y2', function(d) { return d.target.y; });

        self.vis.selectAll('path.hulls')
          .data(self.convex_hulls(self.vis.selectAll('g.node').data()))
            .attr('d', function(d) {
              return self.curve(d.path);
            })
          .enter().insert('path', 'g')
            .attr('class', 'hulls')
            .style('fill', function(d) {
              return self.fill(d.group);
            })
            .style('stroke', function(d) {
              return self.fill(d.group);
            })
            .style('stroke-linejoin', 'round')
            .style('stroke-width', 10)
            .style('opacity', 0.2);
      });
  },

  // Create a new node
  new_node: function(data, x, y) {
    var self = this;
    data = {data: data};
    if (x && y) {
      data.x = x;
      data.y = y;
    }
    self.nodes.push(data);

    var node = self.vis.selectAll('g.node').data(self.nodes);
    var nodeEnter = node.enter().append('g')
      .attr('class', 'node')
      .style('fill', 'white')
      .call(self.force.drag);

    nodeEnter.append('circle')
      .attr('class', 'frame')
      .attr('r', function(d) {
        switch (Object.getPrototypeOf(d.data)) {
          case ExternalNetwork.prototype:
            return 35;
          case Network.prototype:
            return 30;
          case Router.prototype:
            return 25;
          case Server.prototype:
            return 20;
        }
      })
      .style('fill', 'white')
      .style('stroke', 'black')
      .style('stroke-width', 3);

    switch (data.data.iconType) {
      case 'text':
        nodeEnter.append('text')
          .style('fill', 'black')
          .style('font', '20px FontAwesome')
          .attr('text-anchor', 'middle')
          .attr('dominant-baseline', 'central')
          .text(function(d) { return d.data.icon; })
          .attr('transform', function(d) {
            switch (Object.getPrototypeOf(d.data)) {
              case ExternalNetwork.prototype:
                return 'scale(2.5)';
              case Network.prototype:
                return 'scale(1.5)';
              case Server.prototype:
                return 'scale(1)';
            }
          });
        break;
      case 'path':
        nodeEnter.append('path')
          .attr('class', 'svgpath')
          .style('fill', 'black')
          .attr('d', function(d) { return self.svgs(d.data.svg); })
          .attr('transform', function() {
            return 'scale(1.2)translate(-16,-15)';
          });
        break;
    }

    nodeEnter.append('text')
      .attr('class', 'nodeLabel')
      .style('display',function() {
        var labels = horizon.cookies.get('topology_labels');
        if (labels) {
          return 'inline';
        } else {
          return 'none';
        }
      })
      .style('fill','black')
      .text(function(d) {
        return d.data.name;
      })
      .attr('transform', function(d) {
        switch (Object.getPrototypeOf(d.data)) {
          case ExternalNetwork.prototype:
            return 'translate(40,3)';
          case Network.prototype:
            return 'translate(35,3)';
          case Router.prototype:
            return 'translate(30,3)';
          case Server.prototype:
            return 'translate(25,3)';
        }
      });

    if (data.data instanceof Network || data.data instanceof ExternalNetwork) {
      nodeEnter.append('svg:text')
        .attr('class','vmCount')
        .style('fill', 'black')
        .style('font-size','20')
        .text('')
        .attr('transform', 'translate(26,38)');
    }

    nodeEnter.on('click', function(d) {
      self.show_balloon(d.data, d, angular.element(this));
    });

    // Highlight the links for currently selected node
    nodeEnter.on('mouseover', function(d) {
      self.vis.selectAll('line.link').filter(function(z) {
        if (z.source === d || z.target === d) {
          return true;
        } else {
          return false;
        }
      }).style('stroke-width', '3px');
    });

    // Remove the highlight on the links
    nodeEnter.on('mouseout', function() {
      self.vis.selectAll('line.link').style('stroke-width','1px');
    });
  },

  collapse_network: function(d, only_collapse) {
    var self = this;
    var server, vm;

    var filterNode = function(obj) {
      return function(d) {
        return obj == d.data;
      };
    };

    if (!d.data.collapsed) {
      var vmCount = 0;
      for (vm in self.data.servers) {
        if (self.data.servers[vm] !== undefined) {
          if (self.data.servers[vm].networks.length == 1) {
            if (self.data.servers[vm].networks[0].id == d.data.id) {
              vmCount += 1;
              self.removeNode(self.data.servers[vm]);
            }
          }
        }
      }
      d.data.collapsed = true;
      if (vmCount > 0) {
        self.vis.selectAll('.vmCount').filter(filterNode(d.data))[0][0].textContent = vmCount;
      }
    } else if (!only_collapse) {
      for (server in self.data.servers) {
        if ({}.hasOwnProperty.call(self.data.servers, server)) {
          var _vm = self.data.servers[server];
          if (_vm !== undefined) {
            if (_vm.networks.length === 1) {
              if (_vm.networks[0].id == d.data.id) {
                self.new_node(_vm, d.x, d.y);
                self.new_link(self.find_by_id(_vm.id), self.find_by_id(d.data.id));
                self.force.start();
              }
            }
          }
        }
      }
      d.data.collapsed = false;
      self.vis.selectAll('.vmCount').filter(filterNode(d.data))[0][0].textContent = '';
      var i = 0;
      while (i <= 100) {
        self.force.tick();
        i++;
      }
    }
  },

  new_link: function(source, target) {
    var self = this;
    self.links.push({source: source, target: target});
    var line = self.vis.selectAll('line.link').data(self.links);
    line.enter().insert('line', 'g.node')
      .attr('class', 'link')
      .attr('x1', function(d) { return d.source.x; })
      .attr('y1', function(d) { return d.source.y; })
      .attr('x2', function(d) { return d.target.x; })
      .attr('y2', function(d) { return d.target.y; })
      .style('stroke', 'black')
      .style('stroke-width', 2);
  },

  find_by_id: function(id) {
    var self = this;
    var obj, _i, _len, _ref;
    _ref = self.vis.selectAll('g.node').data();
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      obj = _ref[_i];
      if (obj.data.id == id) {
        return obj;
      }
    }
    return undefined;
  },

  already_in_graph: function(data, node) {
    // Check for gateway that may not have unique id
    if (data == this.data.ports) {
      for (var p in data) {
        if (JSON.stringify(data[p]) == JSON.stringify(node)) {
          return true;
        }
      }
      return false;
    }
    // All other node types have UUIDs
    for (var n in data) {
      if (n == node.id) {
        return true;
      }
    }
    return false;
  },

  load_topology: function(data) {
    var self = this;
    var net, _i, _netlen, _netref, rou, _j, _roulen, _rouref, port, _l, _portlen, _portref,
        ser, _k, _serlen, _serref, obj, vmCount;
    var change = false;
    var filterNode = function(obj) {
      return function(d) {
        return obj == d.data;
      };
    };

    // Networks
    _netref = data.networks;
    for (_i = 0, _netlen = _netref.length; _i < _netlen; _i++) {
      net = _netref[_i];
      var network = null;
      if (net['router:external'] === true) {
        network = new ExternalNetwork(net);
      } else {
        network = new Network(net);
      }

      if (!self.already_in_graph(self.data.networks, network)) {
        self.new_node(network);
        change = true;
      } else {
        obj = self.find_by_id(network.id);
        if (obj) {
          network.collapsed = obj.data.collapsed;
          network.instances = obj.data.instances;
          obj.data = network;
        }
      }
      self.data.networks[network.id] = network;
    }

    // Routers
    _rouref = data.routers;
    for (_j = 0, _roulen = _rouref.length; _j < _roulen; _j++) {
      rou = _rouref[_j];
      var router = new Router(rou);
      if (!self.already_in_graph(self.data.routers, router)) {
        self.new_node(router);
        change = true;
      } else {
        obj = self.find_by_id(router.id);
        if (obj) {
          // Keep networks list
          router.networks = obj.data.networks;
          // Keep ports list
          router.ports = obj.data.ports;
          obj.data = router;
        }
      }
      self.data.routers[router.id] = router;
    }

    // Servers
    _serref = data.servers;
    for (_k = 0, _serlen = _serref.length; _k < _serlen; _k++) {
      ser = _serref[_k];
      var server = new Server(ser);
      if (!self.already_in_graph(self.data.servers, server)) {
        self.new_node(server);
        change = true;
      } else {
        obj = self.find_by_id(server.id);
        if (obj) {
          // Keep networks list
          server.networks = obj.data.networks;
          // Keep ip address list
          server.ip_addresses = obj.data.ip_addresses;
          obj.data = server;
        } else if (self.data.servers[server.id] !== undefined) {
          // This is used when servers are hidden because the network is
          // collapsed
            server.networks = self.data.servers[server.id].networks;
            server.ip_addresses = self.data.servers[server.id].ip_addresses;
        }
      }
      self.data.servers[server.id] = server;
    }

    // Ports
    _portref = data.ports;
    for (_l = 0, _portlen = _portref.length; _l < _portlen; _l++) {
      port = _portref[_l];
      if (!self.already_in_graph(self.data.ports, port)) {
        var device = self.find_by_id(port.device_id);
        var _network = self.find_by_id(port.network_id);
        if (angular.isDefined(device) && angular.isDefined(_network)) {
          if (port.device_owner == 'compute:nova' || port.device_owner == 'compute:None') {
            _network.data.instances++;
            device.data.networks.push(_network.data);
            if (port.fixed_ips) {
              for(var ip in port.fixed_ips) {
                if (!listContains(port.fixed_ips[ip], device.data.ip_addresses)) {
                  device.data.ip_addresses.push(port.fixed_ips[ip]);
                }
              }
            }
            // Remove the recently added node if connected to a network which is
            // currently collapsed
            if (_network.data.collapsed) {
              if (device.data.networks.length == 1) {
                self.data.servers[device.data.id].networks = device.data.networks;
                self.data.servers[device.data.id].ip_addresses = device.data.ip_addresses;
                self.removeNode(self.data.servers[port.device_id]);
                vmCount = Number(self.vis.selectAll('.vmCount').filter(filterNode(_network.data))[0][0].textContent);
                self.vis.selectAll('.vmCount').filter(filterNode(_network.data))[0][0].textContent = vmCount + 1;
                continue;
              }
            }
          } else if (port.device_owner == 'network:router_interface') {
            device.data.networks.push(_network.data);
            device.data.ports.push(port);
          } else if (device.data.ports) {
            device.data.ports.push(port);
          }
          self.new_link(self.find_by_id(port.device_id), self.find_by_id(port.network_id));
          change = true;
        } else if (angular.isDefined(_network) && port.device_owner == 'compute:nova') {
          // Need to add a previously hidden node to the graph because it is
          // connected to more than 1 network
          if (_network.data.collapsed) {
            server = self.data.servers[port.device_id];
            server.networks.push(_network.data);
            if (port.fixed_ips) {
              for(var ip in port.fixed_ips) {
                server.ip_addresses.push(port.fixed_ips[ip]);
              }
            }
            self.new_node(server);
            // decrease collapsed vm count on network
            vmCount = Number(self.vis.selectAll('.vmCount').filter(filterNode(server.networks[0]))[0][0].textContent);
            if (vmCount == 1) {
              self.vis.selectAll('.vmCount').filter(filterNode(server.networks[0]))[0][0].textContent = '';
            } else {
              self.vis.selectAll('.vmCount').filter(filterNode(server.networks[0]))[0][0].textContent = vmCount - 1;
            }
            // Add back in first network link
            self.new_link(self.find_by_id(port.device_id), self.find_by_id(server.networks[0].id));
            // Add new link
            self.new_link(self.find_by_id(port.device_id), self.find_by_id(port.network_id));
            change = true;
          }
        }
      }
      self.data.ports[port.id+port.device_id+port.network_id] = port;
    }
    if (change) {
        self.force.start();
    }
    self.load_config();
    self.$loading_template.hide();
  },

  removeNode: function(obj) {
    var filterNetwork, filterNode, n, node, _i, _len, _ref;
    _ref = this.nodes;
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      n = _ref[_i];
      if (n.data === obj) {
        node = n;
        break;
      }
    }
    if (node) {
      this.nodes.splice(this.nodes.indexOf(node), 1);
      filterNode = function(obj) {
        return function(d) {
          return obj === d.data;
        };
      };
      filterNetwork = function(obj) {
        return function(d) {
          return obj === d.network.data;
        };
      };
      if (obj instanceof Network) {
        this.vis.selectAll('.hulls').filter(filterNetwork(obj)).remove();
      }
      this.vis.selectAll('g.node').filter(filterNode(obj)).remove();
      return this.removeNodesLinks(obj);
    }
  },

  removeNodesLinks: function(node) {
    var l, linksToRemove, _i, _j, _len, _len1, _ref;
    linksToRemove = [];
    _ref = this.links;
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      l = _ref[_i];
      if (l.source.data === node) {
        linksToRemove.push(l);
      } else if (l.target.data === node) {
        linksToRemove.push(l);
      }
    }
    for (_j = 0, _len1 = linksToRemove.length; _j < _len1; _j++) {
      l = linksToRemove[_j];
      this.removeLink(l);
    }
    return this.force.resume();
  },

  removeLink: function(link) {
    var i, index, l, _i, _len, _ref;
    index = -1;
    _ref = this.links;
    for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
      l = _ref[i];
      if (l === link) {
        index = i;
        break;
      }
    }
    if (index !== -1) {
      this.links.splice(index, 1);
    }
    return this.vis.selectAll('line.link').data(this.links).exit().remove();
  },

  delete_device: function(device_type, deviceId) {
    var message = {id:deviceId};
    var target = device_type === 'instance' ? 'instance?id=' + deviceId : device_type;
    horizon.networktopologymessager.post_message(deviceId, target, message, device_type, 'delete', data={});
  },

  remove_node_on_delete: function(deleteData) {
    var self = this;
    var deviceId = deleteData.device_id;
    switch (deleteData.device_type) {
      case 'router':
        self.removeNode(self.data.routers[deviceId]);
        break;
      case 'instance':
        self.removeNode(self.data.servers[deviceId]);
        this.data.servers[deviceId] = undefined;
        break;
      case 'network':
        self.removeNode(self.data.networks[deviceId]);
        break;
      case 'port':
        self.removePortOrSubnet(deviceId, deleteData.device_data);
        break;
    }
    self.delete_balloon();
  },

  removePortOrSubnet: function(portId, deviceData) {
    var self = this;
    var routerId = deviceData.router_id;
    var networkId = deviceData.network_id;
    if (routerId) {
      for (var l in self.links) {
        var data = null;
        if(self.links[l].source.data.id == routerId && self.links[l].target.data.id == networkId) {
          data = self.links[l].source.data;
        } else if (self.links[l].target.data.id == routerId && self.links[l].source.data.id == networkId) {
          data = self.links[l].target.data;
        }
        if (data) {
          for (var p in data.ports) {
            if ((data.ports[p].id == portId) && (data.ports[p].network_id == networkId)) {
              self.removeLink(self.links[l]);
              // Update Router to remove deleted port
              var router = self.find_by_id(routerId);
              router.data.ports.splice(router.data.ports.indexOf(data.ports[p]), 1);
              self.force.start();
              return;
            }
          }
        }
      }
    } else {
      var networkData = self.find_by_id(networkId).data;
      var subnets = networkData.subnets;
      for (var subnet in subnets) {
        if (subnets[subnet].id === portId) {
          if (subnets.length == 1) {
            delete(networkData.subnets);
          } else {
            subnets.splice(subnet, 1);
          }
          self.force.start();
          break;
        }
      }
    }
  },

  delete_port: function(routerId, portId, networkId) {
    var message = {id:portId};
    var data = {network_id:networkId,router_id:routerId};
    if (routerId) {
      horizon.networktopologymessager.post_message(portId, 'router/' + routerId + '/', message, 'port', 'delete', data);
    } else {
      horizon.networktopologymessager.post_message(portId, 'network/' + networkId + '/?tab=network_tabs__subnets_tab', message, 'port', 'delete', data);
    }
  },

  show_balloon: function(d,d2,element) {
    var self = this;
    var balloonTmpl = self.balloonTmpl;
    var deviceTmpl = self.balloon_deviceTmpl;
    var portTmpl = self.balloon_portTmpl;
    var netTmpl = self.balloon_netTmpl;
    var instanceTmpl = self.balloon_instanceTmpl;
    var balloonID = 'bl_' + d.id;
    var ports = [];
    var subnets = [];
    if (self.balloonID) {
      if (self.balloonID == balloonID) {
        self.delete_balloon();
        return;
      }
      self.delete_balloon();
    }
    self.force.stop();
    if (d.hasOwnProperty('ports')) {
      angular.element.each(d.ports, function(i, port) {
        var object = {};
        object.id = port.id;
        object.router_id = port.device_id;
        object.url = port.url;
        object.port_status = port.status;
        object.port_status_class = (port.original_status === 'ACTIVE') ? 'active' : 'down';
        var ipAddress = '';
        try {
          for (var ip in port.fixed_ips) {
            ipAddress += port.fixed_ips[ip].ip_address + ' ';
          }
        }catch(e) {
          ipAddress = gettext('None');
        }
        var deviceOwner = '';
        try {
          deviceOwner = port.device_owner.replace('network:','');
        }catch(e) {
          deviceOwner = gettext('None');
        }
        var networkId = '';
        try {
          networkId = port.network_id;
        }catch(e) {
          networkId = gettext('None');
        }
        object.ip_address = ipAddress;
        object.device_owner = deviceOwner;
        object.network_id = networkId;
        object.is_interface = (
          deviceOwner === 'router_interface' ||
          deviceOwner === 'router_gateway' ||
          deviceOwner === 'ha_router_replicated_interface'
        );
        ports.push(object);
      });
    } else if (d.hasOwnProperty('subnets')) {
      angular.element.each(d.subnets, function(i, snet) {
        var object = {};
        object.id = snet.id;
        object.cidr = snet.cidr;
        object.url = snet.url;
        subnets.push(object);
      });
    }
    var htmlData = {
      balloon_id:balloonID,
      id:d.id,
      url:d.url,
      name:d.name,
      type:d.type,
      delete_label: gettext('Delete'),
      status:d.status,
      status_class: (d.original_status === 'ACTIVE') ? 'active' : 'down',
      status_label: gettext('STATUS'),
      id_label: gettext('ID'),
      interfaces_label: gettext('Interfaces'),
      subnets_label: gettext('Subnets'),
      delete_interface_label: gettext('Delete Interface'),
      delete_subnet_label: gettext('Delete Subnet'),
      open_console_label: gettext('Open Console'),
      view_details_label: gettext('View Details'),
      ips_label: gettext('IP Addresses')
    };
    var html;
    if (d instanceof Router) {
      htmlData.delete_label = gettext('Delete Router');
      htmlData.view_details_label = gettext('View Router Details');
      htmlData.port = ports;
      htmlData.add_interface_url = 'router/' + d.id + '/addinterface';
      htmlData.add_interface_label = gettext('Add Interface');
      html = balloonTmpl.render(htmlData,{
        table1:deviceTmpl,
        table2:portTmpl
      });
    } else if (d instanceof Server) {
      htmlData.delete_label = gettext('Delete Instance');
      htmlData.view_details_label = gettext('View Instance Details');
      htmlData.console_id = d.id;
      htmlData.ips = d.ip_addresses;
      htmlData.console = d.console;
      html = balloonTmpl.render(htmlData,{
        table1:deviceTmpl,
        table2:instanceTmpl
      });
    } else if (d instanceof Network || d instanceof ExternalNetwork) {
      for (var s in subnets) {
        subnets[s].network_id = d.id;
      }
      htmlData.subnet = subnets;
      if (d instanceof Network) {
        htmlData.delete_label = gettext('Delete Network');
      }
      htmlData.add_subnet_url = 'network/' + d.id + '/subnet/create';
      htmlData.add_subnet_label = gettext('Create Subnet');
      html = balloonTmpl.render(htmlData,{
        table1:deviceTmpl,
        table2:netTmpl
      });
    } else {
      return;
    }
    angular.element(self.svg_container).append(html);
    var devicePosition = self.getScreenCoords(d2.x, d2.y);
    var x = devicePosition.x;
    var y = devicePosition.y;
    var xoffset = 100;
    var yoffset = 95;
    angular.element('#' + balloonID).css({
      'left': x + xoffset + 'px',
      'top': y + yoffset + 'px'
    }).show();
    var _balloon = angular.element('#' + balloonID);
    if (element.x + _balloon.outerWidth() > angular.element(window).outerWidth()) {
      _balloon
        .css({
          'left': 0 + 'px'
        })
        .css({
          'left': (x - _balloon.outerWidth() + 'px')
        })
        .addClass('leftPosition');
    }
    _balloon.find('.delete-device').click(function() {
      var _this = angular.element(this);
      var delete_modal = horizon.datatables.confirm(_this);
      delete_modal.find('.btn.btn-danger').click(function () {
        _this.prop('disabled', true);
        d3.select('#id_' + _this.data('device-id')).classed('loading',true);
        self.delete_device(_this.data('type'),_this.data('device-id'));
      });
    });
    _balloon.find('.delete-port').click(function() {
      var _this = angular.element(this);
      var delete_modal = horizon.datatables.confirm(_this);
      delete_modal.find('.btn.btn-danger').click(function () {
        _this.prop('disabled', true);
        self.delete_port(_this.data('router-id'),_this.data('port-id'),_this.data('network-id'));
      });
    });
    self.balloonID = balloonID;
  },

  delete_balloon:function() {
    var self = this;
    if (self.balloonID) {
      angular.element('#' + self.balloonID).remove();
      self.balloonID = null;
      self.force.start();
    }
  },

  svgs: function(name) {
    switch (name) {
      case 'router':
        return 'm 26.628571,16.08 -8.548572,0 0,8.548571 2.08,-2.079998 6.308572,6.30857 4.38857,-4.388572 -6.308571,-6.30857 z m -21.2571429,-4.159999 8.5485709,0 0,-8.5485723 -2.08,2.08 L 5.5314281,-0.85714307 1.1428571,3.5314287 7.4514281,9.84 z m -3.108571,7.268571 0,8.548571 8.5485709,0 L 8.7314281,25.657144 15.039999,19.325715 10.674285,14.96 4.3428571,21.268573 z M 29.737142,8.8114288 l 0,-8.54857147 -8.548572,0 2.08,2.07999987 -6.308571,6.3085716 4.388572,4.3885722 6.308571,-6.3085723 z';
      default:
        return '';
    }
  }
};
