
/**
 * Creates the UI that shows a graph of the person's family,
 * i.e. parents, children, siblings etc.
 * Also shows friends.
 * This is displayed as directed graph.
 *
 * Creates:
 * <canvas class="genealogy"/>
 *
 * Depends on: springy.js and springyui.js
 *
 * @param container {jquery DOM Element}   <div> where to add the UI
 * @param person {Person}   The object to display
 * @param navFunc {Function}   Called when the user
 *     clicks on another object and that one should be shown.
 * @param errorCallback {Function(e)}
 */
function uiGenealogy(p) {
  assert(p.container, "div: need element");
  assert(p.person instanceof Person, "person: need");
  assert(typeof(p.navFunc) == "function", "need navFunc");
  assert(typeof(p.errorCallback) == "function", "need errorCallback");

  var relatedPersons = [];
  var marriageNodes = [];
  // look up existing nodes or create one
  function getNode(graph, person) {
    if ( !person) {
      return null;
    }
    if (graph.hasNode(person.id)) {
      return graph.node(person.id);
    }
    if (person != p.person) {
      relatedPersons.push(person);
    }
    var node = { id : person.id, person : person };
    graph.addNode(person.id, node);
    return node;
  }
  function makeMarriage(husbandNode, wifeNode) {
    assert(husbandNode || wifeNode);
    var existing = marriageNodes.filter(function(m) {
      return m.husband == husbandNode && m.wife == wifeNode
    });
    if (existing[0]) {
      return existing[0];
    }
    var m = {};
    m.husband = husbandNode;
    m.wife = wifeNode;
    m.children = [];
    m.id = graph.addNode(m.id, m);
    if (husbandNode) {
      graph.addEdge(null, husbandNode.id, m.id, { label : tr("genealogy.rel.father") });
    }
    if (wifeNode) {
      graph.addEdge(null, wifeNode.id, m.id, { label : tr("genealogy.rel.mother") });
    }
    marriageNodes.push(m);
    return m;
  }
  function addChildNode(marriageNode, childNode) {
    assert(marriageNode);
    assert(childNode);
    if (marriageNode.children.indexOf(childNode) >= 0) {
      return;
    }
    marriageNode.children.push(childNode);
    graph.addEdge(null, marriageNode.id, childNode.id, { label : tr("genealogy.rel.child") });
  }
  // traverse graph
  function addRelatedPersons(graph, person, hop, maxHops) {
    if (hop++ > maxHops) {
      return;
    }
    var subjNode = getNode(graph, person);
    person.personRelations.forEach(function(rel) {
      var objNode = getNode(graph, rel.obj);
      if (rel.type == "married") {
        if (rel.subj.male && !rel.obj.male) {
          makeMarriage(subjNode, objNode);
        } else if (rel.obj.male && !rel.subj.male) {
          makeMarriage(objNode, subjNode);
        } else {
          p.errorCallback(new Exception(tr("genealogy.wrong-marriage-gender",
              [ rel.subj.name, rel.obj.name ] )));
        }
      } else if (rel.type == "father") {
        var m = makeMarriage(objNode, getNode(graph, rel.subj.mother));
        addChildNode(m, subjNode);
      } else if (rel.type == "mother") {
        var m = makeMarriage(getNode(graph, rel.subj.father), objNode);
        addChildNode(m, subjNode);
      } else if (rel.type == "child") {
        var fatherNode = null;
        var motherNode = null;
        if (rel.subj.male) {
          fatherNode = subjNode;
          motherNode = getNode(graph, rel.obj.mother);
        } else {
          motherNode = subjNode;
          fatherNode = getNode(graph, rel.obj.father);
        }
        var m = makeMarriage(fatherNode, motherNode);
        addChildNode(m, objNode);
      } else if (graph.neighbors(subjNode.id).indexOf(objNode.id) != -1) {
        // avoid 2-way edges
      } else {
        graph.addEdge(null, subjNode.id, objNode.id, { label : rel.typeLabel });
      }

      addRelatedPersons(graph, rel.obj, hop, maxHops);
    });
  }
  // When user clicks on a node, load the page for the person
  function selectedFunc(node) {
    assert(node.person instanceof Person, "graph node doesn't have Person");
    if (node.person == p.person) {
      return; // don't change to current
    }
    p.navFunc(node.person, relatedPersons, tr("genealogy.person.hl", p.person.name));
  }

  var canvasE = $("<svg class='genealogy' />").appendTo(p.container);
  //var width = Math.round(gDevice.width) - 50;
  //var height = Math.round(gDevice.height) - 10;
  //canvasE.attr("width", width + "px").attr("height", height + "px");
  //canvasE.attr("width", "1000px");
  //p.container.attr("style", "overflow-x: auto"); TODO
  var maxHops = 2;
  if (gDevice.width < 800) {
    maxHops = 1; // reduce content
  }

  function drawNode(graph, u, svg) {
    var node = graph.node(u);
    if (node.person) {
      var person = node.person;
      dagre_addLabel(svg, person.name, 5, 5);
      svg.attr("gender", node.person.male ? "male" : "female");
      if (node.person == p.person) {
        svg.attr("center", "true");
      } else {
        svg.on("click", function() {
          p.navFunc(person);
        });
      }
    } else { // is marriage node
      dagre_addLabel(svg, "∞", 0, 0);
      svg.attr("marriage", "true");
    }
  }

  var graph = new dagreD3.Digraph();
  addRelatedPersons(graph, p.person, 1, maxHops);
  runLater(100, function() {
    var renderer = new dagreD3.Renderer();
    var layout = dagreD3.layout()
        .nodeSep(15)
        .edgeSep(10)
        .rankSep(20)
        .rankDir("TB");
    renderer.drawNode(drawNode);
    var root = d3.select(canvasE.get(0))
        .append("g").attr("transform", "translate(2, 2)");
    renderer.layout(layout).run(graph, root);
  }, p.errorCallback);
}



function dagre_addLabel(root, label, marginX, marginY) {
  var rect;
  if (!!marginX || !!marginY) {
    // Add the rect first so that it appears behind the label
    rect = root.append("rect");
  }
  var labelSvg = root.append("g");

  // no addForeignObjectLabel(), it does innerHTML() :-(
  dagre_addTextLabel(label, labelSvg);

  var bbox = root.node().getBBox();

  labelSvg.attr("transform",
             "translate(" + (-bbox.width / 2) + "," + (-bbox.height / 2) + ")");

  if (rect) {
    rect
      .attr("rx", 5)
      .attr("ry", 5)
      .attr("x", -(bbox.width / 2 + marginX))
      .attr("y", -(bbox.height / 2 + marginY))
      .attr("width", bbox.width + 2 * marginX)
      .attr("height", bbox.height + 2 * marginY);
  }
}

function dagre_addTextLabel(label, root) {
  root
    .append("text")
      .attr("text-anchor", "left")
    .append("tspan")
      .attr("dy", "1em")
      .text(label);
}
