var db;
var current_instance_id;
var focus_species_id;
var imgw;
var imgh;

/////////
// configuration required from server
//
var imgdir     = "/entitylevelview/tiles";                      // image home directory (where the images live)
var imgx       = 0;                               // absolute image width
var imgy       = 0;                               // absolute image height
//var str        = document.location.href;          // get location (url)
//var ar         = str.match(/(\w+:\/\/[^\/:]+)/);  // check format
//var imgwww     = RegExp.$1;                       // URL
var imgwww = "";
var thumbnailw = 200;                             // thumbnail image width
var thumbnailh = 200;                             // thumbnail image height

/////////
// global variables
//
var ntilesx    = 0;                               // number of tiles (x)
var ntilesy    = 0;                               // number of tiles (y)
var tilewidth  = 200;                             // standard width of each tile
var tileheight = 200;                             // standard height of each tile
var starty     = 0;                               // default starting position of image (y)
var startx     = 0;                               // default starting position of image (x)
var maxz       = 4;                               // max zoom level
var z          = 1;                               // zoom level
var dz         = 1;                               // default zoom multiplier
var dx         = 25;                              // the number of pixels to move the image (left-right)
var dy         = 25;                              // the number of pixels to move the image (up-down)
var zoomFactor = 1;
var iClipWidth = 0;
var iClipHeight = 0;
var highliteBorderThickness = 5;


/* *********************************
 author : ph5
 date : 01-11-2005
 function:  event handling
************************************ */
// global variables
var EventUtil = new Object;
var iDiffX = 0, iDiffY = 0;
var iOrigLeft, iOrigTop;
var isIE = (navigator.appVersion.match(/\bMSIE\b/)) ? true : false;
var viewportLeft;
var viewportRight;
var viewportTop;
var viewportBottom;

var highlitesInViewportOnly = true;

// get events
EventUtil.getEvent = function() {
    if (window.event) {
        return this.formatEvent(window.event);
    } else {
        return EventUtil.getEvent.caller.arguments[0];
    }
};

// Use formatEvent from tooltip.js
//EventUtil.formatEvent = formatEvent;

// format events to sync IE events with W3C DOM events
EventUtil.formatEvent = function (oEvent) {

    if (oEvent.srcElement && !oEvent.target) {
        oEvent.charCode = (oEvent.type == "keypress")?oEvent.keyCode:0;      // match key codes 
        oEvent.eventPhase = 2;                                               // enable bubbling phase
        oEvent.isChar = (oEvent.charCode > 0);                               // if keycode is true is value !=0
        oEvent.pageX = oEvent.clientX + document.body.scrollLeft;            // creates pageX
        oEvent.pageY = oEvent.clientY + document.body.scrollTop;             // creates pageY
        if (!oEvent.preventDefault) {                                        // prevent event from default action
            oEvent.preventDefault = function () {
                this.returnValue = false;
            };
        }
        if (!oEvent.stopPropagation) {                                       // prevent bubbling
            oEvent.stopPropagation = function () {
                this.cancelBubble = true;
            };
        }
        if (oEvent.type == "mouseout") {                                     // create relatedTarget
            oEvent.relatedTarget = oEvent.toElement;
        } else if (oEvent.type == "mouseover") {
            oEvent.relatedTarget = oEvent.fromElement;
        }
	oEvent.target = oEvent.srcElement;                                   // create target
        oEvent.time = (new Date).getTime();                                  // create time
    }
    return oEvent;
};

// create event handler
EventUtil.addEventHandler = function (oTarget, sEventType, fnHandler) {
    if (oTarget.addEventListener) {                             // W3C DOM
        oTarget.addEventListener(sEventType, fnHandler, false);
    } else if (oTarget.attachEvent) {                           // IE
        oTarget.attachEvent("on"+sEventType, fnHandler);
    } else {                                                    // others
        oTarget["on"+sEventType] = fnHandler;
    }
};

// delete event handler
EventUtil.removeEventHandler = function (oTarget, sEventType, fnHandler) {
    if (oTarget.removeEventListener) {                          // W3C DOM
        oTarget.removeEventListener(sEventType, fnHandler, false);
    } else if (oTarget.detachEvent) {                           // IE
        oTarget.detachEvent("on"+sEventType, fnHandler);
    } else {                                                    // others
        oTarget["on"+sEventType] = null;
    }
};





/* ******************************************
 function : Move Controller with drop-zones
********************************************* */


// engage controller
function engageController(oEvent) {

    // global variables
    var oEvent = EventUtil.getEvent();
    var oController = $("controller");

    // set original x&y co-ordinates
    iOrigLeft = oController.style.left;
    iOrigTop  = oController.style.top;

    // calculate offset
    iDiffX = oEvent.clientX - oController.offsetLeft;
    iDiffY = oEvent.clientY - oController.offsetTop;

    // create event handlers
    EventUtil.addEventHandler(document.body, "mousemove", moveController);
    EventUtil.addEventHandler(document.body, "mouseup", dropController);
}


// move controller
function moveController(oEvent) {
    
    // global variables 
    var oEvent = EventUtil.getEvent();
    var oController = $("controller");

    // set new position
    oController.style.left = oEvent.clientX - iDiffX;
    oController.style.top  = oEvent.clientY - iDiffY;

    // change cursor to hand
    oController.style.cursor = "move";
}


// drop controller
function dropController(oEvent) {

    // global variables
    var oEvent = EventUtil.getEvent();
    var oController = $("controller");

    // drop event handlers
    EventUtil.removeEventHandler(document.body, "mousemove", moveController);
    EventUtil.removeEventHandler(document.body, "mouseup", dropController);

    // change cursor back to normal
    oController.style.cursor = "default";
}




/* ***************************
 function: Move Main Image 
***************************** */


// select image to drag
function engage(oEvent) {
    
    // global variables
    var oEvent = EventUtil.getEvent();
    var oCanvas = $("canvas");
    var oClipwindow = $("clipwindow");

    // calculate offset
    iDiffX = oEvent.clientX - oCanvas.offsetLeft;
    iDiffY = oEvent.clientY - oCanvas.offsetTop;

    // change cursor to hand
    oClipwindow.style.cursor = "move";

    // setup event handlers
    EventUtil.addEventHandler(document.body, "mousedown", mousedown);
    EventUtil.addEventHandler(document.body, "mousemove", move);
    EventUtil.addEventHandler(document.body, "mouseup", drop);

    // disable mousemovehandler as it makes Safari noticably sluggish.
    Event.stopObserving('clipwindow', 'mousemove', handleMoveOverCanvas);
}


// Drag image
function move(oEvent) {
    //    $("tooltip").innerHTML = oEvent.target;
    // global variables
    var oEvent = EventUtil.getEvent();

    var oCanvas = $("canvas");

    // set new position
    oCanvas.style.left = oEvent.clientX - iDiffX;
    oCanvas.style.top  = oEvent.clientY - iDiffY;

    // refresh screen 
    reposition(0,0);
}

function mousedown(oEvent) {
    if (!oEvent) {
        oEvent = EventUtil.getEvent();
    }
    if (Event.element(oEvent).tagName == "IMG") {
        Event.stop(oEvent);
    }
}

// stop draging image
function drop(oEvent) {

    // global variables
    var oEvent = EventUtil.getEvent();
    var oClipwindow = $("clipwindow");

    // turn off events 
    EventUtil.removeEventHandler(document.body, "mousemove", move);
    EventUtil.removeEventHandler(document.body, "mouseup", drop)
    EventUtil.removeEventHandler(document.body, "mousedown", mousedown);

    // Re-instate mousemovehandler.
    Event.observe('clipwindow', 'mousemove', handleMoveOverCanvas);

    // change cursor back to normal 
    oClipwindow.style.cursor = "default";
}

function handleClickOnCanvas(oEvent) {
    var oEvent = EventUtil.getEvent(oEvent);
    var oClipwindow = $("clipwindow");
    var cwPos = Position.cumulativeOffset(oClipwindow);
    var clipWindowX = oEvent.pageX - cwPos[0];
    var clipWindowY = oEvent.pageY - cwPos[1];
    //log("clipWindowX=" +clipWindowX + ", clipWindowY=" + clipWindowY);
    var oCanvas = $("canvas");
    //log(oCanvas.style.left + " " + oCanvas.style.top);
    //log(oCanvas.offsetLeft + " " + oCanvas.offsetTop);
    var cPos = getPositionByStyle(oCanvas);
    //log(cPos);
    var canvasX = (-cPos[0] + clipWindowX) / zoomFactor;
    var canvasY = (-cPos[1] + clipWindowY) / zoomFactor;
    //log(canvasX + " " + canvasY + " " + zoomFactor);
    var node = findNodeAtCoordinate(canvasX,canvasY);
    if (node != null) {
		highliteInViewportAndCache(node, oEvent.shiftKey);
    }
}

function handleMoveOverCanvas(oEvent) {
    var oEvent = EventUtil.getEvent(oEvent);
    var oClipwindow = $("clipwindow");
    var cwPos = Position.cumulativeOffset(oClipwindow);
    var clipWindowX = oEvent.pageX - cwPos[0];
    var clipWindowY = oEvent.pageY - cwPos[1];
    var oCanvas = $("canvas");
    var cPos = getPositionByStyle(oCanvas);
    var canvasX = (-cPos[0] + clipWindowX) / zoomFactor;
    var canvasY = (-cPos[1] + clipWindowY) / zoomFactor;
    //var t1 = new Date().getTime();
    var node = findNodeAtCoordinate(canvasX,canvasY);
    //var t2 = new Date().getTime();
    if (node != null) {
		handleHighliteMouseover(oEvent,node.id);
    }
    //clearLog();
    //log(oEvent.pageX + " " + oEvent.pageY + " " + canvasX + " " + canvasY + " " + zoomFactor + " " + ((t2-t1)/1000) + "s " + node);
}

/* ***********************************************************
 function: Move thumbnail Preview and move main image on Drop
************************************************************** */


// global variables
var iThumbDiffX = 0;
var iThumbDiffY = 0;

// select thumbnail window to move
function engageThumb(oEvent) {

    // global variables
    var oEvent = EventUtil.getEvent();
    var oThumbwindow = $("thumbwindow");

    // calculate offset
    iThumbDiffX = oEvent.clientX - oThumbwindow.offsetLeft;
    iThumbDiffY = oEvent.clientY - oThumbwindow.offsetTop;

    // change cursor to hand
    oThumbwindow.style.cursor = "move";

    // setup event handlers
    EventUtil.addEventHandler(document.body, "mousemove", moveThumb);
    EventUtil.addEventHandler(document.body, "mouseup", releaseThumb);
}


// Drag thumbnail window
function moveThumb(oEvent) {

    // global variables
    var oEvent = EventUtil.getEvent();
    var oThumbwindow = $("thumbwindow");

    // set new position
    oThumbwindow.style.left = oEvent.clientX - iThumbDiffX;
    oThumbwindow.style.top  = oEvent.clientY - iThumbDiffY;
}


// stop draging thumbnail window
function releaseThumb(oEvent) {

    // global variables
    var oEvent = EventUtil.getEvent();
    var oThumbwindow = $("thumbwindow");

    // turn off events
    EventUtil.removeEventHandler(document.body, "mousemove", moveThumb);
    EventUtil.removeEventHandler(document.body, "mouseup", releaseThumb);

    // change cursor back to normal
    oThumbwindow.style.cursor = "default";

    // change position of main image
    moveImage();
    if (highlitesInViewportOnly) reposition(0,0);
    showDiagramsForVisibleComplexAndSetNodesIfNecessary();
}


// reposition main image to match location of thumbwindow
function moveImage() {

    // global variables
    var oThumbwindow   = $("thumbwindow");
    var oCanvas        = $("canvas");
    
    // get current position of thumbwindow
    var iThumbX = parseInt(oThumbwindow.style.left);
    var iThumbY = parseInt(oThumbwindow.style.top);

    // calculate position of main image
    var tx = ((imgx / thumbnailw) * iThumbX) - (((imgx / thumbnailw) * iThumbX) % 1);    // change position (left)
    var ty = ((imgy / thumbnailh) * iThumbY) - (((imgy / thumbnailh) * iThumbY) % 1);    // change position (top)

    // set image position
    oCanvas.style.left   = tx*-1+"px";
    oCanvas.style.top    = ty*-1+"px";

    // refresh screen
    fetchtile();
}




/* **********************************
 author : ph5
 date : 07-11-2005
 function : collapsable sections
************************************ */

