The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
// based on http://thejit.org/static/v20/Jit/Examples/Treemap/example2.html
var labelType, useGradients, nativeTextSupport, animate;

(function() {
  var ua = navigator.userAgent,
      iStuff = ua.match(/iPhone/i) || ua.match(/iPad/i),
      typeOfCanvas = typeof HTMLCanvasElement,
      nativeCanvasSupport = (typeOfCanvas == 'object' || typeOfCanvas == 'function'),
      textSupport = nativeCanvasSupport 
        && (typeof document.createElement('canvas').getContext('2d').fillText == 'function');
  //I'm setting this based on the fact that ExCanvas provides text support for IE
  //and that as of today iPhone/iPad current text support is lame
  labelType = (!nativeCanvasSupport || (textSupport && !iStuff))? 'Native' : 'HTML';
  nativeTextSupport = labelType == 'Native';
  useGradients = nativeCanvasSupport;
  animate = !(iStuff || !nativeCanvasSupport);
  console.log({ "labelType":labelType, "useGradients":useGradients, "nativeTextSupport":nativeTextSupport });
})();

var Log = {
  elem: false,
  write: function(text){
    if (!this.elem) 
      this.elem = document.getElementById('log');
    this.elem.innerHTML = text;
    this.elem.style.left = (500 - this.elem.offsetWidth / 2) + 'px';
  }
};

/**
 * Convert number of bytes into human readable format
 *
 * @param integer bytes     Number of bytes to convert
 * @param integer precision Number of digits after the decimal separator
 * @return string
 * via http://codeaid.net/javascript/convert-size-in-bytes-to-human-readable-format-(javascript)
 */
function bytesToSize(bytes, precision) {
    var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
    var posttxt = 0;
    while( bytes >= 1024 ) {
        posttxt++;
        bytes = bytes / 1024;
    }
    var num = (posttxt) ? Number(bytes).toFixed(precision) : bytes;
    return num + " " + sizes[posttxt];
}


// http://stackoverflow.com/questions/5199901/how-to-sort-an-associative-array-by-its-values-in-javascript
function bySortedValue(obj, comparitor, callback, context) {
    var tuples = [];
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            tuples.push([key, obj[key]]);
        }
    }

    tuples.sort(comparitor);

    if (callback) {
        var length = tuples.length;
        while (length--) callback.call(context, tuples[length][0], tuples[length][1]);
    }
    return tuples;
}


