var DEPTH_HEIGHT = 100;

/**
 * Based off of http://mbostock.github.io/d3/talk/20111018/tree.html by Mike Bostock (@mbostock)
 */
function ImageHistoryTree(namespace, name, images, current, formatComment, formatTime) {
    /**
     * The namespace of the repo.
     */
    this.repoNamespace_ = namespace;

    /**
     * The name of the repo.
     */
    this.repoName_ = name;

    /**
     * The images to display.
     */
    this.images_ = images;

    /**
     * The current tag.
     */
    this.currentTag_ = current;

    /**
     * The current image.
     */
    this.currentImage_ = current.image;

    /**
     * Counter for creating unique IDs.
     */
    this.idCounter_ = 0;

    /**
     * Method to invoke to format a comment for an image.
     */
    this.formatComment_ = formatComment;

    /**
     * Method to invoke to format the time for an image.
     */
    this.formatTime_ = formatTime;
}


/**
 * Draws the tree.
 */
ImageHistoryTree.prototype.draw = function(container) {
    // Build the root of the tree.
    var maxHeight = this.buildRoot_();

    var width = document.getElementById(container).clientWidth;
    var height = Math.max(width * 0.625, maxHeight * (DEPTH_HEIGHT + 10));

    var margin = { top: 40, right: 120, bottom: 20, left: 120 };
    var m = [margin.top, margin.right, margin.bottom, margin.left];
    var w = width - m[1] - m[3];
    var h = height - m[0] - m[2];

    var tree = d3.layout.tree()
        .size([h, w]);

    var diagonal = d3.svg.diagonal()
        .projection(function(d) { return [d.x, d.y]; });

    var vis = d3.select("#" + container).append("svg:svg")
        .attr("width", w + m[1] + m[3])
        .attr("height", h + m[0] + m[2])
        .append("svg:g")
        .attr("transform", "translate(" + m[3] + "," + m[0] + ")");

    var formatComment = this.formatComment_;
    var formatTime = this.formatTime_;
    var tip = d3.tip()
        .attr('class', 'd3-tip')
        .offset([-10, 0])
        .html(function(d) {
            var html = '';
            if (d.image.comment) {
                html += '<span class="comment">' + formatComment(d.image.comment) + '</span>';
            }
            html += '<span class="created">' + formatTime(d.image.created) + '</span>';
            html += '<span class="full-id">' + d.image.id + '</span>';
            return html;
        })

    vis.call(tip);

    this.fullWidth_ = width;

    this.width_ = w;
    this.height_ = h;

    this.diagonal_ = diagonal;
    this.vis_ = vis;
    this.tip_ = tip;

    this.tree_ = tree;

    // Populate the tree.
    this.populate_();
};


/**
 * Returns the ancestors of the given image.
 */
ImageHistoryTree.prototype.getAncestors = function(image) {
  var ancestorsString = image.ancestors;

  // Remove the starting and ending /s.
  ancestorsString = ancestorsString.substr(1, ancestorsString.length - 2);

  // Split based on /.
  ancestors = ancestorsString.split('/');
  return ancestors;
};


/**
 * Builds the root node for the tree.
 */
ImageHistoryTree.prototype.buildRoot_ = function() {
    // Build the formatted JSON block for the tree. It must be of the form:
    //  {
    //    "name": "...",
    //    "children": [...]
    //  }
    var formatted = {};
    var currentAncestors = this.getAncestors(this.currentImage_);

    // Build a node for each image.
    var imageByDBID = {};
    for (var i = 0; i < this.images_.length; ++i) {
        var image = this.images_[i];
        var isCurrent = image.id == this.currentImage_.id;
        var isHighlighted = currentAncestors.indexOf(image.dbid.toString()) >= 0;
        var imageNode = {
            "name": image.id.substr(0, 12),
            "children": [],
            "image": image,
            "highlighted": isHighlighted || isCurrent,
            "current": isCurrent,
            "tags": image.tags
        };
        imageByDBID[image.dbid] = imageNode;
    }
    
    // For each node, attach it to its immediate parent. If there is no immediate parent,
    // then the node is the root.
    var maxAncestorCount = 0;
    for (var i = 0; i < this.images_.length; ++i) {
        var image = this.images_[i];
        var imageNode = imageByDBID[image.dbid];
        var ancestors = this.getAncestors(image);
        var immediateParent = ancestors[ancestors.length - 1] * 1;
        var parent = imageByDBID[immediateParent];
        if (parent) {
            parent.children.push(imageNode);
        } else {
            formatted = imageNode;
        }

        maxAncestorCount = Math.max(maxAncestorCount, ancestors.length);
    }

    this.root_ = formatted;
    return maxAncestorCount + 1;
};