// minimise controller
function minController(sDivId) {
    var oController = $(sDivId);
    oController.style.display = "none";
}


// maximise controller
function maxController(sDivId) {
    var oController = $(sDivId);
    oController.style.display = "block";
}


// close controller
function closeController(sController, sIcon) {

    // global variables
    var oController = $(sController);
    var oIcon   = $(sIcon);

    // hide controller
    oController.style.display = "none";

    // show icon
    oIcon.style.display = "block";

}


// open controller
function openController(sController, sIcon) {

    // global variables
    var oController = $(sController);
    var oIcon       = $(sIcon);

    // open controller
    oController.style.display = "block";

    // close minimised controller
    oIcon.style.display = "none";

}






/* ***********************************
 author : ph5 & rmp
 date : 01-10-2005
 function : construct viewer
************************************** */


/////////
// register event handler(s)
//
// Don't do it wholesale - set it only when mouseovering the clip window. Otherwise you'll have all sorts of
// weird effects i.e. not being able to type into a text box.
//document.onkeypress  = keypresshandler;


/*
//////////////////
// perform action if key has been pressed
//
function keypresshandler(oEvent) {

    // get event
    var oEvent = (oEvent)?oEvent:((window.event)?window.event:"");
    if(!oEvent) { return; }

    // stop event performing default action
    if (!oEvent.preventDefault) {
        oEvent.preventDefault = function () {
           this.returnValue = false;
        };
    } else {
        oEvent.preventDefault();
    }

    if(oEvent.keyCode) {
        var key = oEvent.keyCode;         // IE key value
    } else if(oEvent.charCode) {
        var key = oEvent.charCode;        // W3C DOM key value
    } else if(oEvent.which) {
        var key = oEvent.which;           // other browsers key value
    }

    if(key == 38 || key == 63232) {           // up
        reposition(0,+1);
    } else if(key == 40 || key == 63233) {    // down
        reposition(0,-1);
    } else if(key == 37 || key == 63234) {    // left
        reposition(+1,0);
    } else if(key == 39 || key == 63235) {    // right
        reposition(-1,0);
	//    } else if(key == 33 || key == 63276) {    // page up
	//        zoom(-1);
	//    } else if(key == 34 || key == 63277) {    // page down
	//        zoom(+1);
    } else if (key == 43) {
	zoom(-1);
    } else if (key == 45) {
	zoom(+1);
	} else {
    }
    return true;

}

//Safari key codes
//down - 63233
//up - 63232
//left - 63234
//right - 63235
//page up - 63276
//page down - 63277
*/

function keypresshandler(oEvent) {

    // get event
    var oEvent = (oEvent)?oEvent:((window.event)?window.event:"");
    if(!oEvent) { return; }

    // stop event performing default action
    if (!oEvent.preventDefault) {
        oEvent.preventDefault = function () {
           this.returnValue = false;
        };
    } else {
        oEvent.preventDefault();
    }

    var key;
    if(oEvent.keyCode) {
        key = oEvent.keyCode;         // IE key value
    } else if(oEvent.charCode) {
        key = oEvent.charCode;        // W3C DOM key value
    } else if(oEvent.which) {
        key = oEvent.which;           // other browsers key value
    }

    if(key == 38 || key == 63232) {           // up
        reposition(0,+1);
    } else if(key == 40 || key == 63233) {    // down
        reposition(0,-1);
    } else if(key == 37 || key == 63234) {    // left
        reposition(+1,0);
    } else if(key == 39 || key == 63235) {    // right
        reposition(-1,0);
	//    } else if(key == 33 || key == 63276) {    // page up
	//        zoom(-1);
	//    } else if(key == 34 || key == 63277) {    // page down
	//        zoom(+1);
    } else if (key == 43) {
	zoom(-1);
    } else if (key == 45) {
	zoom(+1);
	} else {
    }
    return true;
}

/////////////
// load chosen image into page
//
function reloadimage() {
    // setup and populate tiled image
    initcanvas();
    fetchtile();
    //resetZoomLevelIndicator();
}



/////////
// name: initcanvas
// function: populates the canvas div with empty image tiles
//
function initcanvas() {

    // calculate image (scaled to zoom level)
    var scaling = 1/Math.pow(2,(z - 1));
    imgx    = Math.floor(imgw*scaling);
    imgy    = Math.floor(imgh*scaling);
    //    imgx    = (imgdim[imgid][0]*scaling) - ((imgdim[imgid][0]*scaling) % 1);
    //    imgy    = (imgdim[imgid][1]*scaling) - ((imgdim[imgid][1]*scaling) % 1);

    // calculate number of tiles needed (x & y)
    var modx    = imgx % tilewidth;
    var mody    = imgy % tileheight;
    ntilesx     = (imgx-modx)/tilewidth;
    ntilesy     = (imgy-mody)/tileheight;
    if (imgx-modx < imgx)  { ntilesx++ };
    if (imgx<tilewidth)    { ntilesx=1 };
    if (imgy-mody < imgy)  { ntilesy++ };
    if (imgy<tileheight)   { ntilesy=1 };

    // calculate size of image in tiles
    var maxx  = ntilesx*tilewidth;
    var maxy  = ntilesy*tileheight;

    // decide on position of image when first displayed (middle and center)
    startx      = -(imgx/2) + (iClipWidth/2);
    starty      = -(imgy/2) + (iClipHeight/2);

    // place enlarged thumb image in the background and set the size
    var oCanvas = $('canvas');
    var bgimg = $("backgroundimage");
    if (!bgimg) {
	bgimg = Element.extend(document.createElement('img'));
	bgimg.id = "backgroundimage";
	bgimg.src = imgdir+"/bg.png";
	oCanvas.appendChild(bgimg);
    }
    bgimg.setStyle({width:imgx+"px",height:imgy+"px"});
    var tiles = $("tiles");
    if (!tiles) {
	tiles = Element.extend(document.createElement('div'));
	tiles.id = "tiles";
	tiles.setStyle({position:"absolute",top:"0px",left:"0px"});
	oCanvas.appendChild(tiles);
    }
    var entitydiagrams = $("entitydiagrams");
    if (!entitydiagrams) {
	entitydiagrams = Element.extend(document.createElement('div'));
	entitydiagrams.id = "entitydiagrams";
	//entitydiagrams.setStyle({position:"absolute",top:"0px",left:"0px",zIndex:90,display:"none"});
	entitydiagrams.setStyle({position:"absolute",top:"0px",left:"0px",display:"none"});
	oCanvas.appendChild(entitydiagrams);
    }
    $A(entitydiagrams.childNodes).invoke('remove');
    var overlay = $("canvasoverlay");
    if (!overlay) {
	overlay = Element.extend(document.createElement('div'));
	overlay.id = "canvasoverlay";
	//overlay.setStyle({position:"absolute",top:"0px",left:"0px",zIndex:100});
	overlay.setStyle({position:"absolute",top:"0px",left:"0px"});
	oCanvas.appendChild(overlay);
    }
    $A(overlay.childNodes).invoke('remove');

    oCanvas.style.width  = maxx+"px";
    oCanvas.style.height = maxy+"px";
}


function setViewerSize() {
    var oClipwindow   = $("clipwindow");
    var leftpanel = $("leftpanel");
    var details = $("details");
    /* 
     * Determine the browser's viewport size: insert a dummy div and determine its.
     * Have to do it this way because of IE7 which behaves very difefrently from, 
     * say, Firefox when given a div with top, bottom, left and right values.
     */
    var dummy = Element.extend(document.createElement('div'));  
    dummy.setStyle({position:'absolute',right:'1px',bottom:'1px',fontSize:'1px',height:'1px'});
    dummy.innerHTML = '<!-- -->';
    document.body.insertBefore(dummy, document.body.firstChild);
    var dims = Position.cumulativeOffset(dummy);
    dummy.remove();
    var availableHeight = dims[1] - 13 - Position.cumulativeOffset(oClipwindow)[1];
    // 4 is the height of the little triangle for opening/closing details
    if ((details != null) && details.visible()) {
    	details.style.height = Math.floor(availableHeight * 0.4) + "px";
        oClipwindow.style.height = (Math.ceil(availableHeight * 0.6) - 4) + "px";
    } else {
        oClipwindow.style.height = (availableHeight - 4) + "px";
    }
    iClipWidth = oClipwindow.getWidth();
    iClipHeight = oClipwindow.getHeight();
    positionThumbnail();
    if (leftpanel != null) {
        leftpanel.style.height = iClipHeight+"px";
    }
}


function positionThumbnail() {
    var oClipwindow   = $("clipwindow");
    var pos = Position.cumulativeOffset(oClipwindow);
    var oThumbnailframe   = $("thumbnailframe");
    var oThumbnail   = $("thumbnail");
    // calculate height of thumbnail
    var ratio = thumbnailw/imgw;
    var thumbnail = imgwww+imgdir+"/thumb.png";
    thumbnailh = (Math.ceil(ratio*imgh));
    oThumbnail.style.backgroundImage = "url("+thumbnail+")";
    oThumbnail.style.width  = thumbnailw+"px";
    oThumbnail.style.height = thumbnailh+"px";
    oThumbnailframe.style.width  = (thumbnailw + 5) + "px";
    oThumbnailframe.style.height = (thumbnailh + 5)+ "px";
}

////////
// change image sources
//
function swap(sImgId, sImgName) {
    var oImg = $(sImgId);
    if (oImg) {
        oImg.setAttribute("src", sImgName);
    }
}

///////////
// populate canvas with tiled images. Create the tile divs if necessary.
function fetchtile() {

    // get current position
    var oCanvas = $("canvas");
    var oTiles = $("tiles");
    var iX = parseInt(oCanvas.style.left);
    var iY = parseInt(oCanvas.style.top);

    // find the top left tile in the clip
    var iTileX   = Math.floor(-iX / tilewidth);
    var iTileY   = Math.floor(-iY / tileheight);
    // correction in case we are at the top left corner of the whole image already
    if (iTileX < 0)
	iTileX = 0;
    if (iTileY < 0)
	iTileY = 0;
    // determine bottom and right edge of the are to tile
    var rightEdge  = -iX + iClipWidth;
    var bottomEdge = -iY + iClipHeight;
    // fetch images for each tile in viewer
    for (var ty=iTileY; ty*tileheight < bottomEdge; ty++) {
        for (var tx=iTileX; tx*tilewidth < rightEdge; tx++) {
            if(tx>=0 && tx<ntilesx && ty>=0 && ty<ntilesy) {
                var iTileId = tx+"x"+ty;
                var oTile = $(iTileId);
                if(! oTile) {
		    oTile = document.createElement("div");
		    oTile.setAttribute("id", iTileId);
		    oTile.setAttribute("class", "tile");
		    oTile.style.top = ty * tileheight + "px";
		    oTile.style.left = tx * tilewidth + "px";
		    oTile.style.width = tilewidth +  "px";
                    oTile.style.height = tileheight +  "px";
                    oTile.style.position = 'absolute';
		    oTiles.appendChild(oTile);
		}
		var uri = "url("+imgwww+imgdir+"/"+z+"/"+iTileId+".png)";
		if(oTile.style.backgroundImage != uri) {
		    oTile.style.backgroundImage = uri;
                }
            }
        }
    }
}


//////////
// move image back to the center of the viewing area
//
function recentre() {
    var oCanvas        = $("canvas");
    oCanvas.style.left = startx+"px";
    oCanvas.style.top  = starty+"px";
    reposition(0,0);
}




/////////////
// move image
//
function reposition(fx, fy) {
    iClipWidth = $('clipwindow').getWidth();
    // get current image position
    var oCanvas = $("canvas");
    var iX = parseInt(oCanvas.style.left);
    var iY = parseInt(oCanvas.style.top);

    // change image position values
    iX += Math.round(dx*fx);
    iY += Math.round(dy*fy);

    // set image position values
    oCanvas.style.top  = iY+"px";
    oCanvas.style.left = iX+"px";

    // change position/size of thumbnail window (red box) according to new image position
    var iTw = Math.round((iClipWidth / imgx) * thumbnailw);  // change size of red box (width)
    var iTh = Math.round((iClipHeight / imgy) * thumbnailh); // change size of red box (height)
    var iTx = Math.round((thumbnailw / imgx) * iX);          // change position of red box (left)
    var iTy = Math.round((thumbnailh / imgy) * iY);          // change position of red box (top)

    // set thumbnail window (red box) values
    var oThumbnail          = $("thumbwindow"); // select thumbnail div
    oThumbnail.style.left   = -iTx+"px";        // set left : negate value so red box moves in opposite direction
    oThumbnail.style.top    = -iTy+"px";        // set top  : negate value so red box moves in opposite direction
    oThumbnail.style.width  = iTw+"px";         // set width
    oThumbnail.style.height = iTh+"px";         // set height

   	//
   	viewportLeft = -iX / zoomFactor;
    viewportTop = -iY / zoomFactor;
    viewportRight = (-iX + iClipWidth) / zoomFactor;
    viewportBottom = (-iY + iClipHeight) / zoomFactor;

    // refresh screen image tiles
    fetchtile();
    showDiagramsForVisibleComplexAndSetNodesIfNecessary();

    //Required for "lazy" highliting of things in viewport only
    if (highlitesInViewportOnly) {
    	highliteCachedNodes();
    }
}



