{% from "macros.jinja2" import
	  get_conceptscheme_label
  %}

<div id="tree">
  <div id="loadingOverlay" class="pageOverlay">
    <div style="padding-top: 20px;"><i class="fa fa-4x fa-spinner fa-pulse"></i></div>
  </div>
</div>
<div>
  <p class="treeHelpMessage" style="display: none;"><i class="fa fa-info-circle"></i> {% trans %}tree_help{% endtrans %}</p>
</div>
<style>
  .node {
    cursor: pointer;
  }

  .overlay {
    background-color: #F8F8F8;
    border: 1px solid {{ borderColor|default('rgba(247, 127, 0, 0.2)') }};
  }

  .node circle {
    fill: {{ nodeChildlessColor|default('rgb(255, 255, 255)') }};
    stroke: {{ nodeColor|default('rgb(247, 127, 0)') }};
    stroke-width: 1.5px;
  }

  .node text {
    font-size: 12px;
    font-family: 'Noto+Sans', 'Helvetica Neue', Helvetica, Arial;
  }

  .link {
    fill: none;
    stroke: {{ pathColor|default('rgb(204, 204, 204)') }};
    stroke-width: 1.5px;
  }

</style>
<script>

  var nodeColor = '{{ nodeColor|default('rgb(247, 127, 0)') }}'
  var pathHighlightColor = '{{ pathColor|default('rgba(247, 127, 0, 0.6)') }}'
  var nodeChildlessColor = '{{ nodeChildlessColor|default('rgb(255, 255, 255)') }}'
  var linkTextColor = '{{ linkTextColor|default('rgb(43, 112, 157)') }}'
  var linkTextHoverColor = '{{ linkTextHoverColor|default('rgb(27, 69, 97)') }}'

  {% if conceptscheme %}
    {% set scheme_id = conceptscheme.scheme_id %}
  {% endif %}

  {% if concept %}
    var scheme_label = '{{ get_conceptscheme_label(concept.conceptscheme, request.locale_name)|trim }}';
    var scheme_id = '{{ scheme_id }}';
    var current = '{{ concept.concept_id }}';
  {% else %}
    var scheme_label = '{{ conceptscheme.title|trim }}';
    var scheme_id = '{{ conceptscheme.scheme_id }}';
    var current = '{{ conceptscheme.scheme_id }}';
  {% endif %}

  d3.json('{{ request.route_path("scheme_tree", scheme_id = scheme_id ) }}', function(error, tree) {
    if (error) throw error;

    var treeData = { 'label': scheme_label, 'scheme_id': scheme_id, children: tree }

    // Calculate total nodes, max label length
    var totalNodes = 0;
    var maxLabelLength = 0;
    // panning variables
    var panSpeed = 200;
    var panBoundary = 20; // Within 20px from edges will pan when dragging.
    // Misc. variables
    var i = 0;
    var duration = 750;
    var root;

    // size of the canvas
    var screenWidth = window.innerWidth;
    var viewerWidth = document.getElementById('tree').offsetWidth;
    if (viewerWidth <= 0) { //fix for when container is not initialized
      viewerWidth = 1000;
    }
    if (screenWidth < 1020) { //fix for smaller screens
      viewerWidth = screenWidth - 46;
    }
    var viewerHeight = 600;

    var tree = d3.layout.tree()
      .size([viewerHeight, viewerWidth]);

    // define a d3 diagonal projection for use by the node paths later on.
    var diagonal = d3.svg.diagonal()
      .projection(function(d) {
        return [d.y, d.x];
      });

    // A recursive helper function for performing some setup by walking through all nodes

    function visit(parent, visitFn, childrenFn) {
      if (!parent) return;

      visitFn(parent);

      var children = childrenFn(parent);
      if (children) {
        var count = children.length;
        for (var i = 0; i < count; i++) {
          visit(children[i], visitFn, childrenFn);
        }
      }
    }

    // Call visit function to establish maxLabelLength
    visit(treeData, function(d) {
      totalNodes++;
      if (d.label) {
        maxLabelLength = Math.max(d.label.length, maxLabelLength);
      } else {
        maxLabelLength = Math.max('{{ scheme_id }}'.length, maxLabelLength);
      }
    }, function(d) {
      return d.children && d.children.length > 0 ? d.children : null;
    });

    function pan(domNode, direction) {
      var speed = panSpeed;
      if (panTimer) {
        clearTimeout(panTimer);
        translateCoords = d3.transform(svgGroup.attr("transform"));
        if (direction == 'left' || direction == 'right') {
          translateX = direction == 'left' ? translateCoords.translate[0] + speed : translateCoords.translate[0] - speed;
          translateY = translateCoords.translate[1];
        } else if (direction == 'up' || direction == 'down') {
          translateX = translateCoords.translate[0];
          translateY = direction == 'up' ? translateCoords.translate[1] + speed : translateCoords.translate[1] - speed;
        }
        scaleX = translateCoords.scale[0];
        scaleY = translateCoords.scale[1];
        scale = zoomListener.scale();
        svgGroup.transition().attr("transform", "translate(" + translateX + "," + translateY + ")scale(" + scale + ")");
        d3.select(domNode).select('g.node').attr("transform", "translate(" + translateX + "," + translateY + ")");
        zoomListener.scale(zoomListener.scale());
        zoomListener.translate([translateX, translateY]);
        panTimer = setTimeout(function() {
          pan(domNode, speed, direction);
        }, 50);
      }
    }

    // Define the zoom function for the zoomable tree
    function zoom() {
      svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
    }

    // define the zoomListener which calls the zoom function on the "zoom" event constrained within the scaleExtents
    var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]).on("zoom", zoom);

    // define the baseSvg, attaching a class for styling and the zoomListener
    var baseSvg = d3.select("#tree").append("svg")
      .attr("width", viewerWidth)
      .attr("height", viewerHeight)
      .attr("class", "overlay")
      .call(zoomListener);

    // Helper functions for collapsing and expanding nodes.
    function collapse(d) {
      if (d.children) {
        d._children = d.children;
        d._children.forEach(collapse);
        d.children = null;
      }
    }

    function expand(d) {
      if (d._children) {
        d.children = d._children;
        d.children.forEach(expand);
        d._children = null;
      }
    }

    // Function to center node when clicked/dropped so node doesn't get lost when collapsing/moving with large amount of children.
    function centerNode(source) {
      scale = zoomListener.scale();
      x = -source.y0;
      y = -source.x0;
      x = x * scale + viewerWidth / 2;
      y = y * scale + viewerHeight / 2;
      d3.select('g').transition()
        .duration(duration)
        .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
      zoomListener.scale(scale);
      zoomListener.translate([x, y]);
    }

    // Toggle children function
    function toggleChildren(d) {
      if (d.children) {
        d._children = d.children;
        d.children = null;
      } else if (d._children) {
        d.children = d._children;
        d._children = null;
      }
      return d;
    }

    function openConcept(d) {
      d3.event.preventDefault();
      if (d.concept_id) {
        var basepath = '{{ request.route_path('concept', scheme_id= scheme_id, c_id = '') }}';
        window.location.href = basepath + d.concept_id;
      }
      else {
        if (d.scheme_id) {
          window.location.href = '/conceptschemes/' + d.scheme_id;
        }
      }
    }

    function expandToCurrent(node, current) {
      if (node.concept_id == current) {
        node.highlight = true;
      } else {
        node._children.forEach(function (item, index) {
          if (item.concept_id == current) {
            expandPath(item.id);
            item.highlight = true;
          }
          else {
            expandToCurrent(item, current);
          }
        });
      }
    }

    function expandPath(id) {
      var ids = id.split('.');
      currentIds = ids;
      var childrenArray = root.children;
      for (i = 0; i < (ids.length - 1); ++i) {
        var child = $.grep(childrenArray, function(child){ return child.concept_id == ids[i]; });
        child[0].children = child[0]._children;
        child[0].highlight = true;
        child[0]._children = null;
        childrenArray = child[0].children;
      }
    }

    function centerCurrentNode(current) {

    }

    // Toggle children on click.
    function click(d) {
      if (d3.event.defaultPrevented) return; // click suppressed
      d = toggleChildren(d);
      update(d);
      centerNode(d);
    }

    function update(source) {
      // Compute the new height, function counts total children of root node and sets tree height accordingly.
      // This prevents the layout looking squashed when new nodes are made visible or looking sparse when nodes are removed
      // This makes the layout more consistent.
      var levelWidth = [1];
      var childCount = function(level, n) {

        if (n.children && n.children.length > 0) {
          if (levelWidth.length <= level + 1) levelWidth.push(0);

          levelWidth[level + 1] += n.children.length;
          n.children.forEach(function(d) {
            childCount(level + 1, d);
          });
        }
      };
      childCount(0, root);
      var newHeight = d3.max(levelWidth) * 25; // 25 pixels per line
      tree = tree.size([newHeight, viewerWidth]);

      // Compute the new tree layout.
      var nodes = tree.nodes(root).reverse(),
        links = tree.links(nodes);

      // Set widths between levels based on maxLabelLength.
      nodes.forEach(function(d) {
        d.y = (d.depth * (maxLabelLength * 5)); //maxLabelLength * 6px
        // alternatively to keep a fixed scale one can set a fixed depth per level
        // Normalize for fixed-depth by commenting out below line
        // d.y = (d.depth * 500); //500px per level.
      });

      // Update the nodes…
      node = svgGroup.selectAll("g.node")
        .data(nodes, function(d) {
          return d.id || (d.id = ++i);
        });

      // Enter any new nodes at the parent's previous position.
      var nodeEnter = node.enter().append("g")
        .attr("class", "node")
        .attr("transform", function(d) {
          return "translate(" + source.y0 + "," + source.x0 + ")";
        })
        .on('click', click);


      nodeEnter.append("circle")
        .attr('class', 'nodeCircle')
        .attr("r", 0)
        .style("fill", function(d) {
          return d._children && d._children.length > 0 ? nodeColor : nodeChildlessColor;
        });


      nodeEnter.append("text")
        .attr("x", function(d) {
          return d.children || d._children ? -10 : 10;
        })
        .attr("dy", ".35em")
        .attr('class', 'nodeText')
        .attr("text-anchor", function(d) {
          return d.children || d._children ? "end" : "start";
        })
        .text(function(d) {
          return d.label;
        })
        .style("fill-opacity", 1e-6)
        .style("fill", function(d) { return d.highlight ? nodeColor : linkTextColor })
        .on('contextmenu', openConcept)
        .on("mouseover", function(d) {
          d3.select(this).style("fill", linkTextHoverColor);
        })
        .on("mouseout", function(d) {
          d3.select(this).style("fill", function(d) { return d.highlight ? nodeColor : linkTextColor });
        });

      // Update the text to reflect whether node has children or not.
      node.select('text')
        .attr("x", function(d) {
          return d.children || d._children ? -10 : 10;
        })
        .attr("text-anchor", function(d) {
          return d.children || d._children ? "end" : "start";
        })
        .text(function(d) {
          return d.label;
        });

      // Change the circle fill depending on whether it has children and is collapsed
      node.select("circle.nodeCircle")
        .attr("r", 4.5)
        .style("fill", function(d) {
          return (d._children && d._children.length > 0) ? nodeColor : nodeChildlessColor;
        });

      // Transition nodes to their new position.
      var nodeUpdate = node.transition()
        .duration(duration)
        .attr("transform", function(d) {
          return "translate(" + d.y + "," + d.x + ")";
        });

      nodeUpdate.select("circle")
        .attr("r", 4.5)
        .style("fill", function(d) { return (d._children && d._children.length > 0) ? nodeColor : nodeChildlessColor; });

      nodeUpdate.select("text")
        .style("fill-opacity", 1);


      // Transition exiting nodes to the parent's new position.
      var nodeExit = node.exit().transition()
        .duration(duration)
        .attr("transform", function(d) {
          return "translate(" + source.y + "," + source.x + ")";
        })
        .remove();

      nodeExit.select("circle")
        .attr("r", 0);

      nodeExit.select("text")
        .style("fill-opacity", 0);

      // Update the links…
      var link = svgGroup.selectAll("path.link")
        .data(links, function(d) {
          return d.target.id;
        });

      // Enter any new links at the parent's previous position.
      link.enter().insert("path", "g")
        .attr("class", "link")
        .attr("d", function(d) {
          var o = {
            x: source.x0,
            y: source.y0
          };
          return diagonal({
            source: o,
            target: o
          });
        });

      // Transition links to their new position.
      link.transition()
        .duration(duration)
        .attr("d", diagonal);

      // Transition exiting nodes to the parent's new position.
      link.exit().transition()
        .duration(duration)
        .attr("d", function(d) {
          var o = {
            x: source.x,
            y: source.y
          };
          return diagonal({
            source: o,
            target: o
          });
        })
        .remove();

      link.style("stroke", function(d) { return d.target.highlight ? pathHighlightColor : null });

      // Stash the old positions for transition.
      nodes.forEach(function(d) {
        d.x0 = d.x;
        d.y0 = d.y;
        // center tree on current concept/scheme
        if (parseInt(current) === d.concept_id || current === d.scheme_id) {
          centerNode(d);
        }
      });
    }

    // Append a group which holds all nodes and which the zoom Listener can act upon.
    var svgGroup = baseSvg.append("g");

    // Define the root
    root = treeData;
    root.x0 = viewerHeight / 2;
    root.y0 = 0;
    root.id = -1;

    // Collapse all nodes and open path to current concept
    root.children.forEach(collapse);
    root.children.forEach(function(item) {
      expandToCurrent(item, current);
    });

    // Layout the tree initially
    update(root);

    // hide loading
    $('#loadingOverlay').hide();
    $('.treeHelpMessage').show();
  });
</script>