
var theBox;
var callbacksInstalled = false;
var pendingDeferred = null;
var pendingFor = null;
var baseURI = null;

function installCallbacks(for_uri)
{
    if (callbacksInstalled != false) {
        return;
    }
    callbacksInstalled = true;
    
    if (for_uri == null) {
        return;
    }
    // if anyone knows of a decent urljoin() for JS, that'd 
    // be much nicer than this hack..
    if (for_uri[for_uri.length-1] == '/') {
        for_uri = for_uri.substr(0, for_uri.length-1);
    }
    baseURI = for_uri;

    cbinst = function (e) {
        updateNodeAttributes(e, { "onmouseover" : partial(mouseOverHandler, e), 
                                  "onmouseout" : partial(mouseOutHandler, e) } );
    }

    var elems = getElementsByTagAndClassName(null, "BranchLink");
    map(cbinst, elems);

    var elems = getElementsByTagAndClassName(null, "RevisionLink");
    map(cbinst, elems);

    var elems = getElementsByTagAndClassName(null, "DirLink");
    map(cbinst, elems);

    theBox = $("popupBox");
}

function updatePopup(boundTo, className)
{
    var jsonData = boundTo.jsonData;
    var error_string = null;
    var info = null;
    var pos  = elementPosition(boundTo);
    var newBox;
    
    if (jsonData == null) {
        error_string = "JSON-RPC error - please report";
    } else if (jsonData.error_string != null) {
        error_string = jsonData.error_string;
    }
    
    if (error_string == null) {
        if (jsonData.type == "branch") {
            info = "branch changed " + jsonData.ago + " ago by " + jsonData.author;
        } else if (jsonData.type == "revision") {
            info = "revision made " + jsonData.ago + " ago by " + jsonData.author;
        } else if (jsonData.type == "manifest") {
            info = "manifest contains " + jsonData.file_count + " files in " + jsonData.directory_count + " directories.";
        } else {
            info = "unknown type: " + jsonData.type;
        }
    } else {
        info = "error: " + error_string;
    }

    newBox = DIV({ 'id' : 'popupBox', 'style' : 'font-size: small'}, info);

    if (boundTo.offsetHeight) {
        offset_height = boundTo.offsetHeight;
    } else {
        offset_height = 24; // yick
    }

    newY = pos.y + offset_height;
    newX = pos.x;

    newBox.style.position = "absolute"
    newBox.style.top = newY + 'px';
    newBox.style.left = newX + 'px';
    swapDOM(theBox, newBox);
    theBox = newBox;
}

function jsonLoadComplete(boundTo, className, jsonData)
{
    boundTo.jsonData = jsonData;
    updatePopup(boundTo, className);
    pendingDeferred = null;
    pendingFor = null;
}

function squashPendingRequest()
{
    if (pendingFor != null) {
        pendingFor = null;
        pendingDeferred.cancel();
    }
}

// there should only ever be one pendingDeferred
// if we get a mouse over, we check whether or not 
// this is a duplicate of the existing pending (do
// nothing) or otherwise cancel the pending and 
// schedule what has happened now.

function dampenedJSON(uri, boundTo, className)
{
    // bit of a catch all in case somehow we've leaked through 
    // and not been cancelled
    if (pendingFor == boundTo) {
        pendingDeferred = loadJSONDoc(uri);
        pendingDeferred.addCallback(jsonLoadComplete, boundTo, className);
    }
}

function mouseOverHandler(boundTo, evt)
{
    var className = getNodeAttribute(boundTo, "class");

    if (boundTo != pendingFor) {
        squashPendingRequest();
    }
    if (boundTo.jsonData) {
        return updatePopup(boundTo, className);
    }
    if (boundTo.id) {
        var uri = baseURI + "/json/" + encodeURIComponent(className) + "/" + encodeURIComponent(boundTo.id);
        pendingDeferred = callLater(1, partial(dampenedJSON, uri, boundTo, className));
        pendingFor = boundTo;
    }
}

function mouseOutHandler(boundTo, evt)
{
    squashPendingRequest();
    var newBox = DIV({'id' : 'popupBox', 'class' : 'invisible'});
    swapDOM(theBox, newBox);
    theBox = newBox;
}