//////////////
// change zoom level
//
function zoom1(fz) {

    // change zoom level
    z += dz*fz;

    // set min and max zoom levels
    if (z<1) {
        z = 1;
    } else if (z>maxz) {
        z = maxz;
    }

    // find current position
    var oCanvas = $("canvas");
    var iX = parseInt(oCanvas.style.left);
    var iY = parseInt(oCanvas.style.top);

    // old image size
    var iOldX = imgx;
    var iOldY = imgy;

    // reload image
    reloadimage();

    // new image size
    var iNewX = imgx;
    var iNewY = imgy;

    // find pixel from image in central point of viewer
    var iPixOldX = ((iX)*-1) + (iClipWidth/2);
    var iPixOldY = ((iY)*-1) + (iClipHeight/2);

    // find where pixel has moved to in next image
    var iPixNewX = (iNewX/iOldX) * iPixOldX;
    var iPixNewY = (iNewY/iOldY) * iPixOldY;

    // change position values according to degree of movement of pixel
    var iNewPosX = ( ((iPixNewX-iPixOldX)*-1) - (((iPixNewX-iPixOldX)*-1) % 1) ) + iX;
    var iNewPosY = ( ((iPixNewY-iPixOldY)*-1) - (((iPixNewY-iPixOldY)*-1) % 1) ) + iY;

    // set new position
    oCanvas.style.left = iNewPosX+"px";
    oCanvas.style.top  = iNewPosY+"px";

    // refresh page with new position
    reposition(0,0);

	if (!highlitesInViewportOnly) {
    	highliteCachedCoordinates();
	}
}

function zoom(fz) {
	// For some reason passing z to 'fire' seems to turn it into string.
    z = parseInt(z) + dz*fz;
    zoomChange.fire(z);
}

function setZoomLevel(zl) {

    // change zoom level
    z = zl;

    // set min and max zoom levels
    if (z<1) {
        z = 1;
    } else if (z>maxz) {
        z = maxz;
    }
	zoomFactor = calculateZoomFactor(z);
    // find current position
    var oCanvas = $("canvas");
    var iX = parseInt(oCanvas.style.left);
    var iY = parseInt(oCanvas.style.top);
	if (isNaN(iX) || isNaN(iY)) {
		//log("No canvas coordinates yet.");
		return;
	} 

    // old image size
    var iOldX = imgx;
    var iOldY = imgy;

    // reload image
    reloadimage();

    // new image size
    var iNewX = imgx;
    var iNewY = imgy;

    // find pixel from image in central point of viewer
    var iPixOldX = ((iX)*-1) + (iClipWidth/2);
    var iPixOldY = ((iY)*-1) + (iClipHeight/2);

    // find where pixel has moved to in next image
    var iPixNewX = (iNewX/iOldX) * iPixOldX;
    var iPixNewY = (iNewY/iOldY) * iPixOldY;

    // change position values according to degree of movement of pixel
    var iNewPosX = ( ((iPixNewX-iPixOldX)*-1) - (((iPixNewX-iPixOldX)*-1) % 1) ) + iX;
    var iNewPosY = ( ((iPixNewY-iPixOldY)*-1) - (((iPixNewY-iPixOldY)*-1) % 1) ) + iY;

    // set new position
    oCanvas.style.left = iNewPosX+"px";
    oCanvas.style.top  = iNewPosY+"px";

    // refresh page with new position
    reposition(0,0);

    if (!highlitesInViewportOnly) {
    	highliteCachedCoordinates();
    }
}

function calculateZoomFactor (zl) {
    return 1/Math.pow(2,(zl - 1));
}

/*
function resetZoomLevelIndicator () {
    for (var i = 1; i <= maxz; i++) {
	var zlo = $("zoom_level_indicator_" + i);
	if (zlo != null) {
	    if (i == z) {
		zlo.style.backgroundColor = 'transparent';
	    } else {
		zlo.style.backgroundColor = 'navy';
	    }
	}
    }
    zoomFactor = calculateZoomFactor(z);
}*/

function findCurrentPosition () {
    var oCanvas = $("canvas");
    var iX = parseInt(oCanvas.style.left);
    var iY = parseInt(oCanvas.style.top);
    return {'x' : iX, 'y' : iY};
}

function centreAndHighliteOnCoordinate (x, y, w, h) {
    centreOnCoordinate(x, y);
    highliteCoordinate(x, y, w, h, false);
}

function centreOnCoordinate (x, y) {
	x *= zoomFactor;
    y *= zoomFactor;
    var canvas = $('canvas');
    var cx = -parseInt(canvas.getStyle('left')) + iClipWidth/2;
    var cy = -parseInt(canvas.getStyle('top')) + iClipHeight/2;
    var dx = cx - x;
    var dy = cy - y;
    if ((dx != 0) || (dy != 0)) {
		animatedReposition(dx,dy);
    }
}

function highliteCoordinate (x, y, w, h, keepPrevious) {
    x *= zoomFactor;
    y *= zoomFactor;
    w *= zoomFactor;
    h *= zoomFactor;
    var oCO = $("canvasoverlay");
//    var tmp = '<div class="highlite" style="position:absolute;top:' + (y-highliteBorderThickness) + ';left:' + (x-highliteBorderThickness) + ';width:' + w + 'px;height:'+ h + 'px;" onmouseover=""></div>';
    if (isIE) {
        w += highliteBorderThickness;
        h += highliteBorderThickness;
    } else {
        x -= highliteBorderThickness;
        y -= highliteBorderThickness;
    }
    var tmp = '<div class="highlite" style="position:absolute;top:' + y + ';left:' + x + ';width:' + w + 'px;height:'+ h + 'px;" onmouseover=""><!-- --></div>';
    if (keepPrevious) {
	oCO.innerHTML += tmp;
    } else {
	oCO.innerHTML = tmp;
    }
}

function clearHighlites () {
	highlitedCoordinates.map(function(n){n.isHighlited = false;});
    highlitedCoordinates.length = 0;
    var oCO = $("canvasoverlay");
    oCO.innerHTML = '';
    var to =  $("thumbnailoverlay");
    to.innerHTML = '';
}

function log (msg) {
    var oLog = $("log");
    if (oLog != null) {
		//oLog.innerHTML += msg + "<BR/>\n";
		oLog.innerHTML = msg + "<BR/>\n" + oLog.innerHTML;
    }
}

function clearLog () {
     var oLog = $("log");
    if (oLog != null) {
	oLog.innerHTML = "";
    }
}

function toString (obj) {
    var out = '';
    for (var k in obj) {
	out += k + "\t" + obj[k] + "\n";
    }
    return out;
};


var representedInstances = null;
var highlitedCoordinates = new Array();
var orderedRepresentedInstances = new Array();
var eventhierarchydtree = null;
var nodes = null;
var node2highlite = new Object();

function loadCoordinates () {
    new Ajax.Request(imgdir + '/coordinates.json',{
        method: 'get',
        onSuccess: function(transport) {
	    	var t1 = new Date().getTime();
	    	representedInstances = eval('(' + transport.responseText + ')');
	    	var t2 = new Date().getTime();
	    	for (var k in representedInstances) {
				orderedRepresentedInstances.push(representedInstances[k]);
	    	}
	    	var t3 = new Date().getTime();
	    	//alert("Eval: " + ((t2-t1)/1000) + "s\nSort: " + ((t3-t2)/1000) + "s\n");
        }
    });
}

function loadOrderedCoordinates (handleQueryInURIFlag) {
    new Ajax.Request(imgdir + '/orderedcoordinates.json',{
		method: 'get',
		onSuccess: function(transport) {
	     	orderedRepresentedInstances = eval('(' + transport.responseText + ')');
			processOrderedRepresentedInstances(orderedRepresentedInstances);
			// Has to be done here to ensure that coordinates have been loaded.
			if (handleQueryInURIFlag) handleQueryInURI();
		}
    });
}

function processOrderedRepresentedInstances (orderedRepresentedInstances) {
	representedInstances = new Object();
	nodes = new Object();
	for (var i = 0, l = orderedRepresentedInstances.length; i < l; ++i) {
		 var n = orderedRepresentedInstances[i];
		 representedInstances[n.id] = n;
		 var coords = n.coords;
		 for (var j = 0, m = coords.length; j < m; ++j) {
		     coords[j].userObject = n;
		     nodes[coords[j].id] = coords[j];
		 }
	}
}

/*
This method is slower than loadCoordinates. In Firefox 14s vs 4s, in Safari 17s vs 9s.
In both methods in Safari it's the sorting bit which is slow.
Firefox may ask if you want to stop execution of an unresponsive script.
*/
function loadCoordinatesAsXML () {
    var req = new XMLHttpRequest();
    var t0 = new Date().getTime();
    req.onreadystatechange = function () {
	if (req.readyState == 4) {
	    if (req.status == 200) {
		var t1 = new Date().getTime();
		var xmlDoc = req.responseXML;
		representedInstances = new Object();
		var instanceEls = xmlDoc.getElementsByTagName('instance');
		for (var i = 0; i < instanceEls.length; i++) {
		    var instanceEl = instanceEls[i];
		    var instance = xml2jso(instanceEl);
		    var coordsEls = instanceEl.getElementsByTagName('coords');
		    var coords = new Array();
		    for (var j = 0; j < coordsEls.length; j++) {
			var coordsEl = coordsEls[j];
			coords.push(xml2jso_number(coordsEl));
		    }
		    instance.coords = coords;
		    orderedRepresentedInstances.push(instance);
		    representedInstances[instance.id] = instance;
		}
		var t2 = new Date().getTime();
		orderedRepresentedInstances.sort(function(a,b){return a.coords[0].x - b.coords[0].x});
		var t3 = new Date().getTime();
		alert("Get: " + ((t1-t0)/1000) + "s\nParse: " + ((t2-t1)/1000) + "s\nSort: " + ((t3-t2)/1000) + "s\nAll: " + ((t3-t0)/1000));
	    } else {
		alert("There was a problem retrieving the coordinates:\n" +
		      req.statusText);
	    }
	}
    };
    //req.open("GET", '/cgi-bin/entitylevelview/elementcoordinates', true);
    req.open("GET", imgdir + '/coordinates.xml', true);
    req.send(null);
}

function xml2jso (el, obj) {
    if (obj == null) {
	obj = new Object();
    }
    var atts = el.attributes;
    for (var j = 0; j < atts.length; j++) {
	//log(atts.item(j).name + " => " +  atts.item(j).value);
	obj[atts.item(j).name] = atts.item(j).value;
    }
    return obj;
}

function xml2jso_number (el, obj) {
    if (obj == null) {
	obj = new Object();
    }
    var atts = el.attributes;
    for (var j = 0; j < atts.length; j++) {
	obj[atts.item(j).name] = new Number(atts.item(j).value);
    }
    return obj;
}

function highliteNodesWithId (dbids, keepPrevious) {
    keepPrevious || clearHighlites();
    if (nodes != null) {
		var l = dbids.length;
		if (l > 100) {
	    	l = 100;
	    	log("Highliting 1st " + l + " nodes only from total " + dbids.length);
		}
		for (var i = 0; i < l; ++i) {
	    	var n = nodes[dbids[i]];
	    	if (n != null) {
				cacheHighlitedCoordinate(n);
				highliteCoordinate(n.x, n.y, n.w, n.h, true);
				highliteThumbCoordinate(n.x, n.y);
	    	} else {
				log("No node with id " + dbids[i]);
	    	}
		}
    } else {
		log("No coordinate data!");
    }
}