function init(){

    var levelsToShow = 1;

  //init TreeMap
  var tm = new $jit.TM.Squarified({
    //where to inject the visualization
    injectInto: 'infovis',
    //show only one tree level
    levelsToShow: levelsToShow,
    //parent box title heights
    titleHeight: 14,
    //enable animations
    animate: animate,
    //box offsets
    offset: 1,
    //use canvas text
    // XXX disabled to allow the onMouseEnter/onMouseLeave Events to fire to set the blue border
    XXX_Label: {
      type: labelType,
      size: 10,
      family: 'Tahoma, Verdana, Arial'
    },
    //enable specific canvas styles
    //when rendering nodes
    Node: {
      CanvasStyles: {
        shadowBlur: 0,
        shadowColor: '#000'
      }
    },
    //Attach left and right click events
    Events: {
      enable: true,
      onClick: function(node) {
        if(node) {
            // TODO use https://github.com/browserstate/history.js ?
            window.history.pushState({ "id":node.id }, "", "/"+node.id+"?"+jQuery('#sizeme_params_form').serialize());
            tm.enter(node);
        }
      },
      onRightClick: function() {
        tm.out();
      },
      //change node styles and canvas styles
      //when hovering a node
      onMouseEnter: function(node, eventInfo) {
        if(node) {
          //add node selected styles and replot node
          node.setCanvasStyle('shadowBlur', 7);
          node.setData('color', '#888');
          tm.fx.plotNode(node, tm.canvas);
          tm.labels.plotLabel(tm.canvas, node);
        }
      },
      onMouseLeave: function(node) {
        if(node) {
          node.removeData('color');
          node.removeCanvasStyle('shadowBlur');
          tm.plot();
        }
      }
    },
    //duration of the animations
    duration: 200,
    //Enable tips
    Tips: {
      enable: true,
      type: 'Native',
      //add positioning offsets
      offsetX: 20,
      offsetY: 20,
      //implement the onShow method to
      //add content to the tooltip when a node
      //is hovered
      onShow: function(tip, node, isLeaf, domElement) {

          // XXX all this needs html escaping
        var data = node.data;
        var html = "<div class=\"tip-title\">"
          + (data.title ? "\""+data.title+"\"" : "")
          + " " + data.name
          + "</div><div class=\"tip-text\">";

        html += "<br />";
        html += sprintf("Memory use: %s<br />", bytesToSize(data.self_size+data.kids_size,2));
        if (data.kids_size) {
            html += sprintf("Child use:  %s<br />", bytesToSize(data.kids_size,2));
        }
        if (data.self_size) {
            if (data.kids_size)
                html += sprintf("Own use:    %s<br />", bytesToSize(data.self_size,2));
        }

        $("#sizeme_data_div").JSONView({
            "attributes": data.attr,
            "parts": data.leaves,
            "depth": data.depth,
            "kids_node_count": data.kids_node_count,
            "kids_size_bytes": data.kids_size,
            "self_size_bytes": data.self_size,
            "name": data.name,
            "title": data.title,
            "parent_id": data.parent_id,
            "id": sprintf("%s%s", node.id, data._ids_merged ? data._ids_merged : "")
        });

        tip.innerHTML =  html; 
      }  
    },

    //Implement this method for retrieving a requested  
    //subtree that has as root a node with id = nodeId,  
    //and level as depth. This method could also make a server-side  
    //call for the requested subtree. When completed, the onComplete   
    //callback method should be called.  
    request: function(nodeId, level, onComplete){  
            request_jit_tree(nodeId, level, levelsToShow, function(data) {
                console.log({ "node": nodeId, "request_jit_tree": data});
                update_node_path(data);
                onComplete.onComplete(nodeId, data.nodes);
            });
            // XXX workaround jit bug where old tooltip is still shown till the
            // mouse moves
            jQuery("#_tooltip").fadeOut("fast");
    },

    //Add the name of the node in the corresponding label
    //This method is called once, on label creation and only for DOM labels.
    onCreateLabel: function(domElement, node){
        domElement.innerHTML = node.name;

        // this doesn't work with Label:{} above
        var style = domElement.style;  
        style.display = '';  
        style.border = '1px solid transparent';  
        domElement.onmouseover = function() {  
            style.border = '1px solid #9FD4FF';  
        };  
        domElement.onmouseout = function() {  
            style.border = '1px solid transparent';  
        };  

    },
    onPlaceLabel: function(domElement, node){ },
  });


function request_jit_tree(nodeId, level, levelsToShow, onComplete){
    var params = jQuery('#sizeme_params_form').serialize();
    jQuery.getJSON('jit_tree/'+nodeId+'/'+levelsToShow, params, onComplete);
}

function request_jit_tree_for_node_and_refresh(nodeId){
  if (!nodeId)
      nodeId = 1;
  console.log({ request_jit_tree_for_node_and_refresh: nodeId });
  request_jit_tree(nodeId, 0, levelsToShow, function(data) {
        console.log({ request_jit_tree: data });
        update_node_path(data);
        tm.loadJSON(data.nodes);
        tm.refresh();
    });
}

function update_node_path(data){
    jQuery("#sizeme_path_ul").html(data.name_path_html);
    // update #sizeme_path_ul
    //   <li><a href="#">Home</a> <span class="divider">/</span></li>
    //   <li><a href="#">Library</a> <span class="divider">/</span></li>
    //   <li class="active">Data</li>
}

  request_jit_tree_for_node_and_refresh(window.location.pathname.split('/')[1]); // XXX get from url

    //add event to buttons
    $jit.util.addEvent($jit.id('goto_parent'), 'click', function() {
        // record new node in history
        var parents = tm.graph.getNode(tm.clickedNode && tm.clickedNode.id || tm.root).getParents();
        var parent_id = parents[0].id;
        window.history.pushState({ "id":parent_id }, "", "/"+parent_id+"?"+jQuery('#sizeme_params_form').serialize());
        // move out one level with a nice animation
        tm.out();
    });

    jQuery('#sizeme_logarea_checkbox').change( function() {
        request_jit_tree_for_node_and_refresh(tm.clickedNode && tm.clickedNode.id || tm.root);
    });

    window.onpopstate = function(e) {
        if (e.state && e.state.id)
            request_jit_tree_for_node_and_refresh(e.state.id);
    };

}