{% 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, locale)|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, _query={'language':locale}) }}')
    .header("Accept", "application/json")
    .get(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 nodeLengthsByDepth = [];

      // 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, depth) {
        if (!parent) return;

        visitFn(parent, depth);

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

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

      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('skosprovider.c', scheme_id= scheme_id, c_id = '') }}';
          window.location.href = basepath + d.concept_id +'?view_tree';
        }
        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 == decodeURIComponent(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) {
          var lengths = nodeLengthsByDepth.slice(0, d.depth + 1);
          d.y =  lengths.reduce(function (sum, length) {
            return sum + (length * 7); // length * 7px
          });
          // 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>