function highliteNodeWithId (dbid, keepPrevious) {
    if (nodes != null) {
	var n = nodes[dbid];
	if (n != null) {
	    keepPrevious || clearHighlites();
	    cacheHighlitedCoordinate(n);
	    highliteCoordinate(n.x, n.y, n.w, n.h, true);
	    highliteThumbCoordinate(n.x, n.y);
	} else {
	    log("No node with DB_ID " + dbid);
	}
    } else {
	log("No coordinate data!");
    }
}

function highliteNode (node) {
    var oCO = $("canvasoverlay");
	/*if (!keepPrevious) {
		cCO.descendants().invoke('remove');
	}*/
    var x = zoomFactor * node.x;
    var y = zoomFactor * node.y;
    var w = zoomFactor * node.w;
    var h = zoomFactor * node.h;
    if (isIE) {
        w += highliteBorderThickness;
        h += highliteBorderThickness;
    } else {
        x -= highliteBorderThickness;
        y -= highliteBorderThickness;
    }
	var node_id  = 'hl_' + node.id;
	var hl = Element.extend(document.createElement('div'));
    hl.innerHTML = "&nbsp;";
    hl.id = node_id;
    hl.className = "highlite";
    hl.setStyle({position:"absolute",top:y+"px",left:x+"px",width:w+"px",height:h+"px"});
    hl.setAttribute('onmouseover','handleHighliteMouseover(event,' + node.id+')');
    hl.setAttribute('onmouseout','handleHighliteMouseout(event,' + node.id+')');
    hl.setAttribute('onmousemove','handleHighliteMouseover(event,' + node.id+')');
    $("canvasoverlay").appendChild(hl);
    node.isHighlited = true;
    return hl;
}

function highliteNodeIfInViewPort (node) {
	if (isInViewPort(node)) {
	    var oCO = $("canvasoverlay");
	    var x = zoomFactor * node.x;
	    var y = zoomFactor * node.y;
	    var w = zoomFactor * node.w;
	    var h = zoomFactor * node.h;
	    if (isIE) {
	        w += highliteBorderThickness;
	        h += highliteBorderThickness;
	    } else {
	        x -= highliteBorderThickness;
	        y -= highliteBorderThickness;
	    }
	    var node_id  = 'hl_' + node.id;
	    var hl = $(node_id);
	    if (hl == null) {
			var hl = Element.extend(document.createElement('div'));
		    hl.innerHTML = "&nbsp;";
		    hl.id = node_id;
	    	hl.className = "highlite";
	    	hl.setAttribute('onmouseover','handleHighliteMouseover(event,' + node.id+')');
	    	hl.setAttribute('onmouseout','handleHighliteMouseout(event,' + node.id+')');
	    	hl.setAttribute('onmousemove','handleHighliteMouseover(event,' + node.id+')');
	    	$("canvasoverlay").appendChild(hl);
	    }
	    hl.setStyle({position:"absolute",top:y+"px",left:x+"px",width:w+"px",height:h+"px"});
	    return hl;
	}
}

function highliteInViewportAndCache (node, keepPrevious) {
    if (!node.isHighlited) {
		keepPrevious || clearHighlites();
		if (highlitesInViewportOnly) {
			highliteNodeIfInViewPort(node);
		} else {
			highliteNode(node);
		}
		highliteThumbCoordinate(node.x, node.y);
		cacheHighlitedNode(node);
    }
}

function highliteAndCache (node) {
    if (!node.isHighlited) {
		var hl = highliteNode(node);
		highliteThumbCoordinate(node.x, node.y);
		cacheHighlitedNode(node);
    }
}

function highliteNodesWithInstanceId (dbid, keepPrevious) {
    if (representedInstances != null) {
	var n = representedInstances[dbid];
	if (n != null) {
	    keepPrevious || clearHighlites();
	    var coords = n.coords;
	    for (var i = 0; i < coords.length; i++) {
		//coords[i].userObject = n;
		highliteInViewportAndCache(coords[i], true);
	    }
	} else {
	    log("No node with DB_ID " + dbid);
	}
    } else {
	alert("No coordinate data!");
    }
}

function cacheHighlitedCoordinate (coords) {
    highlitedCoordinates.push(coords);
}

function cacheHighlitedNode (node) {
    highlitedCoordinates.push(node);
}

function highliteCachedCoordinates () {
    for (var i = 0; i < highlitedCoordinates.length; i++) {
		var coords = highlitedCoordinates[i];
		if (coords.userObject != null) {
		    highliteNode(coords);
		} else {
		    highliteCoordinate(coords.x, coords.y, coords.w, coords.h, true);
		}
    }
}

function highliteCachedNodes_1 () {
    for (var i = 0; i < highlitedCoordinates.length; i++) {
		var node = highlitedCoordinates[i];
		highliteNode(node, true);
    }
}

function highliteCachedNodes () {
	var t1 = new Date().getTime();
    var c = 0;
    for (var i = 0; i < highlitedCoordinates.length; i++) {
		var node = highlitedCoordinates[i];
		var hl = highliteNodeIfInViewPort(node);
		if (hl != null) c++;
    }
    var t2 = new Date().getTime();
    //log("Highlited " + c + " nodes in " + (t2 - t1)/1000 + "s");
}

function findNodesWithNameMatching (str) {
    //log("findNodesWithNameMatching(" + str + ")");\
    str = decodeURIComponent(str);
	str = str.replace(/([^\w\d\s])/g, "\\$1");
	log(str);
    var re = new RegExp(str, "i");
    var out = new Array();
    for (var i = 0; i < orderedRepresentedInstances.length; i++) {
		var node = orderedRepresentedInstances[i];
		if (node.name.match(re) != null) {
	    	out.push(node);
		}
    }
    return out;
}

function highliteThumbCoordinate (x, y) {
	x = Math.round(x / imgw * thumbnailw - 1);
	y = Math.round(y / imgh * thumbnailh - 1);
	//log(x + ", " + y);
	var node_id  = 'thl_' + Math.round(x/2) + '_' + Math.round(y/2);
	var thl = $(node_id);
	if (thl == null) {
		thl = Element.extend(document.createElement('div'));
    	thl.innerHTML = "&nbsp;";
    	thl.id = node_id;
    	thl.className = "thumbhighlite";
    	thl.setStyle({position:"absolute",top:y+"px",left:x+"px"});
    	$("thumbnailoverlay").appendChild(thl);
	}
}

function getPositionByStyle(obj) {
    return [parseInt(obj.style.left),parseInt(obj.style.top)];
}

function findNodeAtCoordinate (x, y) {
    var tolerance = 10;
    //log(x + ", " + y);
    for (var id in nodes) {
	var node = nodes[id];
	if ((x > node.x-tolerance) && 
	    (x < node.x+node.w+tolerance) && 
	    (y > node.y-tolerance) && 
	    (y < node.y+node.h+tolerance)) {
	    //log("Found: " + node.id + "\n" + toString(node));
	    return node;
	}
    }
    //log("Nothing found");
}

function loadDetails1 (dbid) {
    //log(dbid);
    var details = getDetailsDiv();
    var ol = overlayElementWithTimeIndicator(details);
    var req = new XMLHttpRequest();
    req.onreadystatechange = function () {
		if (req.readyState == 4) {
		    if (req.status == 200) {
				var rtxt = req.responseText;
				if (rtxt) {
					var o = eval(rtxt);
					processTitle(o.title);
					processDetails(o.details);
					updateLocationHref(dbid);
				} else {
				    log("No responseText?!")
				}
		    } else {
				details.innerHTML = "<PRE>There was a problem loading the details of instance with DB_ID " +dbid+ ":\n" + req.statusText + "</PRE>";
		    }
		    if (ol) ol.remove();
		}
    };
    req.open("GET", '/cgi-bin/entitylevelview/details?DB=' + db + '&FOCUS_SPECIES_ID='+ focus_species_id +'&ID='+dbid, true);
    req.send(null);
}

function loadDetails (dbid) {
    var details = getDetailsDiv();
    var ol = overlayElementWithTimeIndicator(details);
    new Ajax.Request('/cgi-bin/entitylevelview/details?', {
    	parameters: {'ID': dbid, 'DB': db, 'FOCUS_SPECIES_ID': focus_species_id},
        method: 'get',
        onSuccess: function(transport) {
	    	var rtxt = transport.responseText;
			if (rtxt) {
				var o = eval(rtxt);
				processTitle(o.title);
				processDetails(o.details);
				updateLocationHref(dbid);
				current_instance_id = dbid;
			} else {
			    log("No responseText?!")
			}
		},
		onFailure: function(transport) {
	    	$('details').innerHTML = "<PRE>There was a problem loading the details of instance with DB_ID " +dbid+ ":\n" + transport.statusText + "</PRE>";
		},
		onComplete: function(transport) {
			if (ol) ol.remove();
		}   
    });
}

function loadEventHierarchy1 (dbid) {
    //var lp = $("leftpanel");
    var lp = $("hierarchytab");
    var ol = overlayElementWithTimeIndicator(lp);
    var req = new XMLHttpRequest();
    req.onreadystatechange = function () {
	if (req.readyState == 4) {
	    if (req.status == 200) {
		var rtxt = req.responseText;
		if (rtxt && (rtxt != " ")) {
		    var a = eval(rtxt);
		    a = a.collect(function(el){return decodeURIComponent(el)});
		    lp.innerHTML = a[0].replace(/<script.*?>(\n|\r|.)*?<\/script>/,eval(a[1]));
		    eventhierarchydtree.openToDbId(dbid,true);
		    var links = lp.getElementsBySelector('a');
		    tweakEventHierarchyLinks(links);
		} else {
		    if (eventhierarchydtree) eventhierarchydtree.deselectAllNodes();
		}
	    } else {
		lp.innerHTML = "There was a problem loading the eventhierarchy of instance with DB_ID " +dbid+ ":<BR />" + req.statusText;
	    }
	    if (ol) ol.remove();
	}
    };
    req.open("GET", '/cgi-bin/entitylevelview/eventhierarchy?DB='+db+'&ID='+dbid, true);
    req.send(null);
}

function loadEventHierarchyForNode (node) {
	//log("loadEventHierarchyForNode(" + node.userObject.name +")");
    var lp = $("hierarchytab");
    var ol = overlayElementWithTimeIndicator($("leftpanel"));
    new Ajax.Request('/cgi-bin/entitylevelview/eventhierarchy', {
    	parameters: {'ID': node.id, 'DB': db},
        method: 'get',
        onSuccess: function(transport) {
			var rtxt = transport.responseText;
			if (rtxt && (rtxt != " ")) {
			    var a = eval(rtxt);
			    /*if ((eventhierarchydtree != null) && eventhierarchydtree.containsNodesWithDbIDs(a[2])) {
					eventhierarchydtree.deselectAllNodes();
					eventhierarchydtree.openToDbIds(a[2],true);
			    } else {*/
					a[0] = decodeURIComponent(a[0]);
					a[1] = decodeURIComponent(a[1]);
					lp.innerHTML = a[0].replace(/<script.*?>(\n|\r|.)*?<\/script>/,eval(a[1]));
					eventhierarchydtree.closeAll();
					eventhierarchydtree.openToDbIds(a[2],true);
					var links = lp.getElementsBySelector('a');
					tweakEventHierarchyLinks(links);
			    //}
			} else {
			    if (eventhierarchydtree) eventhierarchydtree.deselectAllNodes();
			}
		},
		onFailure: function(transport) {
			var uo = node.userObject;
			lp.innerHTML = "There was a problem loading the eventhierarchy of instance ["+uo.cls+":"+uo.id+"] "+node.userObject.name+ ":<BR />" + req.statusText;
		},
		onComplete: function(transport) {
			if (ol) ol.remove();
		}
	});
}

