(function() {
  packages = {

    // Lazily construct the package hierarchy from class names,
    // then recursively computes import counts, where the count
    // of an inner node is the total of its children's external
    // import counts
    root: function(classes) {
      var map = {},
          leaves = [],
          root = null;  

      function find(name, data) {
        var node = map[name], i;
        if (!node) {
          node = map[name] = data || {name: name, children: []};
          if (name.length) {
            node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
            node.parent.children.push(node);
            node.key = name.substring(i + 1);
          }
        }
        return node;
      }

      function aggregateExternalImports(node){
        var children = node.children,
            name = node.name,
            allImports = [],
            imports = [];

        if (children && children.length) {
          // inner node stuff
          children.forEach(function(child) {
            allImports.push.apply(allImports, aggregateExternalImports(child));
          });
          allImports.forEach(function(importName) {
            if (importName.indexOf(name) == -1)
              imports.push(importName);
          });
          node.externalImports = imports;
          return imports;
        } else {
          // leaf node stuff
          allImports = node.imports;
          node.externalImports = allImports;
          return allImports;
        }
      }

      // adds up external dependant + import counts to
      // obtain external link count
      function countExternalLinks(node) {
        var children = node.children,
            links = 0;

        if (children && children.length) {
          // inner node stuff
          children.forEach(function(child) { 
            links += countExternalLinks(child); 
          });
        } else {
          // leaf node stuff
          links = node.externalImports.length + node.externalDepCount;
        }

        node.externalLinkCount = links;
        return links;
      }

      classes.forEach(function(d) {
        var leaf = find(d.name, d);
        leaves.push(leaf);
      });

      root = map.flare;
      aggregateExternalImports(root);

      // initialize external dep count for leaf nodes
      leaves.forEach(function(d) { d.externalDepCount = 0; });

      // add a dependants count to each class (leaf node)
      // based on the external classes that import them
      leaves.forEach(function(d) {
        var exNode;
        d.externalImports.forEach(function(importName) {
          exNode = find(importName);
          exNode.externalDepCount++;
        });
      });
      
      countExternalLinks(root);

      return root;
    },

    // Return a list of imports for the given array of nodes.
    imports: function(nodes) {
      var map = {},
          imports = [];

      // Compute a map from name to node.
      nodes.forEach(function(d) {
        map[d.name] = d;
      });

      // For each import, construct a link from the source to target node.
      nodes.forEach(function(d) {
        if (d.imports) d.imports.forEach(function(i) {
          imports.push({source: map[d.name], target: map[i]});
        });
      });

      return imports;
    }
  };
})();