text: angular
      .module('horizon.dashboard.project.vitrage')
      .directive('hzEntitiesGraph', hzEntitiesGraph);
  
  function hzEntitiesGraph() {
      var directive = {
          link: link,
          scope: {
              data: '=',
              selected: '=',
              itemSelected: '&',
              search: '=',
              searchItem: '='
          },
          templateUrl: STATIC_URL + 'dashboard/project/entities/graph/entities-graph.html',
          restrict: 'E'
      };
      return directive;
  
      function link(scope, element, attrs) {
  
  
          var minZoom = 0.3,
              maxZoom = 4,
              linkWidth = 1,
              circleRadius = 14,
              circlePadding = 1,
              zoom = d3.behavior.zoom().scaleExtent([minZoom, maxZoom]),
              ellipsisWidth = 80,
              hightlightDepth = 2,
              heightOffset,
              pinned,
              graphCreated,
              node,
              link,
              linksMap,
              content;
  
          (function() {
              var p = $('.panel.panel-primary');
              heightOffset = (p.length ? p.offset().top : 180) + 75;
  
              pinned = horizon.cookies.get('pinned') || [];
              if (_.isString(pinned)) {
                  try {
                      pinned = JSON.parse(pinned);
                  }
                  catch(ex) {
                      pinned = [];
                      console.error('Failed to parse the pinned cookie');
                  }
               }
          })();
  
          scope.$watch('searchItem', function(newData, oldData) {
           if (newData != oldData) {
               console.log('searching for node: ', newData);
               searchNode(newData);
           }
          });
  
          function searchNode(value) {
              value = value.toLowerCase();
              var allNodes = d3.select("svg g").selectAll("g")
                .filter(function(d, i){ return this.classList.contains("node"); })
                .selectAll("circle");
  
              //console.log('all nodes: ', allNodes.length);
  
              allNodes
                .transition()
                .duration(200)
                .style("stroke", "darkgray")
                .style("stroke-width", "1")
                .style("fill", "#FFFFFF")
                .attr('r', function(item) { return 14; });
  
              // Set special style to the found node
              if (value && value !== '') {
                  var theNodes = d3.select("svg g").selectAll("g")
                    .filter(function(d, i){ return this.classList.contains("node"); })
                    .filter(function(d, i){ return d.name ? d.name.toLowerCase().indexOf(value) > -1 : ''; })
                    .selectAll("circle");
  
                  theNodes
                    .transition()
                    .duration(400)
                    .style("stroke", "#006600")
                    .style("stroke-width", "3")
                    .style("fill", "#51EFD2")
                    .attr('r', function(item) { return 40; });
              }
  
          }
  
          scope.$watch('data.ts', function(newVal, oldVal) {
              if (newVal) {
                  prepareData();
  
                  if (!graphCreated) {
                      createGraph();
                  } else {
                      drawGraph();
                  }
              }
          });
  
          scope.$on('toolbox-pin', function () {
              pinAll();
          });
  
          scope.$on('toolbox-unpin', function () {
              unpinAll();
          })
  
          scope.$on('toolbox-zoom-to-fit', function () {
              console.log('on toolbox-pin', arguments)
          });
  
          scope.$on('toolbox-toggle-fullscreen', function () {
              console.log('on toolbox-unpin', arguments)
          })
  
          scope.isEmpty = function() {
            return scope.data && scope.data.nodes && scope.data.nodes.length === 0;
          };
  
          var svg = d3.select(element[0]).select('svg')
              .style('cursor', 'move')
              .attr('width', '100%')
              .attr('pointer-events', 'all');
              //.on('click', selectNone);
  
          svg.call(zoom);
  
          var svg_g = svg.append('g')
              .attr('width', '100%')
              .attr('height', '100%')
  
          link = svg_g.selectAll('.link');
  
          node = svg_g.selectAll('.node');
  
          var force = d3.layout.force()
              .gravity(0.15)
              //.distance(200)
              .charge(-1000)
              //.friction(0.8)
              .linkDistance(function(d) {
                  if (d.relationship_type === 'on') {
                      return 80;
                  }
                  return 120;
              })
              .linkStrength(function(d) {
                  if (d.relationship_type === 'on') {
                      return 2;
                  }
                  return 0.5;
              });
  
          var drag = force.drag()
              .on('dragend', nodeDragend);
              //.on('dragstart', nodeDragstart);
  
          resize();
          d3.select(window).on('resize', resize);
  
          function resize() {
              svg.attr('height', window.innerHeight - heightOffset + 'px')
              force.size([angular.element(svg[0]).width(),
                  angular.element(svg[0]).height()])
                  .resume();
          }
  
          function prepareData() {
              _.each(pinned, function(pin) {
                  var node = _.find(scope.data.nodes, function(node) {
                      return pin.id === node.id;
                  });
  
                  if (node) {
                      node.fixed = true;
                      node.x = pin.x;
                      node.y = pin.y;
                  }
              })
  
              linksMap = {};
              _.each(scope.data.links, function(link) {
                  linksMap[link.source.id + ',' + link.target.id] = true;
              });
          }
  
          function createGraph() {
  
              graphCreated = true;
  
              function collide(node) {
                  return function(quad, x1, y1, x2, y2) {
                      var updated = false;
                      if (quad.point && (quad.point !== node)) {
  
                          var x = node.x - quad.point.x,
                              y = node.y - quad.point.y,
                              xSpacing = (quad.point.width + node.width) / 2,
                              ySpacing = (quad.point.height + node.height) / 2,
                              absX = Math.abs(x),
                              absY = Math.abs(y),
                              l,
                              lx,
                              ly;
  
                          if (absX < xSpacing && absY < ySpacing) {
                              l = Math.sqrt(x * x + y * y);
  
                              lx = (absX - xSpacing) / l;
                              ly = (absY - ySpacing) / l;
  
                              // the one that's barely within the bounds probably triggered the collision
                              if (Math.abs(lx) > Math.abs(ly)) {
                                  lx = 0;
                              } else {
                                  ly = 0;
                              }
  
                              node.x -= x *= lx;
                              node.y -= y *= ly;
                              quad.point.x += x;
                              quad.point.y += y;
  
                              updated = true;
                          }
                      }
                      return updated;
                  };
              }
  
              /*function collide(alpha) {
                  var quadtree = d3.geom.quadtree(scope.data.nodes);
                  return function(d) {
                      var rb = 2 * circleRadius + circlePadding,
                          nx1 = d.x - rb,
                          nx2 = d.x + rb,
                          ny1 = d.y - rb,
                          ny2 = d.y + rb;
                      quadtree.visit(function(quad, x1, y1, x2, y2) {
                          if (quad.point && (quad.point !== d)) {
                              var x = d.x - quad.point.x,
                                  y = d.y - quad.point.y,
                                  l = Math.sqrt(x * x + y * y);
                              if (l < rb) {
                                  l = (l - rb) / l * alpha;
                                  d.x -= x *= l;
                                  d.y -= y *= l;
                                  quad.point.x += x;
                                  quad.point.y += y;
                              }
                          }
                          return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
                      });
                  };
              }*/
  
              force.nodes(scope.data.nodes)
                  .links(scope.data.links);
  
              force.on('tick', function() {
                  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; });
  
                  node.attr('transform', function(d) {
                      return 'translate(' + d.x + ',' + d.y + ')';
                  });
  
                  //node.each(collide(0.5));
  
                  var nodes = scope.data.nodes;
  
                  var q = d3.geom.quadtree(nodes),
                      i = 0,
                      n = nodes.length;
                  while (++i < n) {
                      q.visit(collide(nodes[i]));
                  }
              });
  
              zoom.on('zoom', function() {
                  var strokeWidth = linkWidth / zoom.scale();
                  if (strokeWidth > linkWidth) {
                      strokeWidth = linkWidth;
                  }
  
                  link.style('stroke-width', strokeWidth);
  
                  var scale = 1 / (zoom.scale() / 1.2);
                  if (scale > 1) {
                      scale = 1;
                  }
                  //content.attr('transform', 'scale(' + scale + ')');
                  svg_g.selectAll('.node-content').attr('transform', 'scale(' + scale + ')');
  
                  svg_g.attr('transform', 'translate(' + d3.event.translate + ')scale(' + d3.event.scale + ')');
  
              });
  
              drawGraph();
          }
  
          window.drawGraph = drawGraph;
          window.dforce = force;
  
          function drawGraph() {
              link = link.data(force.links(), function(d) { return d.source.id + '-' + d.target.id; });
              link
                  .enter().append('line')
                  .attr('class', 'link')
              link.exit().remove();
  
              node = node.data(force.nodes(), function(d) { return d.id;});
              content = node
                  .enter().append('g')
                  .attr('class', 'node')
                  .attr('name', function(d) {
                      return d.name;
                  })
                  .classed('pinned', function(d) { return d.fixed; })
                  .call(force.drag)
                  .on('click', nodeClick)
                  .on('mousedown', function(d) { d3.event.stopPropagation() })
                  .on('dblclick', pinNode)
                  .call(drag)
                  .append('g')
                  .classed('node-content', true);
  
  
              //Only for updates
              /*content
                  .attr('transform', 'scale(0)')
                  .transition(750)
                  .attr('transform', 'scale(1)');*/
  
              node.exit()
                  .select('.node-content')
                  .transition()
                  .duration(750)
                  .attr('transform', 'scale(0)')
  
              node.exit()
                  .transition()
                  .duration(750)
                  .remove();
  
              content.append('circle')
                  .attr('r', circleRadius + 'px');
  
              content.append('text')
                  .attr('text-anchor', 'middle')
                  .attr('dominant-baseline', 'central')
                  .attr('transform', 'scale(1)')
                  .attr('class', function(d) {
                      var category = d.category,
                          cls = '';
  
                      if (category && category.toLowerCase() === 'alarm') {
                          var severity = d.vitrage_operational_severity;
                          if (severity) {
                              switch (severity.toLowerCase()) {
                                  case 'critical':
                                      cls = 'red';
                                      break;
                                  case 'severe':
                                      cls = 'orange';
                                      break;
                                  case 'warning':
                                      cls = 'yellow';
                                      break;
                                  case 'ok':
                                      cls = 'green';
                                      break;
                                  case 'n/a':
                                      cls = 'gray';
                                      break;
                                  default: //'DISABLED', 'UNKNOWN', 'UNDEFINED'
                                      cls = 'gray';
                                      break;
                              }
                          }
                      } else {
                          var state = d.vitrage_operational_state;
                          if (state) {
                              switch (state.toLowerCase()) {
                                  case 'error':
                                      cls = 'red';
                                      break;
                                  case 'suboptimal':
                                      cls = 'yellow';
                                      break;
                                  case 'n/a':
                                      cls = 'gray';
                                      break;
                              }
                          }
                      }
                      return cls;
                  })
                  .style('font-size', function(d) {
                      var category = d.category || 'no_category',
                          icon_size;
  
                      if (category && category.toLowerCase() === 'alarm') {
                          icon_size = '18px';
                      } else {
                          var type = d.vitrage_type || 'no_type';
                          switch(type.toLowerCase()) {
                              case 'nova.instance':
                              case 'nova.host':
                              case 'nova.zone':
                              case 'neutron.port':
                                  icon_size = '16px'; //fa-external-link-square
                                  break;
                              case 'openstack.cluster':
                                  icon_size = '18px'; //fa-cloud
                                  break;
                              case 'cinder.volume':
                                  icon_size = '22px';
                                  break;
                              case 'neutron.network':
                              default:
                                  icon_size = '20px';
                                  break;
                          }
                      }
  
                      return icon_size;
                  })
                  .style('stroke', function(d) {
                      var category = d.category;
                      if (category && category.toLowerCase() === 'alarm') {
                          return '18px'
                      }
                      return '20px'
                  })
                  .classed('icon', true)
                  .classed('fill-only', function(d) {
                      var type = (d.vitrage_type || '').toLowerCase();
                      if (type && type === 'nova.host' || type === 'cinder.volume') {
                          return true;
                      }
                  })
                  .text(function(d) {
                      var category = d.category,
                          icon;
  
                      if (category && category.toLowerCase() === 'alarm') {
                          icon = '\uf0f3'; //\uf0a2'; //bell-o
                      } else {
                          var type = d.vitrage_type || 'no_type';
                          switch(type.toLowerCase()) {
                              case 'nova.instance':
                                  icon = '\uf108'; //fa-desktop
                                  break;
                              case 'nova.host':
                                  icon = '\uf233'; //fa-server
                                  break;
                              case 'nova.zone':
                                  icon = '\uf279'; //fa-map
                                  break;
                              case 'neutron.network':
                                  icon = '\uf0ac'; //fa-globe
                                  break;
                              case 'neutron.port':
                                  icon = '\uf14c'; //fa-external-link-square
                                  break;
                              case 'cinder.volume':
                                  icon = '\uf0a0'; //fa-hdd-o
                                  break;
                              case 'openstack.cluster':
                                  icon = '\uf0c2'; //fa-cloud
                                  break;
                              default:
                                  icon = '\uf013'; //fa-cog
                                  break;
                          }
                      }
                      return icon
                  });
  
              content.append('text')
                  .classed('pin', true)
                  .attr('dx', '-18px')
                  .attr('dy', '-12px')
                  .text('\uf08d')
                  .on('click', pinNode)
  
              var textNode = content.append('text')
                  .classed('.label', true)
                  .attr('dx', 18)
                  .attr('dy', '.35em')
                  .text(function(d) {
                      return d.name;
                  })
                  .call(function(textNodes) {
                      textNodes.each(function(d) {
                          if (d.name) {
                              setEllipsis(this, d.name, ellipsisWidth);
                              d.bbox = this.getBBox();
                          }
                          d.width = 2 * (circleRadius + circlePadding) + (d.bbox ? d.bbox.width * 2 : 0);
                          d.height = 2 * (circleRadius + circlePadding);
                      })
                  })
                  .append('title')
                  .text(function(d) { return d.name; });
  
              content.insert('rect', 'text.pin')
                  .attr('width', function(d) {
                      return d.bbox ? d.bbox.width + 4: 0;
                  })
                  .attr('height', function(d) {
                      return d.bbox ? d.bbox.height + 2 : 0;
                  })
                  .attr('x', 16)
                  .attr('y', -8)
                  .attr('rx', 4)
                  .attr('ry', 4)
                  .classed('text-bg', true);
  
              force.start();
          }
  
          function nodeClick(d) {
              scope.selected = d;
              //scope.itemSelected(scope.selected);
  
              d3.event.stopImmediatePropagation();
              d3.event.preventDefault();
  
              scope.$emit('graphItemClicked', d);
  
              svg_g.selectAll('.node')
                  .classed('selected', false);
  
  
              if ($(this).is('.node')) {
                  //d3.select(this).classed('selected', true);
  
                  findHighlight(d);
              }
          }
  
          function findHighlight(rootNode) {
  
              _.each(scope.data.nodes, function(node) {
                  node.high = false;
              })
  
              var depth = hightlightDepth;
  
              findNodes(rootNode, depth, scope.data.nodes, linksMap);
  
              _.each(scope.data.links, function(link) {
                  link.high = false;
              })
  
              svg_g.selectAll('.node')
                  .classed('selected', function(d) {
                      return d.high;
                  })
                  .select('circle')
                  .style('stroke-width', function(d) {
                      return d.high ? (Math.max(d.highDepth + 1, 1) * 2) : null;
                  })
  
              svg_g.selectAll('.link').classed('selected', function(d) {
                  return d.source.high && d.target.high;
              })
          }
  
          function selectNone(d) {
              nodeClick(null);
          }
  
          function pinNode(d) {
              d3.event.stopImmediatePropagation();
              d3.event.preventDefault();
  
              var node;
  
              if ($(this).is('.node')) {
                  node = d3.select(this);
              } else if ($(this).is('.pin')) {
                  node = d3.select(this.parentNode.parentNode);
              }
  
              if (node) {
                  node.classed('pinned', d.fixed = (d.fixed ? false : true));
  
                  updatePinnedCookie(d);
              }
  
              //fixing some bug with unpinning
              /*setTimeout(function() {
                  force.resume()
              }, 100)*/
          }
  
          function updatePinnedCookie(d) {
              var pinIndex = -1;
              pinned.forEach(function(pin, i) {
                  if (pin.id === d.id) {
                      pinIndex = i;
                  }
              })
  
              if (pinIndex > -1) {
                  pinned.splice(pinIndex, 1);
              }
  
              if (d.fixed) {
                  pinned.push({id: d.id, x: d.x, y: d.y});
              }
  
              horizon.cookies.put('pinned', JSON.stringify(pinned));
          }
  
          function nodeDragend(d) {
              if (d.fixed) {
                  updatePinnedCookie(d);
              }
          }
  
          function pinAll() {
              pinned = [];
  
              svg_g.selectAll('.node')
                  .classed('pinned', true)
                  .each(function(d) {
                      d.fixed = true;
                      pinned.push({id: d.id, x: d.x, y: d.y});
                  })
  
              horizon.cookies.put('pinned', JSON.stringify(pinned));
          }
  
          function unpinAll() {
              pinned = [];
  
              svg_g.selectAll('.node')
                  .classed('pinned', false)
                  .each(function(d) {
                      d.fixed = false;
                  })
  
              horizon.cookies.put('pinned', JSON.stringify([]));
  
              setTimeout(function() {
                  force.resume()
              }, 100)
          }
  
          function pinAllNodes(isPin) {
  
          }
  
          function setEllipsis(el, text, width) {
  
              el.textContent = text;
  
              if (el.getSubStringLength(0, text.length) >= width) {
  
                  for (var x = text.length - 3; x > 0; x -= 3){
  
                      if (el.getSubStringLength(0, x) <= width){
  
                          el.textContent = text.substring(0, x) + '...';
                          return;
                      }
                  }
  
                  el.textContent = '...';
              }
          };
  
          function findNodes(rootNode, depth, allNodes, linksMap) {
              if (rootNode) {
                  rootNode.high = true;
                  rootNode.highDepth = depth;
                  depth--;
  
                  _.each(allNodes, function(node) {
                      if (linksMap[node.id + ',' + rootNode.id] || linksMap[rootNode.id + ',' + node.id]) {
  
                          if (depth > -1 && !node.high) {
                              findNodes(node, depth, allNodes, linksMap);
                          } else if (depth <= -1) {
                              //Always find 'depth' + alarms & (sdns + alarms)
                              if (node.category.toLowerCase() === 'alarm') {
                                  node.high = true;
                                  node.highDepth = 0;
                              } else if (!node.high && node.vitrage_type && node.vitrage_type.toLowerCase() === 'sdn_controller') {
                                  findNodes(node, depth, allNodes, linksMap);
                              }
                          }
                      }
                  });
              }
          }
  
          /*function nodeDragstart(d) {
              d3.select(this).classed('pinned', d.fixed = true);
          }*/
  
      }
  }
  