function loadEventHierarchyForNode1 (node) {
    //var lp = $("leftpanel");
    var lp = $("hierarchytab");
    var ol = overlayElementWithTimeIndicator($("leftpanel"));
    var req = new XMLHttpRequest();
    req.onreadystatechange = function () {
		if (req.readyState == 4) {
		    if (req.status == 200) {
				var rtxt = req.responseText;
				if (rtxt && (rtxt != " ")) {
				    var a = eval(rtxt);
				    if ((eventhierarchydtree != null) && eventhierarchydtree.containsNodesWithDbIDs(a[2])) {
						eventhierarchydtree.deselectAllNodes();
						eventhierarchydtree.openToDbIds(a[2],true);
				    } else {
						a[0] = decodeURIComponent(a[0]);
						a[1] = decodeURIComponent(a[1]);
						lp.innerHTML = a[0].replace(/<script.*?>(\n|\r|.)*?<\/script>/,eval(a[1]));
						eventhierarchydtree.closeAll();
						eventhierarchydtree.openToDbIds(a[2],true);
						var links = lp.getElementsBySelector('a');
						tweakEventHierarchyLinks(links);
				    }
				} else {
				    if (eventhierarchydtree) eventhierarchydtree.deselectAllNodes();
				}
		    } else {
				var uo = node.userObject;
				lp.innerHTML = "There was a problem loading the eventhierarchy of instance ["+uo.cls+":"+uo.id+"] "+node.userObject.name+ ":<BR />" + req.statusText;
		    }
		   	//setLeftPanelVisibilityAccordingToContent();
		    if (ol) ol.remove();
		}
    };
    req.open("GET", '/cgi-bin/entitylevelview/eventhierarchy?DB='+db+'&ID='+node.id, true);
    req.send(null);
}

function highlitePathwayComponents (dbid) {
    var ol = overlayElementWithTimeIndicator($("clipwrapper"));
    new Ajax.Request('/cgi-bin/entitylevelview/pathwaycomponents',{
    	parameters: {'ID': dbid, 'DB': db},
        method: 'get',
        onSuccess: function(transport) {
	    	var rtxt = transport.responseText;
	    	var a = eval(rtxt);
	    	clearHighlites();
	    	log(a.length + " nodes");
	    	a.map(function(id){highliteNodesWithInstanceId(id,true);});
	    	centreOnCoordinate(highlitedCoordinates[0].x, highlitedCoordinates[0].y);
		},
		onFailure: function(transport) {
	    	alert(transport.statusText);
		},
		onComplete:  function(transport) {
	    	if (ol) ol.remove();
		}	     
    });
}

function handleHighliteMouseover (event, dbid) {
    var id = "tip_" + dbid;
    if (!$(id)) {
		//log(dbid);
		// For some reason Safari often ignores the request to remove the popups on mouseout.
		// Hence the need to "manually" remove them here.
		$("canvasoverlay").getElementsBySelector('div.tip').invoke('remove');
		var tip = Element.extend(document.createElement('div'));
		tip.className = 'tip';
		tip.id = id;
		var node = nodes[dbid];
		var w = node.w;
		var h = node.h;
		var nw, nh;
		if (node.cls == "ReactionVertex") {
		    nw = 100;
		    nh = 100;
		} else {
		    nw = 1.5 * w;
		    nh = 2 * h;
		}
		tip.innerHTML = node.userObject.name;
		tip.setStyle({width:nw+"px",display:'none'});
		$("canvasoverlay").appendChild(tip);
		Event.observe(tip, 'mouseout', function(event){
		    var el = Event.element(event);
		    // Safari often gives the text node in div as the element. Hence the
		    // need to look into ancestors (OK, parent node would prolly be enough)
		    Element.remove([el, Element.ancestors(el)].find(function(a){return (a.hasClassName('tip')) ? true : false;}));
		});
		Event.observe(tip, 'click', function(event){
		    //alert(Event.element(event).inspect());
		    Event.stop(event);
		    highliteNodeWithId(dbid, false);
		    loadDetails(node.userObject.id);
		    highliteNodeInEventHierarchy(node,true);
		});
		var border_w = 5;
		nh = tip.getHeight();
		//alert(h + "\t" + nh);
		var x = node.x;
		var y = node.y;
		x = x * zoomFactor - border_w - (nw- w*zoomFactor)/2;
		y = y * zoomFactor - border_w - (nh- h*zoomFactor)/2;
		tip.setStyle({left:x+"px",top:y+"px",opacity:0.85,height:nh+"px",border:border_w+"px solid #DCDCDC",background:"#DCDCDC"});
		//tip.show();
		new Effect.Appear(tip,{duration:0.5,to:0.85});
		bringElementIntoViewPort(tip);
    }
}

function bringElementIntoViewPort (el) {
    el = $(el);
    var dims = Element.getDimensions(el);
    var ex = parseInt(el.getStyle('left'));
    var ey = parseInt(el.getStyle('top'));
    var canvas = $('canvas');
    var cx = parseInt(canvas.getStyle('left'));
    var cy = parseInt(canvas.getStyle('top'));
    var dx = 0;
    if (ex < -cx) {
		dx = -cx - ex;
    } else if ((ex + dims.width) > (-cx + iClipWidth)) {
		dx =  (-cx + iClipWidth) - (ex + dims.width);
    }
    var dy = 0;
    if (ey < -cy) {
		dy = -cy - ey;
    } else if ((ey + dims.height) > (-cy + iClipHeight)) {
		dy =  (-cy + iClipHeight) - (ey + dims.height);
		dy -= 10;
    }
    if ((dx != 0) || (dy != 0)) {
		animatedReposition(dx,dy);
    }
}

function bringElementIntoViewPortCenter (el) {
    el = $(el);
    var dims = Element.getDimensions(el);
    var ex = parseInt(el.getStyle('left')) + dims.width/2;
    var ey = parseInt(el.getStyle('top')) + dims.height/2;
    var canvas = $('canvas');
    var cx = -parseInt(canvas.getStyle('left')) + iClipWidth/2;
    var cy = -parseInt(canvas.getStyle('top')) + iClipHeight/2;
    var dx = cx - ex;
    var dy = cy - ey;
    if ((dx != 0) || (dy != 0)) {
		animatedReposition(dx,dy);
    }
}

function handleHighliteMouseout (event, dbid) {
}



var searchResults;

function handleMapsearchform (event) {
    var query = document.forms["mapsearchform"].QUERY.value;
    if ((query != null) && (query != "") && (query.length > 1)) {
    	clearHighlites();
		tabView.set('activeTab',tabView.getTab(0),true);    	
		doSearch(query);
    }
    if (event != null)
    	Event.stop(event);
}

function doExampleSearch (query) {
	document.forms["mapsearchform"].QUERY.value = query;
	handleMapsearchform();
}

var lastSearchResults = null;

function doSearch (query) {
	var lp = $("searchresultstab");
    var ol = overlayElementWithTimeIndicator($("leftpanel"));
	query = decodeURIComponent(query);
	//query = query.replace(/([^\w\d\s])/g, "\\$1");
	new Ajax.Request('/cgi-bin/entitylevelview/search', {
    	parameters: {'QUERY': query, 'DB': db, 'FOCUS_SPECIES_ID': focus_species_id},
        method: 'get',
        onSuccess: function(transport) {
			var rtxt = transport.responseText;
			if (rtxt && (rtxt != " ")) {
			    var a = eval(rtxt);
			    var n = getNodesWithIds(a[0]);
			    if (!anyInViewPort(n)) {
			    	centreOnCoordinate(n[0].x, n[0].y);
			    }
				highliteWithPeriodicalExecutioner(n);
				//lp.innerHTML = "Found <B>" + n.length + "</B> nodes matching <B>" + query + "</B>.";
				lastSearchResults = $H(a[1]);
				renderRemoteSearchResults(a[1], query);
			} else {
			    lp.innerHTML = "No matches for query <B>" + query + "</B>";
			}
		},
		onFailure: function(transport) {
			var uo = node.userObject;
			lp.innerHTML = "Search failed:<BR />" + req.statusText;
		},
		onComplete: function(transport) {
			if (ol) ol.remove();
		}
	});
}

function doSearch2 (query) {
    var oSR = $("searchresultstab");
    if (oSR != null) {
		searchResults = findNodesWithNameMatching(query);
		if (searchResults.length > 0) {
	    	var nodeArray = new Array();
	    	for (var i = 0, len = searchResults.length; i < len; ++i) {
				nodeArray.push(searchResults[i].coords);
	    	}
	    	nodeArray = nodeArray.flatten();
	    	oSR.innerHTML = "Found <B>" + nodeArray.length + "</B> nodes (<B>" + searchResults.length + "</B> instances) with name matching <B>" + query + "</B>. Please note that the same PhysicalEntity can occur multiple times, i.e. drawn as as mutiple nodes, on the map.";
			highliteWithPeriodicalExecutioner(nodeArray);
			if (!isAnyNodeVisible(nodeArray)) centreOnCoordinate(nodeArray[0].x, nodeArray[0].y);
		} else {
	    	oSR.innerHTML = "No instances with name matching <B>" + query + "</B>.";
		}
    }
}

function doSearch1 (query) {
    var oSR = $("searchresultstab");
    if (oSR != null) {
		searchResults = findNodesWithNameMatching(query);
		if (searchResults.length > 0) {
	    	var nodecount = 0;
	    	for (var i = 0, len = searchResults.length; i < len; ++i) {
				nodecount += searchResults[i].coords.length;
	    	}
	    	var sliceSize = 10;
	    	var tmp = "Found <B>" + nodecount + "</B> nodes (<B>" + searchResults.length + "</B> instances) with name matching <B>" + query + "</B>. Please note that the same PhysicalEntity can occur multiple times, i.e. drawn as as mutiple nodes, on the map. Each occurrence is shown as a number is square brackets after the display name of the instance. Click on the number to center the map on the node.";
	    	if (searchResults.length > sliceSize) {
				tmp += " View instances";
				for (var i = 0; i < searchResults.length; i+= sliceSize) {
		    		var h = (i+sliceSize>searchResults.length) ? searchResults.length : i+sliceSize
					tmp += " <A HREF=\"javascript:showResultSlice(" + i + "," + sliceSize + ")\">[" + (i+1) + "-" + h+ "]</A>"
				}
	    	}
	    	tmp += "<HR /><div id=\"resultslice\"></div>";
	    	oSR.innerHTML = tmp;
	    	showResultSlice(0, sliceSize);
		} else {
	    	oSR.innerHTML = "No instances with name matching <B>" + query + "</B>.";
		}
    }
}

function showResultSlice (startIdx, length) {
    var nodes = searchResults;
    //var nodecount = 0;
    clearHighlites();
    var tmp = "";
    for (var i = startIdx; (i < startIdx+length) && (i < nodes.length); i++) {
		var n = nodes[i];
		tmp += "<img src=\"" + n.icon + "\">" + n.name;
		for (var j = 0; j < n.coords.length; j++) {
	    	var node = n.coords[j];
	    	highliteInViewportAndCache(node, true);
	    	var h = j + 1;
	    	//tmp += " <A HREF=\"javascript:centreOnCoordinate(" + n.coords[j].x + "," + n.coords[j].y + ");loadDetails(" + n.id + ");\">[" + h + "]</A>";
	    	tmp += " <A HREF=\"javascript:centreOnNodeAndLoadDetails(" + node.id + ");\">[" + h + "]</A>";
	    	//nodecount++;
		}
		tmp += "<BR />";
    }
    var rsO = $("resultslice");
    rsO.innerHTML = tmp;
    centreOnCoordinate(highlitedCoordinates[0].x, highlitedCoordinates[0].y);
}

function renderRemoteSearchResults1 (results, query) {
	var tmp = "";
	if (query != null) {
	    var tmp = "Found <B>" + results.length + "</B> matche(s) for query <B>" + query + "</B>:<BR />";
	}
    for (var i = 0, l = results.length; i < l; ++i) {
		var r = results[i];
		tmp += "<img src=\"" + r.icon + "\">" + "<A HREF=\"javascript:handleClickOnTopicList(" + r.id + ")\">" + r.name + "</A>" + "<BR />";
    }
    $("searchresultstab").innerHTML = tmp;
}

function renderRemoteSearchResults (results, query) {
	var tmp = "";
	var counter = 0;
	$H(results).each(function(p) {
		//log(p.key);
		counter++;
		var r = p.value;
		tmp += "<img src=\"" + r.icon + "\">" + "<A HREF=\"javascript:handleClickOnSearchResults(" + r.id + ")\"" +
		" ONMOUSEOVER=\"handleSearchResultMouseover(" + r.id + ")\"" +
		" ONMOUSEOUT=\"handleSearchResultMouseout(" + r.id + ")\"" + ">" + r.name + "</A>" + "<BR />";
	});
    $("searchresultstab").innerHTML = "Found <B>" + counter + "</B> matche(s) for query <B>" + query + "</B>:<BR />" + tmp;
}

function handleSearchResultMouseover (dbid) {
	if ((lastSearchResults != null) && (lastSearchResults.values().length > 1)) {
		var r = lastSearchResults[dbid];
		if (r != null) {
			transientlyHighliteNodesWithIds(r.nodeIds);
		}
	}
}

