/**
 *
 * HeatTop JS Framework
 * Dependencies: jQuery 1.7.1 or later, d3 v3 or later
 * Date: June 2013
 * Description: JS Framework that subclasses the D3 Force Directed Graph library to create
 * Heat-specific objects and relationships with the purpose of displaying
 * Stacks, Resources, and related Properties in a Resource Topology Graph.
 *
 * 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.
 */

var container = "#heat_resource_topology";

/* NOTE: The structure/architecture of this file (lots of global variables
 *       being referenced within stand-alone functions) means that
 *       "no-use-before-define" eslint warnings will be rampant.
 *       I don't believe there's any plan to overhaul this file, so let's not
 *       clutter up our eslint output with this file's singluar-but-repeated issue.
 */
/* eslint-disable no-use-before-define */

function update(){
  node = node.data(nodes, function(d) { return d.name; });
  link = link.data(links);

  var nodeEnter = node.enter().append("g")
    .attr("class", "node")
    .attr("node_name", function(d) { return d.name; })
    .attr("node_id", function(d) { return d.instance; })
    .call(force.drag);

  nodeEnter.append("image")
    .attr("xlink:href", function(d) { return d.image; })
    .attr("id", function(d){ return "image_"+ d.name; })
    .attr("x", function(d) { return d.image_x; })
    .attr("y", function(d) { return d.image_y; })
    .attr("width", function(d) { return d.image_size; })
    .attr("height", function(d) { return d.image_size; });
  node.exit().remove();

  link.enter().insert("svg:line", "g.node")
    .attr("class", "link")
    .style("stroke-width", function(d) { return Math.sqrt(d.value); });
  link.exit().remove();
  //Setup click action for all nodes
  node.on("mouseover", function(d) {
    $("#info_box").html(d.info_box);
  });
  node.on("mouseout", function() {
    $("#info_box").html('');
  });

  force.start();
}

function tick() {
  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 + ")"; });
}

function set_in_progress(stack, nodes) {
  if (stack.in_progress === true) { in_progress = true; }
  for (var i = 0; i < nodes.length; i++) {
    var d = nodes[i];
    if (d.in_progress === true){ in_progress = true; return false; }
  }
}

function findNode(name) {
  for (var i = 0; i < nodes.length; i++) {
    if (nodes[i].name === name){ return nodes[i]; }
  }
}

function findNodeIndex(name) {
  for (var i = 0; i < nodes.length; i++) {
    if (nodes[i].name === name){ return i; }
  }
}

function addNode (node) {
  nodes.push(node);
  needs_update = true;
}

function removeNode (name) {
  var i = 0;
  var n = findNode(name);
  while (i < links.length) {
    if (links[i].source === n || links[i].target === n) {
      links.splice(i, 1);
    } else {
      i++;
    }
  }
  nodes.splice(findNodeIndex(name),1);
  needs_update = true;
}

function remove_nodes(old_nodes, new_nodes){
  //Check for removed nodes
  for (var i=0;i<old_nodes.length;i++) {
    var remove_node = true;
    for (var j=0;j<new_nodes.length;j++) {
      if (old_nodes[i].name === new_nodes[j].name){
        remove_node = false;
        break;
      }
    }
    if (remove_node === true){
      removeNode(old_nodes[i].name);
    }
  }
}

function build_links(){
  for (var i=0;i<nodes.length;i++){
    build_node_links(nodes[i]);
    build_reverse_links(nodes[i]);
  }
}

function build_node_links(node){
  for (var j=0;j<node.required_by.length;j++){
    var push_link = true;
    var target_idx = '';
    var source_idx = findNodeIndex(node.name);
    //make sure target node exists
    try {
      target_idx = findNodeIndex(node.required_by[j]);
    } catch(err) {
      push_link =false;
    }
    //check for duplicates
    for (var lidx=0;lidx<links.length;lidx++) {
      if (links[lidx].source === source_idx && links[lidx].target === target_idx) {
        push_link=false;
        break;
      }
    }

    if (push_link === true && (source_idx && target_idx)){
      links.push({
        'source':source_idx,
        'target':target_idx,
        'value':1
      });
    }
  }
}

function build_reverse_links(node){
  for (var i=0;i<nodes.length;i++){
    if(nodes[i].required_by){
      for (var j=0;j<nodes[i].required_by.length;j++){
        var dependency = nodes[i].required_by[j];
        //if new node is required by existing node, push new link
        if(node.name === dependency){
          links.push({
            'source':findNodeIndex(nodes[i].name),
            'target':findNodeIndex(node.name),
            'value':1
          });
        }
      }
    }
  }
}

function ajax_poll(poll_time){
  setTimeout(function() {
    $.getJSON(ajax_url, function(json) {
      //update d3 data element
      $("#d3_data").attr("data-d3_data", JSON.stringify(json));

      //update stack
      $("#stack_box").html(json.stack.info_box);
      set_in_progress(json.stack, json.nodes);
      needs_update = false;

      //Check Remove nodes
      remove_nodes(nodes, json.nodes);

      //Check for updates and new nodes
      json.nodes.forEach(function(d){
        var current_node = findNode(d.name);
        //Check if node already exists
        if (current_node) {
          //Node already exists, just update it
          current_node.status = d.status;

          //Status has changed, image should be updated
          if (current_node.image !== d.image){
            current_node.image = d.image;
            var this_image = d3.select("#image_"+current_node.name);
            this_image
              .transition()
              .attr("x", function(d) { return d.image_x + 5; })
              .duration(100)
              .transition()
              .attr("x", function(d) { return d.image_x - 5; })
              .duration(100)
              .transition()
              .attr("x", function(d) { return d.image_x + 5; })
              .duration(100)
              .transition()
              .attr("x", function(d) { return d.image_x - 5; })
              .duration(100)
              .transition()
              .attr("xlink:href", d.image)
              .transition()
              .attr("x", function(d) { return d.image_x; })
              .duration(100)
              .ease("bounce");
          }

          //Status has changed, update info_box
          current_node.info_box = d.info_box;

        } else {
          addNode(d);
          build_links();
        }
      });

      //if any updates needed, do update now
      if (needs_update === true){
        update();
      }
    });
    //if no nodes still in progress, slow AJAX polling
    if (in_progress === false) { poll_time = 30000; }
    else { poll_time = 3000; }
    ajax_poll(poll_time);
  }, poll_time);
}

/* eslint-disable no-use-before-define */

if ($(container).length){
  var width = $(container).width(),
    height = 500,
    stack_id = $("#stack_id").data("stack_id"),
    ajax_url = '/project/stacks/get_d3_data/' + stack_id + '/',
    graph = $("#d3_data").data("d3_data"),
    force = d3.layout.force()
      .nodes(graph.nodes)
      .links([])
      .gravity(0.1)
      .charge(-2000)
      .linkDistance(100)
      .size([width, height])
      .on("tick", tick),
    svg = d3.select(container).append("svg")
      .attr("width", width)
      .attr("height", height),
    node = svg.selectAll(".node"),
    link = svg.selectAll(".link"),
    needs_update = false,
    nodes = force.nodes(),
    links = force.links();

  build_links();
  update();

  //Load initial Stack box
  $("#stack_box").html(graph.stack.info_box);
  //On Page load, set Action In Progress
  var in_progress = false;
  set_in_progress(graph.stack, node);

  //If status is In Progress, start AJAX polling
  var poll_time = 0;
  if (in_progress === true) { poll_time = 3000; }
  else { poll_time = 30000; }
  ajax_poll(poll_time);
}