/**
 * Populates the tree.
 */
ImageHistoryTree.prototype.populate_ = function() { 
    // Set the position of the initial node.
    this.root_.x0 = this.fullWidth_ / 2;
    this.root_.y0 = 0;
        
    // Initialize the display to show the current path of nodes.
    this.update_(this.root_);
};


/**
 * Updates the tree in response to a click on a node.
 */
ImageHistoryTree.prototype.update_ = function(source) {
  var tree = this.tree_;
  var vis = this.vis_;
  var diagonal = this.diagonal_;
  var tip = this.tip_;
  var currentTag = this.currentTag_;
  var repoNamespace = this.repoNamespace_;
  var repoName = this.repoName_;

  var that = this;

  var duration = 500;

  // Compute the new tree layout.
  var nodes = tree.nodes(this.root_).reverse();

  // Normalize for fixed-depth.
  nodes.forEach(function(d) { d.y = d.depth * DEPTH_HEIGHT; });

  // Update the nodes...
  var node = vis.selectAll("g.node")
      .data(nodes, function(d) { return d.id || (d.id = that.idCounter_++); });

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

  nodeEnter.append("svg:circle")
      .attr("r", 1e-6)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; })
      .on("click", function(d) { that.toggle_(d); that.update_(d); });
 
  // Create the group that will contain the node name and its tags.
  var g = nodeEnter.append("svg:g").style("fill-opacity", 1e-6);

  // Add the repo ID.
  g.append("svg:text")
      .attr("x", function(d) { return d.children || d._children ? -10 : 10; })
      .attr("dy", ".35em")
      .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
      .text(function(d) { return d.name; })
      .on("click", function(d) { that.toggle_(d); that.update_(d); })
      .on('mouseover', tip.show)
      .on('mouseout', tip.hide);

    nodeEnter.selectAll("tags")
        .append("svg:text")
        .text("bar");

  // Create the foreign object to hold the tags (if any).
  var fo = g.append("svg:foreignObject")
      .attr("x", 14)
      .attr("y", 10)
      .attr("width", 110)
      .attr("height", DEPTH_HEIGHT - 20);
  
  // Add the tags.
  fo.append('xhtml:div')
    .attr("class", "tags")
    .style("display", "none")
    .html(function(d) {
        var html = '';
        for (var i = 0; i < d.tags.length; ++i) {
            var tag = d.tags[i];
            var kind = 'default';
            if (tag == currentTag.name) {
                kind = 'success';
            }
            html += '<a href="/#/repository/' + repoNamespace + '/' + repoName + '/tag/' + tag + '">';
            html += '<span class="label label-' + kind + ' tag">' + tag + '</span>';
            html += '</a>';
        }
        return html;
     });

  // Translate the foreign object so the tags are under the ID.
  fo.attr("transform", function(d, i) {
    bbox = this.getBBox()
    return "translate(" + [-130, 0 ] + ")";
  });

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

  nodeUpdate.select("circle")
      .attr("r", 4.5)
      .attr("class", function(d) { return d._children ? "closed" : "open"; })
      .style("fill", function(d) {
        if (d.current) {
            return "";
        }
        return d._children ? "lightsteelblue" : "#fff";
      });

  nodeUpdate.select(".tags")
      .style("display", "");

  nodeUpdate.select("g")
      .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.x + "," + source.y + ")"; })
      .remove();

  nodeExit.select("circle")
      .attr("r", 1e-6);

  nodeExit.select(".tags")
      .style("display", "none");

  nodeExit.select("g")
      .style("fill-opacity", 1e-6);

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

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

  // 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();

  // Stash the old positions for transition.
  nodes.forEach(function(d) {
    d.x0 = d.x;
    d.y0 = d.y;
  });
};


/**
 * Toggles children of a node.
 */
ImageHistoryTree.prototype.toggle_ = function(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
};