function handleSearchResultMouseout (dbid) {
	if ((lastSearchResults != null) && (lastSearchResults.values().length > 1)) {
		var r = lastSearchResults[dbid];
		if (r != null) {
			removeTransientHighlitesFromNodesWithIds(r.nodeIds);
		}
	}
}

function handleClickOnSearchResults (dbid) {
	if ((lastSearchResults != null) && (lastSearchResults.values().length > 1) && (lastSearchResults[dbid] != null)) {
		var r = lastSearchResults[dbid];
		centreOnNodeWithId(r.nodeIds[0]);
	}
	loadEventHierarchyAndDetails(dbid);
}

function openEventbrowserPage (dbid) {
    var newWindow = window.open("/cgi-bin/eventbrowser?ID=" + dbid, 'eventbrowser');
    newWindow.focus();
    return false;
}

function init () {
	setDBandFocusSpecies();
	var url = "/cgi-bin/entitylevelview/ssvdims";
    new Ajax.Request(url,{
		parameters: {"DB" : db, "FOCUS_SPECIES_ID" : focus_species_id},
        method: 'get',
        onSuccess: function(transport) {
	    	var o = eval(transport.responseText);
	    	db = o.db;
	    	focus_species_id = o.focus_species_id;
	    	if (o.map) {
	    		imgw = o.map[0];
	    		imgh = o.map[1];
	    	} else {
	    		log("No entity level view for species " + focus_species_id + " in db " + db);
	    	}
	    	imgdir = "/entitylevelview/tiles/" + db + "/" + focus_species_id;
	    	init_handlers();
	    	loadOrderedCoordinates(true);
	    	$('content').show();
	    	setViewerSize();
	    	adjustStyle();
	    	reloadimage();
	    	createLeftPanelTabs();
        }
    });
    //tweakNavigationBarLinks();
}

function init_handlers () {
    if ($('mapsearchform'))
		Event.observe('mapsearchform', 'submit', handleMapsearchform);
    Event.observe('clipwindow', 'mousedown', engage);
    Event.observe('clipwindow', 'mouseover', function(){document.onkeypress=keypresshandler;});
    Event.observe('clipwindow', 'mousemove', handleMoveOverCanvas);
    Event.observe('clipwindow', 'mouseout', function(){document.onkeypress=null;});
    Event.observe('canvas', 'click', handleClickOnCanvas);
    Event.observe('thumbwindow', 'mousedown', engageThumb);
    Event.observe(window, 'resize', function(){setViewerSize();reposition(0,0)});
    Event.observe('leftpaneltoggler','mousedown',handleToggleLeftPanel);
    init_slider();
}

// Extend scriptaculous' Effect.Move so that the tiles get created/populated
// upon move. This is what the reposition(0,0) is for. OK, undoubtedly things
// could be done more efficiently.
Effect.Move2 = Class.create();
Object.extend(Object.extend(Effect.Move2.prototype, Effect.Move.prototype), {
  update: function(position) {
    this.element.setStyle({
      left: Math.floor(this.options.x  * position + this.originalLeft) + 'px',
      top:  Math.floor(this.options.y  * position + this.originalTop)  + 'px'
    });
    reposition(0,0);
  }
});

function animatedReposition (x, y) {
    new Effect.Move2($("canvas"), {x: x, y: y});
    reposition(0,0);
}

function handleToggleLeftPanel (event) {
	toggleLeftPanel();
}

function toggleLeftPanel () {
    var l = Position.cumulativeOffset($('clipwindow'))[0];
    var n = $($('leftpanel').parentNode);
    n.toggle();
    var el = $('leftpaneltoggler');
    if (n.visible()) {
		el.src = "/icons/hide-arrow.png";
		el.setStyle({position: 'relative', left: '0px'});
    } else {
		el.src = "/icons/show-arrow.png";
		el.setStyle({position: 'absolute', left: '0px'});
    }
    var canvas = $('canvas');
    canvas.setStyle({left: (parseInt(canvas.style.left) + (l - Position.cumulativeOffset($('clipwindow'))[0]))+'px'});
    reposition(0,0);
}

function toggleDetails () {
    var n = $('details');
    n.toggle();
    var el = $('detailstoggler');
    if (n.visible()) {
		el.src = "/icons/hide-arrow-v.png";
		//el.setStyle({position: 'relative', top: '0px'});
    } else {
		el.src = "/icons/show-arrow-v.png";
		//el.setStyle({position: 'absolute', bottom: '0px'});
    }
	setViewerSize();
	reposition(0,0);
}

function setLeftPanelVisibilityAccordingToContent () {
	var lp = $('hierarchytab');
	var n = $($('leftpanel').parentNode);
	//log("lp.empty()="+lp.empty()+", n.visible()="+n.visible());
	if ((lp.empty() && n.visible()) || (!lp.empty() && !n.visible())) {
		toggleLeftPanel();
	}
}

function tweakInternalLinks (links) {
    var re = new RegExp("ID=(\\d+)");
    for (var i = 0, l = links.length; i < l; ++i) {
		var href = links[i].readAttribute('href');
		//log(href);
		if (href) {
	    	var matches = href.match(re);
	    	if (matches) {
				//log(matches.inspect());
				//Event.observe(links[i], 'mouseover', function(event){log($(Event.element(event)).readAttribute('href'));Event.stop(event);return false;});
				links[i].setAttribute("href","javascript:handleClickOnInternalLinkInDetailsSection(" + matches[1] + ");");
	    	}
		}
    }
}

function handleClickOnTopicList (dbid) {
	var ol = overlayElementWithTimeIndicator($("content"));
    new Ajax.Request('/cgi-bin/entitylevelview/hhd?DB='+db+'&FOCUS_SPECIES_ID='+focus_species_id+'&ID='+dbid, {
        method: 'get',
        onSuccess: function(transport) {
			var rtxt = transport.responseText;
	    	var o = eval(rtxt);
	    	processHhdResponse(o);
	    	/*
	    	 * Centering on single highlite has been ordered already by processHhdResponse->
	    	 * processHighlites. As the centering happens with animation and thus delay, asking it
	    	 * to happen twice moves the thumb at location [2*x,2*y] rather than the expected [x,y].
	    	 * Only do the centering here if there are multiple highlites,
	    	 * coming presumably from clicking on the topic list
	    	 */
	    	if (highlitedCoordinates.length > 1) {
	    		centreOnCoordinate(highlitedCoordinates[0].x, highlitedCoordinates[0].y);
	    	}
	    	updateLocationHref(dbid);
	    	current_instance_id = dbid;
		},
		onComplete: function(transport) {
	    	if (ol) ol.remove();
		}		     
    });
}

function handleClickOnInternalLinkInDetailsSection (dbid) {
    var ol = overlayElementWithTimeIndicator($("content"));
    new Ajax.Request('/cgi-bin/entitylevelview/hhd',{
		parameters: {"DB" : db, "FOCUS_SPECIES_ID" : focus_species_id, 'ID' : dbid},
        method: 'get',
        onSuccess: function(transport) {
			var rtxt = transport.responseText;
			if (rtxt) {
				//log(rtxt);
				var o = eval(rtxt);
				processHhdResponse(o);
				updateLocationHref(dbid);
				reposition(0,0);
				current_instance_id = dbid;
			} else {
				log("No responseText?!")
			}
        },
        onComplete: function () {
        	if (ol) ol.remove();
        }
    });
}

function processHhdResponse (o) {
	processTitle(o.title);
	if (o.focus_species_id && (o.focus_species_id != focus_species_id)) handleFocusSpeciesChange(o);
	processLeftpanel(o.leftpanel);
	processDetails(o.details);
	processHighlites(o.highlites);
}

function processTitle (title) {
    if (title) {
		document.title = title;
    }
}

function processHighlites (highlites) {
    if (highlites) {
		//log("Handling highlites: " + highlites.length);
		if (highlites.length > 1000) {
			highliteNodesWithIdsWithPeriodicalExecutioner(highlites);
		} else {
			highliteNodesForRepresentedInstanceIds(highlites);
			if (((highlites.length == 1) || (highlitedCoordinates.length == 1)) && highlitedCoordinates.length > 0) {
				centreOnCoordinate(highlitedCoordinates[0].x, highlitedCoordinates[0].y);
			}
		}
    } else {
		clearHighlites();
    }
}

function processLeftpanel (leftpanel) {
    //var lp = $("leftpanel");
    var lp = $("hierarchytab");
    if (leftpanel) {
		//log("Handling leftpanel");
		var html = decodeURIComponent(leftpanel.html);
		if (leftpanel.js) {
			var script = decodeURIComponent(leftpanel.js);
			lp.innerHTML = html.replace(/<script.*?>(\n|\r|.)*?<\/script>/,eval(script));
			if (leftpanel.opento) {
				eventhierarchydtree.closeAll();
				eventhierarchydtree.openToDbIds(leftpanel.opento,true);
			}
		} else {
			eventhierarchydtree = null;
			lp.innerHTML = html;
		}
		var links = lp.getElementsBySelector('a');
		tweakEventHierarchyLinks(links);
		tabView.set('activeTab',tabView.getTab(1),true);
    } else {
		lp.innerHTML = "";
		eventhierarchydtree = null;
    }
    //setLeftPanelVisibilityAccordingToContent();
}

function processDetails (details) {
    var ds = getDetailsDiv();
    if (details) {
		//log("Handling details");
		var html = decodeURIComponent(details[0]);
		var script = decodeURIComponent(details[1]);
		ds.innerHTML = html.replace(/<script.*?>(\n|\r|.)*?<\/script>/,eval(script));
		var links = ds.getElementsBySelector('a');
		tweakInternalLinks(links);
    } else {
		ds.innerHTML = "";
    }
}

function handleFocusSpeciesChange (o) {
	//log("Focus species changed to " + o.focus_species_id);
	focus_species_id = o.focus_species_id;
	orderedRepresentedInstances = o.orderedRepresentedInstances;
	processOrderedRepresentedInstances(orderedRepresentedInstances);
	if (o.map) {
		imgw = o.map[0];
		imgh = o.map[1];
	}
	imgdir = "/entitylevelview/tiles/" + db + "/" + focus_species_id;
    $("thumbnail").style.backgroundImage = "url("+imgdir+"/thumb.png)";
    $("backgroundimage").src = imgdir+"/bg.png";
	loadOrderedCoordinates(false);
	$("searchresultstab").innerHTML = "";
	//In order to update the FOCUS_SPECIES_ID sent to the server.
	init_query_autocomplete();
}

function tweakEventHierarchyLinks (links) {
    var re = new RegExp("ID=(\\d+)");
    for (var i = 0, l = links.length; i < l; ++i) {
	var href = links[i].readAttribute('href');
	//log(href);
	if (href) {
	    var matches = href.match(re);
	    if (matches) {
		links[i].setAttribute("href","javascript:handleClickOnEventHierarchy(" + matches[1] + ");");
		links[i].setAttribute("onmouseover","transientlyHighliteNodesForRepresentedInstanceId(" + matches[1] + ");");
		//links[i].setAttribute("onmouseout","removeTransientlyHighliteFromNodesForRepresentedInstanceId(" + matches[1] + ");");
		links[i].setAttribute("onmouseout","removeTransientHighlites();");
		//The following does not work as matches[1] will be the same for all elements
		//Event.observe(links[i], 'mouseover', function(event){transientlyHighliteNodesForRepresentedInstanceId(matches[1])});
		//Event.observe(links[i], 'mouseout', function(event){removeTransientlyHighliteFromNodesForRepresentedInstanceId(matches[1])});
	    }
	}
    }
}

function handleClickOnEventHierarchy (dbid) {
    var n = representedInstances[dbid];
    clearHighlites();
    if (n != null) {
		//clearHighlites();
		var coords = n.coords;
		for (var i = 0, l = coords.length; i < l; ++i) {
	    	//highliteAndCache(coords[i],true);
	    	highliteInViewportAndCache(coords[i],true);
		}
		centreOnCoordinate(coords[0].x, coords[0].y);
    } else {
		//clearLog();
		//log("No node with DB_ID " + dbid);
		highlitePathwayComponents(dbid);
    }
    loadDetails(dbid);
    highliteInstanceInEventHierarchy(dbid);
}

function adjustStyle () {
    if (isIE) {
		$('clipwrapper').setStyle({left:'0px'});
		$('thumbnailframe').setStyle({bottom:'-1px',right:'-1px'});
    }
}

function highliteInstanceInEventHierarchy (dbid) {
    if ((eventhierarchydtree != null) && eventhierarchydtree.containsNodeWithDbID(dbid)) {
		eventhierarchydtree.deselectAllNodes();
		eventhierarchydtree.openToDbId(dbid,true);
    } else {
		loadEventHierarchy(dbid);
    }
}

function highliteNodeInEventHierarchy (node, activateHierarchyTab) {
    if ((eventhierarchydtree != null) && eventhierarchydtree.containsNodeWithDbID(node.userObject.id)) {
		eventhierarchydtree.deselectAllNodes();
		eventhierarchydtree.openToDbId(node.userObject.id,true);
    } else {
		loadEventHierarchyForNode(node);
    }
    if (activateHierarchyTab) tabView.set('activeTab',tabView.getTab(1),true);
}

function overlayElementWithTimeIndicator (el) {
	if (el.visible()) {
	    var ol = Element.extend(document.createElement('div'));
	    //    ol.innerHTML = '<center><img src="/icons/time_indicator.gif" /></center>';
	    var pos = Position.cumulativeOffset(el);
	    var dim = el.getDimensions();
	    ol.innerHTML = '<center><img src="/icons/time_indicator.gif" style="margin-top:' +  dim.height/2 + 'px;"/></center>';
	    ol.setStyle({zIndex:100,position:'absolute',top:pos[1]+'px',left:pos[0]+'px',width:dim.width+'px',height:dim.height+'px',backgroundColor:'#EEEEEE',opacity:0.5});
	    el.parentNode.appendChild(ol);
	    return ol;
	}
}

function showDiagramsForVisibleComplexAndSetNodesIfMaxZoom () {
    if ((z == 1) && $("entitydiagrams").visible()) {
	showDiagramsForVisibleComplexAndSetNodes();
	$("entitydiagrams").descendants().invoke('show');
    } else {
	$("entitydiagrams").descendants().invoke('hide');
    }
}

function showDiagramsForVisibleComplexAndSetNodesIfNecessary () {
    if ($("entitydiagrams").visible()) {
		showDiagramsForVisibleComplexAndSetNodes();
    }
}

function showDiagramsForVisibleComplexAndSetNodes () {
    var coords = findVisibleComplexAndSetNodes();
    coords.each(function(n){showDiagramForNode(n);});
}

function showDiagramForNode (node) {
    var id = "diagram_" + node.userObject.id + "_" + node.x + "_" + node.y;
    if (!$(id)) {
		var x = zoomFactor * node.x;
		var y = zoomFactor * node.y;
		/*
		 * For some reason, have to make images a bit wider in order to cover
		 * the underlying node competely.
		 */
		var w = zoomFactor * node.w + 1;
		var h = zoomFactor * node.h + 1;
		//var imgsrc = '/cgi-bin/entitylevelview/entitydiagram?ID=' + node.userObject.id + '&W=' + (node.w*2) +'&H=' + (node.h*2);
		var imgsrc = imgdir + '/entitydiagrams/' + node.userObject.id + '.png';
		var img = Element.extend(document.createElement('img'));
		img.id = id;
		img.setStyle({position:"absolute",top:y+"px",left:x+"px",width:w+"px",height:h+"px"});
		img.src = imgsrc;
		$("entitydiagrams").appendChild(img);
    }
}

function findVisibleComplexAndSetNodes () {
    var canvas = $('canvas');
    var x1 = -parseInt(canvas.getStyle('left')) / zoomFactor;
    var y1 = -parseInt(canvas.getStyle('top')) / zoomFactor;
    var x2 = (-parseInt(canvas.getStyle('left')) + iClipWidth) / zoomFactor;
    var y2 = (-parseInt(canvas.getStyle('top')) + iClipHeight) / zoomFactor;
    return findNodesInArea(x1,y1,x2,y2).findAll(function(n) {
						    return (
							(n.userObject.cls == "Complex")
							||
							(n.userObject.cls == "DefinedSet")
							||
							(n.userObject.cls == "CandidateSet")
							);
						});
}

function findNodesInArea (x1,y1,x2,y2) {
    var out = new Array();
    for (var j = 0, l = orderedRepresentedInstances.length; j < l; ++j) {
	var coords = orderedRepresentedInstances[j].coords;
	for (var i = 0; i < coords.length; i++) {
	    if ((x2 > coords[i].x) && 
		(x1 < coords[i].x+coords[i].w) && 
		(y2 > coords[i].y) && 
		(y1 < coords[i].y+coords[i].h)) {
		//log("Found: " + orderedRepresentedInstances[j].name + "\n" + toString(coords[i]));
		//coords[i].userObject = orderedRepresentedInstances[j];
		out.push(coords[i]);
	    }
	}
    }
    return out;
}

function getVisibleArea () {
	var canvas = $('canvas');
    var x = -parseInt(canvas.getStyle('left')) / zoomFactor;
    var y = -parseInt(canvas.getStyle('top')) / zoomFactor;
    var w = iClipWidth / zoomFactor;
    var h = iClipHeight / zoomFactor;
    return {"x":x,"y":y,"w":w,"h":h};
}

function isAnyNodeVisible (nodeArray) {
	var r = getVisibleArea();
	var x1 = r.x;
	var x2 = r.x+r.w;
	var y1 = r.y;
	var y2 = r.y+r.h;
	for (var i = 0, l = nodeArray.length; i < l; ++i) {
		var n = nodeArray[i];
		if ((x2 > n.x) && 
			(x1 < n.x+n.w) && 
			(y2 > n.y) && 
			(y1 < n.y+n.h)) {
			return true;
		}
	}
	return false;
}

function updateLocationHref (dbid) {
	var i = document.location.href.indexOf("#");
	var href = document.location.href;
	if (i != -1) {
		href = href.substring(0,i);
	}
	href += "#DB=" + db + "&FOCUS_SPECIES_ID=" + focus_species_id;
	if (dbid && (dbid != focus_species_id)) {
		href += "&ID=" + dbid;
	}
	document.location.href = href;
}

function extractParamsFromURI () {
	var uri = String(document.location);
	var fragment = uri.substring(uri.indexOf("#")+1,uri.length);
	var params = fragment.toQueryParams();
	return params;
}

function handleQueryInURI () {
    //var params = String(document.location).toQueryParams();
    var params = extractParamsFromURI();
    if (params['ID'] && (typeof(params['ID']) == 'string')) {
		handleClickOnTopicList(params['ID']);
    } else {
    	//handleClickOnInternalLinkInDetailsSection(focus_species_id);
    	loadEventHierarchy(focus_species_id);
    	recentre();
    }
}

function setDBandFocusSpecies () {
    //var params = String(document.location).toQueryParams();
    var params = extractParamsFromURI();
    if (params['DB'] && (typeof(params['DB']) == 'string')) {
		db = params['DB'];
    } else {
		var cv = getCookie('DB');
		if (cv) {
		    db = cv;
		}
    }
    if (params['FOCUS_SPECIES_ID'] && (typeof(params['FOCUS_SPECIES_ID']) == 'string')) {
		focus_species_id = params['FOCUS_SPECIES_ID'];
    } else {
		var cv = getCookie('FOCUS_SPECIES_ID');
		if (cv) {
		    focus_species_id = cv;
		}
    }
}

function transientlyHighliteNode (node) {
    var x = zoomFactor * node.x;
    var y = zoomFactor * node.y;
    var w = zoomFactor * node.w;
    var h = zoomFactor * node.h;
    if (isIE) {
        w += highliteBorderThickness;
        h += highliteBorderThickness;
    } else {
        x -= highliteBorderThickness;
        y -= highliteBorderThickness;
    }
    var thl = Element.extend(document.createElement('div'));
    thl.innerHTML = "&nbsp;";
    thl.id = "thl_" + node.id;
    thl.className = "transienthighlite";
    thl.setStyle({position:"absolute",top:y+"px",left:x+"px",width:w+"px",height:h+"px"});
    $("canvasoverlay").appendChild(thl);
    //Thumb
    x = node.x / imgw * thumbnailw - 1;
    y = node.y / imgh * thumbnailh - 1;
    var t_thl = Element.extend(document.createElement('div'));
    t_thl.innerHTML = "&nbsp;";
    t_thl.id = "t_thl_" + node.id;
    t_thl.className = "thumbtransienthighlite";
    t_thl.setStyle({position:"absolute",top:y+"px",left:x+"px",width:"2px",height:"2px"});    
    $("thumbnailoverlay").appendChild(t_thl);
}

function removeTransientHighlites () {
	$("canvasoverlay").getElementsByClassName("transienthighlite").map(function(e){e.remove();});
	$("thumbnailoverlay").getElementsByClassName("thumbtransienthighlite").map(function(e){e.remove();});
}

function removeTransientHighliteFromNode (node) {
	removeTransientHighliteFromNodeWithId(node.id);
}

function removeTransientHighliteFromNodeWithId (id) {
    var thl = $("thl_" + id);
    if (thl) {
		thl.remove();
    }
    var t_thl = $("t_thl_" + id);
    if (t_thl) {
		t_thl.remove();
    }
}

function transientlyHighliteNodesForRepresentedInstance (i) {
    i.coords.map(function(n){transientlyHighliteNode(n);});
}

function removeTransientlyHighliteFromNodesForRepresentedInstance (i) {
    i.coords.map(function(n){removeTransientHighliteFromNode(n);});
}

function transientlyHighliteNodesForRepresentedInstanceId (id) {
	removeTransientHighlites();
	if (current_instance_id == id) return;
    var i = representedInstances[id];
    if (i) {
    	transientlyHighliteNodesForRepresentedInstance(i);
    	//centreOnCoordinate(i.coords[0].x, i.coords[0].y);
    } else if (transientHighliteIdCache[id]) {
		transientlyHighliteNodesWithIds(transientHighliteIdCache[id]);
		//if (transientHighliteIdCache[id][0]) centreOnNodeWithId(transientHighliteIdCache[id][0]);
    } else {
    	loadTransientHighlites(id);
    }
}

function removeTransientlyHighliteFromNodesForRepresentedInstanceId (id) {
    var i = representedInstances[id];
    if (i) {
    	removeTransientlyHighliteFromNodesForRepresentedInstance(i);
    } else if (transientHighliteIdCache[id]) {
    	//log(transientHighliteIdCache[id]);
    	removeTransientHighlitesFromNodesWithIds(transientHighliteIdCache[id]);
    }
}

function highliteNodesForRepresentedInstance (i, keepPrevious) {
    if (! keepPrevious) clearHighlites();
    i.coords.map(function(n){highliteInViewportAndCache(n, true);});
}

function highliteNodesForRepresentedInstanceId (id, keepPrevious) {
    var i = representedInstances[id];
    if (i) highliteNodesForRepresentedInstance(i, keepPrevious);
}

function highliteNodesForRepresentedInstanceIds (ids, keepPrevious) {
    if (! keepPrevious) clearHighlites();
    for (var j = 0, l = ids.length; j < l; ++j) {
		var i = representedInstances[ids[j]];
		if (i) {
			highliteNodesForRepresentedInstance(i, true);
		} else {
			//log("No representedInstance with id "+ids[j]);
		}
    }
}

function transientlyHighliteNodesWithIds (ids) {
	ids.map(function(id){var n = nodes[id]; if(n != null) transientlyHighliteNode(n)});
}

function removeTransientHighlitesFromNodesWithIds (ids) {
	ids.map(function(id){removeTransientHighliteFromNodeWithId(id)});
}

/*
function ddrivetip () {}
function hideddrivetip () {}
*/

/*
handle click on internal link in details section

clear highlighted nodes
if (species changed) {
  new map
  if (locatable item) {
    pan to selected item
  } else {
    pan to region equivalent to previous view in other species
  }
  new hierarchy if applicable
  new details
} else {
  if (nodes) {
    highlite nodes
    if (few nodes) {
      pan to selected node(s)
    } else {
      keep existing view
    }
  }
  if (hierarchy) {
    show new hierarchy
  } else {
    remove old hierarchy
  }
  new details  
}
*/

function highliteNNodes (n) {
    clearHighlites();
    var i = 0;
    var t1 = new Date().getTime();
    for (var k in nodes) {
		var node = nodes[k];
		highliteInViewportAndCache(node, true);
		//highliteNode(node, true);
		//highliteThumbCoordinate(node.x, node.y);
		if (++i > n) {
		    break;
		}
    }
    var t2 = new Date().getTime();
    log("Highlited " + n + " nodes in " + (t2 - t1)/1000 + "s");
}

function isInViewPort (n) {
	//log(viewportRight+">"+n.x+","+viewportLeft +"<"+(n.x+n.w)+","+viewportBottom+">"+n.y+","+viewportTop+"<"+(n.y+n.h));
	return ((viewportRight > n.x) && 
		(viewportLeft < n.x+n.w) && 
		(viewportBottom > n.y) && 
		(viewportTop < n.y+n.h));
    /*var f = ((viewportRight > n.x) && 
		(viewportLeft < n.x+n.w) && 
		(viewportBottom > n.y) && 
		(viewportTop < n.y+n.h));
	//log((f?"IN":"OUT") + " " + n.userObject.name);
	return f;*/
}

function anyInViewPort (nodes) {
	for (var i = 0, l = nodes.length; i < l; ++i) {
		if (isInViewPort(nodes[i])) return true;
	}
	return false;
}

/*
 * The point of this is to do the highliting is small chunks so that the brwser
 * doesn't get "tired" with the process and suggest to kill it.
 */
function highliteWithPeriodicalExecutioner (nodeArray) {
	var c = 0;
	var step = 500;
	new PeriodicalExecuter(function(pe) {
		var t1 = new Date().getTime();
		for (var i = c; i < c+step; ++i) {
			if (i >= nodeArray.length) {
				//log("PeriodicalExecuter.stop() "+i);
				pe.stop();
				log("# thumbhighlites: "+$("thumbnailoverlay").childNodes.length);
				break;
			} else {
				var node = nodeArray[i];
				highliteInViewportAndCache(node, true);
			}
		}
		var t2 = new Date().getTime();
		log("PeriodicalExecuter "+c+" ran for "+(t2 - t1)/1000 + "s");
		c += step;
	}, 1);
}

function highliteNodesWithIdsWithPeriodicalExecutioner (idArray) {
	clearHighlites();
	var c = 0;
	var step = 1000;
	new PeriodicalExecuter(function(pe) {
		var t1 = new Date().getTime();
		for (var i = c; i < c+step; ++i) {
			if (i >= idArray.length) {
				//log("PeriodicalExecuter.stop() "+i);
				pe.stop();
				break;
			} else {
				var node = representedInstances[idArray[i]];
				if (node != null) {
					highliteNodesForRepresentedInstance(node, true);
				} else {
					//log("No node: Idx: " + i + ", DB_ID:" + idArray[i]);
				}
			}
		}
		var t2 = new Date().getTime();
		log("PeriodicalExecuter "+c+" ran for "+(t2 - t1)/1000 + "s");
		c += step;
	}, 1);
}

var tabView;
function createLeftPanelTabs () {
	tabView = new YAHOO.widget.TabView('leftpanel');
}

function centreOnNodeAndLoadDetails (node_id) {
	var node = nodes[node_id];
	if (node) {
		centreOnCoordinate(node.x, node.y);
		loadDetails(node.userObject.id);
		highliteNodeInEventHierarchy(node, false);
	} else {
		log("No node with id "+node_id);	
	}
}

function centreOnNodeWithId (node_id) {
	var node = nodes[node_id];
	if (node) {
		centreOnCoordinate(node.x, node.y);
	} else {
		log("No node with id "+node_id);	
	}
}

function loadEventHierarchy (dbid) {
    var ol = overlayElementWithTimeIndicator($("content"));
    new Ajax.Request('/cgi-bin/entitylevelview/hhd',{
		parameters: {"DB" : db, "FOCUS_SPECIES_ID" : focus_species_id, 'ID' : dbid},
        method: 'get',
        onSuccess: function(transport) {
			var rtxt = transport.responseText;
			if (rtxt) {
				//log(rtxt);
				var o = eval(rtxt);
				processTitle(o.title);
				processLeftpanel(o.leftpanel);
				updateLocationHref(dbid);
				reposition(0,0);
				current_instance_id = dbid;
			} else {
				log("No responseText?!")
			}
        },
        onComplete: function () {
        	if (ol) ol.remove();
        }
    });
}

function loadEventHierarchyAndDetails (dbid) {
    var ol = overlayElementWithTimeIndicator(getDetailsDiv());
    new Ajax.Request('/cgi-bin/entitylevelview/hhd',{
		parameters: {"DB" : db, "FOCUS_SPECIES_ID" : focus_species_id, 'ID' : dbid},
        method: 'get',
        onSuccess: function(transport) {
			var rtxt = transport.responseText;
			if (rtxt) {
				//log(rtxt);
				var o = eval(rtxt);
				processTitle(o.title);
				processLeftpanel(o.leftpanel);
				processDetails(o.details);
				updateLocationHref(dbid);
				reposition(0,0);
				current_instance_id = dbid;
			} else {
				log("No responseText?!")
			}
        },
        onComplete: function () {
        	if (ol) ol.remove();
        }
    });
}

var zoomChange;
function init_slider () {
    var Event = YAHOO.util.Event,
        Dom   = YAHOO.util.Dom,
        lang  = YAHOO.lang,
        slider, 
        bg="slider-bg", thumb="slider-thumb", 
        valuearea="slider-value", textfield="slider-converted-value";

    var topConstraint = 0;
    var bottomConstraint = 60;
    var scaleFactor = 20;
    var keyIncrement = 20;
    var tickSize = 20;

    Event.onDOMReady(function() {
        slider = YAHOO.widget.Slider.getVertSlider(bg, 
                         thumb, topConstraint, bottomConstraint, tickSize);
                         
        zoomChange = new YAHOO.util.CustomEvent('zoom level change');
		zoomChange.subscribe(_setZoomLevel, slider);
        slider.getRealValue = function() {
            return Math.round(this.getValue() / scaleFactor + 1);
        }
        slider.setRealValue = function(value) {
            slider.setValue((value - 1) * scaleFactor);
        }
        slider.subscribe("change", function(offsetFromStart) {
            var actualValue = slider.getRealValue();
            // Update the title attribute on the background.  This helps assistive
            // technology to communicate the state change
            Dom.get(bg).title = "slider value = " + actualValue;
			zoomChange.fire(actualValue);
        });
		slider.setRealValue(z);
		init_query_autocomplete();
    });
}

function _setZoomLevel (type, zl, slider) {
	log(type + ", " + zl + ", " + slider);
	setZoomLevel(zl);
	slider.setRealValue(zl);
}

function getDetailsDiv () {
	var el = $("details");
	if (el == null) {
		var content = $("content");
		var v = Element.extend(document.createElement('div'));
		v.innerHTML += '<center><img src="/icons/hide-arrow-v.png" id="detailstoggler" /></center>';
		content.appendChild(v);
		el = Element.extend(document.createElement('div'));
		el.id = "details";
		el.setStyle({position:"relative",border:"none",width:"100%",top:"3px",overflow:"auto"});
		content.appendChild(el);
		setViewerSize();
		Event.observe('detailstoggler','mousedown',toggleDetails);
	}
	return el;
}

function init_query_autocomplete1 () {
	this.oACDS = new YAHOO.widget.DS_JSFunction(function (query) {
		query = decodeURIComponent(query);
		query = query.replace(/([^\w\d\s])/g, "\\$1");
    	var re = new RegExp(query, "i");
    	var out = new Array();
    	for (var i = 0, l = orderedRepresentedInstances.length; i < l; ++i) {
			var node = orderedRepresentedInstances[i];
			if (node.name.match(re) != null) {
	    		out.push([node.name]);
			}
    	}
    	log("query='" +query + "' # matches: "+out.length);
    	return out;
	}); 

    // Instantiate first AutoComplete
    this.oAutoComp = new YAHOO.widget.AutoComplete('query','querycontainer', this.oACDS);
    this.oAutoComp.prehighlightClassName = "yui-ac-prehighlight";
    this.oAutoComp.typeAhead = false;
    this.oAutoComp.useShadow = true;
    this.oAutoComp.minQueryLength = 2;
    this.oAutoComp.maxResultsDisplayed = 10;
    this.oAutoComp.formatResult = function(oResultItem, sQuery) {
        var sMarkup = oResultItem[0];
        return (sMarkup);
    };
}

function init_query_autocomplete () {
	var maxResultsDisplayed = 20;
	this.oACDS = new YAHOO.widget.DS_XHR("/cgi-bin/entitylevelview/searchtermautocomplete", ["\n", "\t"]);
	this.oACDS.scriptQueryParam = "QUERY";
	this.oACDS.scriptQueryAppend = "DB=" + db + "&FOCUS_SPECIES_ID=" + focus_species_id + "&MAXROWS=" + maxResultsDisplayed;
    this.oACDS.responseType = YAHOO.widget.DS_XHR.TYPE_FLAT;
    this.oACDS.maxCacheEntries = 0;
    this.oACDS.queryMatchSubset = false;

    this.oAutoComp = new YAHOO.widget.AutoComplete('query','querycontainer', this.oACDS);
    this.oAutoComp.prehighlightClassName = "yui-ac-prehighlight";
    this.oAutoComp.typeAhead = false;
    this.oAutoComp.useShadow = true;
    this.oAutoComp.minQueryLength = 2;
    this.oAutoComp.maxResultsDisplayed = maxResultsDisplayed;
    this.oAutoComp.queryDelay = 1; 
    this.oAutoComp.formatResult = function(oResultItem, sQuery) {
   		sQuery = sQuery.replace(/\*/g, "");
   		sQuery = '(' + sQuery + ')';
    	var re = new RegExp(sQuery, "gi");
        var sMarkup = oResultItem[0];
        sMarkup = sMarkup.replace(re, "<span class=\"hl-substr-1\">$1</span>");
        return (sMarkup);
    };
}

function getNodesWithIds (ids) {
	var out = [];
	for (var i = 0, l = ids.length; i < l; ++i) {
		var n = nodes[ids[i]];
		if (n != null) {
			out.push(n);
		}
		//log(i + "\t" + ids[i] + "\t" + n);
	}
	return out;
}

function tweakNavigationBarLinks () {
	var e = $("productsandservices");
	if (e) {
		var links = e.getElementsBySelector('a');
		var re = new RegExp("DB=\\w+");
    	for (var i = 0, l = links.length; i < l; ++i) {
			var href = links[i].readAttribute('href');
			if (href) {
				href = href.replace(re, "DB="+db);
				//log(href);
				links[i].setAttribute("href",href);
			}
    	}
	}
}

var transientHighliteIdCache = new Hash();
var inLoadTransientHighlites = false;
function loadTransientHighlites (dbid) {
	if (! inLoadTransientHighlites) {
		inLoadTransientHighlites = true;
		//var ol = overlayElementWithTimeIndicator($("clipwrapper"));
	    new Ajax.Request('/cgi-bin/entitylevelview/highlites?DB='+db+'&FOCUS_SPECIES_ID='+focus_species_id+'&ID='+dbid, {
    	    method: 'get',
        	onSuccess: function(transport) {
				var rtxt = transport.responseText;
	    		var o = eval(rtxt);
    			transientHighliteIdCache[dbid] = o.highlites;
    			transientlyHighliteNodesWithIds(o.highlites);
    			//if (o.highlites[0]) centreOnNodeWithId(o.highlites[0]);
			},
			onComplete: function() {
				inLoadTransientHighlites = false;
				//ol.remove();
			}
    	});
	}
}

function goToOldUI () {
	var loc;
	if ((current_instance_id != null) && (current_instance_id != focus_species_id)) {
//		loc = '/cgi-bin/eventbrowser?DB=' + db + '&ID=' + current_instance_id + '&FOCUS_SPECIES_ID=' + focus_species_id;
                loc = '/cgi-bin/eventbrowser?' + 'ID=' + current_instance_id + '&FOCUS_SPECIES_ID=' + focus_species_id;
	} else {
//		loc = '/cgi-bin/frontpage?DB=' + db + '&FOCUS_SPECIES_ID=' + focus_species_id;
                loc = '/cgi-bin/frontpage?' + 'FOCUS_SPECIES_ID=' + focus_species_id;
	}
	window.location = loc;
}

function popupFeedbackWindow () {
	var cs = $("canvas").style;
	var params = $H({
		var_db: db,
		var_current_instance_id: current_instance_id, 
		var_focus_species_id: focus_species_id,
		var_z: z,
		var_left: cs.left,
		var_top: cs.top,
		var_iClipWidth: iClipWidth,
		var_iClipHeight: iClipHeight});
	var url = 'feedbackform.html?' + params.toQueryString();
	window.open(url,'feedback','height=400,width=400,left=10,screenX=10,top=10,screenY=10,resizable,scrollbars=yes');
}
