//<!--
//# Written by Russell Moffitt, NOAA
//# Modified Oct 2006
//# License: GPL
//-->

/*
MapManager requires the xbrowser (modified cross-browser.com x lib) for
the functions that handle mouse events and other things.  Both x_core.js 
and x_event.js must be imported in the HTML code before this script.
MapManager requires the jsonrpc.js file to be imported to handle JsonRPC
requests.  The jsonrpc.js depends on the MochiKit javascript library to 
handle JSON serialization and XmlHttpRequests.  Base.js and Async.js must be
imported in the HTML code before this script.  The rest of MochiKit
is not used at the moment, and the RPC code may be separated out from
MochiKit at some future time.
*/

/*
mapmanager.js contains the MapManager class constructor.  This creates a MapManager object
instance that is in charge of coordinating map drawing state, mouse interactions, layer
config updating, and dispatching server processing and mapping requests and handling their
output.  The server is sent a JSON-RPC formatted request with structured-hierarchical
parameters destined for a named server-side method function.  The server should return
a structured JSON response with object properties representing map info and image urls.

MapManager, draws its required layers/elements inside the specified parent HTML element at
a given size.  This includes creating a ZoomBox object and the glass layer for mouse event 
capture that it requires.  This glass layer will be used for more than just standard 
zoomboxing.

It is debateable how much of the layer config handling that should be part of MapManager.
It might be best to handle all of that through an independent javascript file and provide
an API for it to communicate with MapManager.

MapManager does the following:
* Action State
    - Zoom In (box/point), Zoom Out (point), Pan (point, drag map, reference map click)
    - Query
        > Point  +value, +"buoy" station query
        > Line/Transect  +value/transect graph, +transect vs time smooth plot
        > Box?  +animation
* Map Layer Control
    - Get dataset layer array of config objects via layer handler API
* Drawing Control
    - Request draw with new zoomed/panned map extent
    - Request draw with new layer configuration
    - Request printable/saveable map
    - Request map-state bookmarklet (complex feature)
*/

//////////////////////////////////
// Begin utility functions
//////////////////////////////////


function reportStatus(result) {
	var content = "";
	var mapStatus = xGetElementById('mapStatus')
	mapStatus.innerHTML = "";
	for (i = 0; i < result.length; i++) {
		content += result[i] + '\n<br>'
	}
	mapStatus.innerHTML = content;
}

function getStatus(reqId) {
	sendJsonRpc('/cgi-bin/oe/cdatMapper.py', 'status', [reqId] , reportStatus);
}

function curry(obj, f) {
	
	var args = [];
	for (var i = 2; i < arguments.length; i++) {
		args.push(arguments[i]);
	}
	return function () {
		var args2 = [];
		for (var i = 0; i < arguments.length; i++) {
			args2.push(arguments[i]);
		}
		return f.apply(obj, args.concat(args2));
	}

}

function curry2(func,args,space) {
	var n  = func.length - args.length; //arguments still to come
	var sa = Array.prototype.slice.apply(args); // saved accumulator array
	function accumulator(moreArgs,sa,n) {
		var saPrev = sa.slice(0); // to reset
		var nPrev  = n; // to reset
		for(var i=0;i<moreArgs.length;i++,n--) {
			sa[sa.length] = moreArgs[i];
		}
		if ((n-moreArgs.length)<=0) {
			var res = func.apply(space,sa);
			// reset vars, so curried function can be applied to new params.
			sa = saPrev;
			n  = nPrev;
			return res;
		} else {
			return function (){
				// arguments are params, so closure bussiness is avoided.
				return accumulator(arguments,sa.slice(0),n);
			}
		}
	}
	return accumulator([],sa,n);
}

function padDigits(n, totalDigits, padchar) {
	if (typeof(n) != 'string') n = n.toString();
	var pd = "";
	if (!xDef(padchar)) padchar = '0';
	if (totalDigits > n.length) {
		for (i=0; i < (totalDigits-n.length); i++) {
			pd += padchar;
		}
	}
	return pd + n;
}

//////////////////////////////////
// End utility functions
//////////////////////////////////

//////////////////////////////////
// Begin MapManager class definition
//////////////////////////////////

function MapManager(mapHolder, legendImg, layerHandler, width, height, mapServiceUrl, defaultExtent) {
	
	/* Defines the following methods:
	and the following internal elements:
	*/
	
	//this.parentElement = document;
	//if (parentElement) this.parentLayer = $(parentElement);
	
	this.setMapSize(width, height);
	
	// Create viewing pane: contains map image, zoombox, glass layer.  Clips at edges.
	//this.mapHolder = document.createElement('div');
	this.mapHolder = xGetElementById(mapHolder);
	this.mapHolder.className = "mapHolder";
	//xResizeTo(this.mapHolder, width, height);
	//this.parentElement.insertBefore(this.mapHolder, this.parentElement.firstChild);
	
	// Load ZoomBox object
	this.zoomBox = new ZoomBox(this.mapHolder);
	this.zoomBox.setColor('#FF4444');
	this.glass = this.zoomBox.glass;
	this.zoomBox.myMapManager = this; // Set this so we can find this MapManager object from zoomBox events
	
	//this.mapImageDiv = document.createElement('div');
	//this.mapImageDiv.className = "mapImage";
	//this.mapImg = document.createElement('img');
	//this.mapImageDiv.appendChild(this.mapImg)
	//this.mapImg.width = "100%";
	//this.mapImg.id = "theMap";
	//this.mapImg.alt = "theMap";
	//this.mapImg.name = "theMap";
	//this.mapImg.align = "center";
	//this.mapImg.src = "images/splash.png"
	//xResizeTo(this.mapImageDiv, this.width, this.height);
	//this.mapHolder.appendChild(this.mapImageDiv);
	////this.mapImageDiv = xGetElementsByClassName("mapImage", this.mapHolder)[0];
	////this.mapImg = xGetElementsByClassName("theMap", this.mapImageDiv)[0];
	this.mapImageDiv = xGetElementById("mapImage");
	this.mapImg = xGetElementById("theMap");
	this.mapImg.className = "theMap";
	this.loader = document.createElement('img');
	this.loader.className = "loader";
	this.mapStatus = document.createElement('div');
	this.mapStatus.id = 'mapStatus';
	this.mapStatus.className = "status";
	this.toggleLoader(false);
	
	this.mapHolder.insertBefore(this.loader, this.glass.nextSibling);
	this.coords = document.createElement('div');
	this.coords.className = "coords";
	this.coords.coordsbg = document.createElement('div');
	this.coords.coordsbg.className = "coordsbg";
	this.coords.coordstext = document.createElement('div');
	this.coords.coordstext.className = "coordstext";
	this.coords.appendChild(this.coords.coordsbg);
	this.coords.appendChild(this.coords.coordstext);
		
	this.mapHolder.insertBefore(this.coords, this.loader.nextSibling);
	this.mapHolder.insertBefore(this.mapStatus, this.coords.nextSibling);
	

	this.legendImg = legendImg;
	this.layerHandler = layerHandler;
	
	// Set this so we can find this MapManager object from the mapHolder element
	this.mapHolder.myMapManager = this;
	
	this.actionState = 'zoomin';	// zoomin/zoomout/pan/transect/query
	this.dragging = false;
	
	this.url = '';
	this.mapService = mapServiceUrl;
	
	this.defaultExtent = [-158,-157,-90,90]
	if (defaultExtent) this.defaultExtent = defaultExtent;
	
	this.history = new Object();
	this.history.id = null;
	this.history.views = new Array();
	this.history.views[0] = new Array();
	this.history.views[0]['extent'] = defaultExtent;
	this.history.views[0]['layers'] = new Array();
	this.history.views[0]['mapsize'] = [this.width,this.height];
	this.history.init = -1;
	this.history.current = 0;
	this.history.last = 0;
	
	//if (refImg) this.refImg = refImg;
	
	this.zoomFactor = 2;
	
	this.enableDisplayCoords();
}

MapManager.prototype.getHolderWidth = function() {
	return(xWidth(this.mapHolder));
}

MapManager.prototype.getHolderHeight = function() {
	return(xHeight(this.mapHolder));
}

MapManager.prototype.setMapSize = function(mapw, maph) {
	if (this.width && this.height) {
		this.oldwidth=this.width;
		this.oldheight=this.height;
	} else {
		this.oldwidth=mapw;
		this.oldheight=maph;
	}
	this.width = mapw;
	this.height = maph;
}

MapManager.prototype.refreshSize = function(mapw, maph) {
	if (this.history.init == -1 || (this.width == this.oldwidth && this.height == this.oldheight) ) {
		//this.reset();
	} else {
		this.requestMap();
	}
}

MapManager.prototype.zoomInMouseDown = function(init, pageX, pageY) {
	var zoombox = init.ele;
	var pageX1 = init.pX1;
	var pageY1 = init.pY1;
	var eleX1 = init.eX1;
	var eleY1 = init.eY1;
	var x = eleX1;
	var y = eleY1;
	if (x < 0) x = 0;
	if (y < 0) y = 0;
	if (x >= zoombox.getParentWidth()) x = zoombox.getParentWidth() - 1;
	if (y >= zoombox.getParentHeight()) y = zoombox.getParentHeight() - 1;
	zoombox.reset();
	zoombox.setCoords(x, y);
	zoombox.setMode('cross');  // Start box or cross by drawing cross, draw box once mouse starts moving
	zoombox.drawCursor();
}

MapManager.prototype.zoomInMouseMove = function(init, pageX, pageY) {
	var zoombox = init.ele;
	var pageX1 = init.pX1;
	var pageY1 = init.pY1;
	var eleX1 = init.eX1;
	var eleY1 = init.eY1;
	// Calculate coordinates inside zoombox container
	var x = pageX - pageX1 + eleX1;
	var y = pageY - pageY1 + eleY1;
	if (x < 0) x = 0;
	if (y < 0) y = 0;
	if (x >= zoombox.getParentWidth()) x = zoombox.getParentWidth() - 1;
	if (y >= zoombox.getParentHeight()) y = zoombox.getParentHeight() - 1;
	zoombox.setCoords(zoombox.X1, zoombox.Y1, x, y);
	zoombox.setMode('box');
	zoombox.drawCursor();
}

MapManager.prototype.zoomOutMouseMove = function(init, pageX, pageY) {
	var zoombox = init.ele;
	var pageX1 = init.pX1;
	var pageY1 = init.pY1;
	var eleX1 = init.eX1;
	var eleY1 = init.eY1;
	// Calculate coordinates inside zoombox container
	var x = pageX - pageX1 + eleX1;
	var y = pageY - pageY1 + eleY1;
	if (x < 0) x = 0;
	if (y < 0) y = 0;
	if (x >= zoombox.getParentWidth()) x = zoombox.getParentWidth() - 1;
	if (y >= zoombox.getParentHeight()) y = zoombox.getParentHeight() - 1;
	zoombox.setCoords(x, y);
	zoombox.setMode('cross');
	zoombox.drawCursor();
}

MapManager.prototype.panMouseMove = function(init, pageX, pageY) {
	var zoombox = init.ele;
	var pageX1 = init.pX1;
	var pageY1 = init.pY1;
	var eleX1 = init.eX1;
	var eleY1 = init.eY1;
	// Calculate coordinates inside zoombox container
	var x = pageX - pageX1 + eleX1;
	var y = pageY - pageY1 + eleY1;
	if (x < 0) x = 0;
	if (y < 0) y = 0;
	if (x >= zoombox.getParentWidth()) x = zoombox.getParentWidth() - 1;
	if (y >= zoombox.getParentHeight()) y = zoombox.getParentHeight() - 1;
	zoombox.setMode('cross');
	mapMan = zoombox.myMapManager;
	zoombox.setCoords(mapMan.width/2, mapMan.height/2);
	zoombox.drawCursor();
	dragOffsetX = pageX - pageX1;
	dragOffsetY = pageY - pageY1;
	xMoveTo(mapMan.mapImageDiv, dragOffsetX, dragOffsetY);
}

MapManager.prototype.transectMouseMove = function(init, pageX, pageY) {
	var zoombox = init.ele;
	var pageX1 = init.pX1;
	var pageY1 = init.pY1;
	var eleX1 = init.eX1;
	var eleY1 = init.eY1;
	// Calculate coordinates inside zoombox container
	var x = pageX - pageX1 + eleX1;
	var y = pageY - pageY1 + eleY1;
	if (x < 0) x = 0;
	if (y < 0) y = 0;
	if (x >= zoombox.getParentWidth()) x = zoombox.getParentWidth() - 1;
	if (y >= zoombox.getParentHeight()) y = zoombox.getParentHeight() - 1;
	zoombox.setCoords(zoombox.X1, zoombox.Y1, x, y);
	zoombox.setMode('line');
	zoombox.drawCursor();
}

MapManager.prototype.zoomInMouseUp = function(init, pageX, pageY) {
	var zoombox = init.ele;
	var pageX1 = init.pX1;
	var pageY1 = init.pY1;
	var eleX1 = init.eX1;
	var eleY1 = init.eY1;
	// Calculate coordinates inside zoombox container
	var x = pageX - pageX1 + eleX1;
	var y = pageY - pageY1 + eleY1;
	if (x < 0) x = 0;
	if (y < 0) y = 0;
	if (x >= zoombox.getParentWidth()) x = zoombox.getParentWidth() - 1;
	if (y >= zoombox.getParentHeight()) y = zoombox.getParentHeight() - 1;
	if ((Math.abs(zoombox.X1 - x) <= zoombox.jitter) && (Math.abs(zoombox.Y1 - y) <= zoombox.jitter)) {
		// box is too small, stick to the original point and draw a cross
		zoombox.setCoords(zoombox.X2, zoombox.Y2);
		zoombox.setMode('cross');
		zoombox.drawCursor(); // force cross
		var zoomFactor = zoombox.myMapManager.zoomFactor;
		var x1 = zoombox.getTLX();  var y1 = zoombox.getTLY();
		zoombox.myMapManager.zoomMap(zoomFactor, x1, y1);
	} else {
		zoombox.setCoords(zoombox.X1, zoombox.Y1, x, y);
		zoombox.setMode('box');
		zoombox.drawCursor();
		var x1 = zoombox.getTLX();  var y1 = zoombox.getTLY();
		var x2 = zoombox.getBRX();  var y2 = zoombox.getBRY();
		zoombox.myMapManager.zoomBoxMap(x1,y1,x2,y2);
	}
}

MapManager.prototype.zoomOutMouseUp = function(init, pageX, pageY) {
	var zoombox = init.ele;
	var pageX1 = init.pX1;
	var pageY1 = init.pY1;
	var eleX1 = init.eX1;
	var eleY1 = init.eY1;
	// Calculate coordinates inside zoombox container
	var x = pageX - pageX1 + eleX1;
	var y = pageY - pageY1 + eleY1;
	if (x < 0) x = 0;
	if (y < 0) y = 0;
	if (x >= zoombox.getParentWidth()) x = zoombox.getParentWidth() - 1;
	if (y >= zoombox.getParentHeight()) y = zoombox.getParentHeight() - 1;
	zoombox.setCoords(x, y);
	zoombox.setMode('cross');
	zoombox.drawCursor();
	var zoomFactor = zoombox.myMapManager.zoomFactor;
	var x1 = zoombox.getTLX();  var y1 = zoombox.getTLY();
	zoombox.myMapManager.zoomMap(zoomFactor, x1, y1);
}

MapManager.prototype.panMouseUp = function(init, pageX, pageY) {
	var zoombox = init.ele;
	var pageX1 = init.pX1;
	var pageY1 = init.pY1;
	var eleX1 = init.eX1;
	var eleY1 = init.eY1;
	// Calculate coordinates inside zoombox container
	var x = pageX - pageX1 + eleX1;
	var y = pageY - pageY1 + eleY1;
	if (x < 0) x = 0;
	if (y < 0) y = 0;
	if (x >= zoombox.getParentWidth()) x = zoombox.getParentWidth() - 1;
	if (y >= zoombox.getParentHeight()) y = zoombox.getParentHeight() - 1;
	
	if ((Math.abs(pageX1 - pageX) <= zoombox.jitter) && (Math.abs(pageY1 - pageY) <= zoombox.jitter)) {
		// drag is too small, stick to the original point, draw a cross, and recenter there
		zoombox.setMode('cross');
		zoombox.setCoords(x, y);
		zoombox.drawCursor(); // force cross
		var mapMan = zoombox.myMapManager;
		xMoveTo(mapMan.mapImageDiv, 0, 0);
		var zoomFactor = 1; // No zoom, just recenter
		mapMan.zoomMap(zoomFactor, x, y);
	} else {
		// calculate final offset and move recenter point
		zoombox.setMode('cross');
		var mapMan = zoombox.myMapManager;
		zoombox.setCoords(mapMan.width/2, mapMan.height/2);
		zoombox.drawCursor();
		var dragOffsetX = pageX - pageX1;
		var dragOffsetY = pageY - pageY1;
		xMoveTo(mapMan.mapImageDiv, dragOffsetX, dragOffsetY);
		var recenterX = parseInt(mapMan.width/2) - dragOffsetX;
		var recenterY = parseInt(mapMan.height/2) - dragOffsetY;
		var zoomFactor = 1; // No zoom, just recenter
		mapMan.zoomMap(zoomFactor, recenterX, recenterY);
	}
}

MapManager.prototype.transectMouseUp = function(init, pageX, pageY) {
	var zoombox = init.ele;
	var pageX1 = init.pX1;
	var pageY1 = init.pY1;
	var eleX1 = init.eX1;
	var eleY1 = init.eY1;
	// Calculate coordinates inside zoombox container
	var x = pageX - pageX1 + eleX1;
	var y = pageY - pageY1 + eleY1;
	if (x < 0) x = 0;
	if (y < 0) y = 0;
	if (x >= zoombox.getParentWidth()) x = zoombox.getParentWidth() - 1;
	if (y >= zoombox.getParentHeight()) y = zoombox.getParentHeight() - 1;
	if ((Math.abs(zoombox.X1 - x) <= zoombox.jitter) && (Math.abs(zoombox.Y1 - y) <= zoombox.jitter)) {
		// box is too small, stick to the original point and draw a cross
		zoombox.setCoords(zoombox.X2, zoombox.Y2);
		zoombox.setMode('cross');
		zoombox.drawCursor(); // force cross
		var zoomFactor = zoombox.myMapManager.zoomFactor;
		var x1 = zoombox.getTLX();  var y1 = zoombox.getTLY();
		//zoombox.myMapManager.zoomMap(zoomFactor, x1, y1);
		//alert('point query');
	} else {
		zoombox.setCoords(zoombox.X1, zoombox.Y1, x, y);
		zoombox.setMode('line');
		zoombox.drawCursor();
		var x1 = zoombox.getTLX();  var y1 = zoombox.getTLY();
		var x2 = zoombox.getBRX();  var y2 = zoombox.getBRY();
		//zoombox.myMapManager.zoomBoxMap(x1,y1,x2,y2);
		//alert('transect query');
	}
}

MapManager.prototype.setActionState = function(actionState) {
	actionState = actionState.toLowerCase();
	if (actionState == 'zoomin') {
		this.actionState = actionState;
		this.zoomFactor = Math.abs(this.zoomFactor);
		this.zoomBox.reset();
		this.zoomBox.onMouseDown = this.zoomInMouseDown;
		this.zoomBox.onMouseMove = this.zoomInMouseMove;
		this.zoomBox.onMouseUp = this.zoomInMouseUp;
		this.zoomBox.startMouseDown();
	} else if (actionState == 'zoomout') {
		this.actionState = actionState;
		this.zoomFactor = 0 - Math.abs(this.zoomFactor);  // negative zoom factor
		this.zoomBox.reset();
		this.zoomBox.onMouseDown = this.zoomInMouseDown; // no need to duplicate, so use zoomIn mousedown
		this.zoomBox.onMouseMove = this.zoomOutMouseMove; // but use our own zoomout move/up because we don't need boxes
		this.zoomBox.onMouseUp = this.zoomOutMouseUp;
		this.zoomBox.startMouseDown();
	} else if (actionState == 'pan') {
		this.actionState = actionState;
		this.zoomBox.reset();
		this.zoomBox.onMouseDown = this.zoomInMouseDown;
		this.zoomBox.onMouseMove = this.panMouseMove;
		this.zoomBox.onMouseUp = this.panMouseUp;
		this.zoomBox.startMouseDown();
	} else if (actionState == 'transect') {
		this.actionState = actionState;
		this.zoomBox.reset();
		this.zoomBox.onMouseDown = this.zoomInMouseDown;
		this.zoomBox.onMouseMove = this.transectMouseMove;
		this.zoomBox.onMouseUp = this.transectMouseUp;
		this.zoomBox.startMouseDown();
	} else if (actionState == 'query') {
		this.actionState = actionState;
		this.zoomBox.reset();
		this.zoomBox.onMouseDown = null;
		this.zoomBox.onMouseMove = null;
		this.zoomBox.onMouseUp = null;
		this.zoomBox.startMouseDown();
	} else {
		this.zoomBox.reset();
		this.zoomBox.onMouseDown = null;
		this.zoomBox.onMouseMove = null;
		this.zoomBox.onMouseUp = null;
		this.zoomBox.removeMouseDown();
	}
}

MapManager.prototype.lon360 = function(lon) {
	return(lon % 360);
}

MapManager.prototype.lon180 = function(lon) {
	lon = this.lon360(lon);
	if (lon > 180) return(lon-360);
	else return(lon);
}

MapManager.prototype.getLonSpan = function(lonmin, lonmax) {
	var lons = this.fitLons720(lonmin, lonmax);
	lonmin = lons[0];
	lonmax = lons[1];
	return(lonmax-lonmin);
}

MapManager.prototype.getLatSpan = function(latmin, latmax) {
	return(latmax-latmin);
}

MapManager.prototype.fitLons720 = function(lonmin, lonmax) {
	//Adjust lonmin and lonmax so that they fit within -360 to 360 in increasing order
	lonmin = this.lon180(lonmin);
	lonmax = this.lon180(lonmax);
	if (lonmin >= lonmax) {
		lonmax += 360;
	}
	if (lonmax > 360) {
		lonmin -= 360;
		lonmax -= 360;
	}
	if (lonmin < -360) {
		lonmin += 360;
		lonmax += 360;
	}
	return([lonmin, lonmax]);
}

MapManager.prototype.getLonMid = function(lonmin, lonmax) {
	lons = this.fitLons720(lonmin, lonmax);
	lonmin = lons[0];
	lonmax = lons[1];
	return(lonmax - (lonmax-lonmin)/2.0);
}

MapManager.prototype.getLatMid = function(latmin, latmax) {
	return(latmax - (latmax-latmin)/2.0);
}

MapManager.prototype.getDegMinSec = function(decDegree) {
	var degree = parseInt(decDegree);
	var decMinutes = 60 * Math.abs(decDegree - degree);
	var minutes = parseInt(decMinutes);
	var seconds = 60 * Math.abs(decMinutes - minutes);
	return([degree, minutes, seconds]);
}

MapManager.prototype.displayCoords = function(evt) {
	e = new xEvent(evt); // make cross-browser event object
	xPreventDefault(evt);
	// A clear "glass" layer has been placed in front of all elements in the container.
	// We are capturing the mouse coords from the offsets from either "glass" or the parent container,
	// which should be exactly the same.  If we didn't do this, sub elements inside the
	// the parent container would be our target in some browsers, and it is way too hard
	// to determine the offset coords of a parent in different browsers, since borders and
	// such can be handled differently.  This saves us some headache.  The downside is that
	// right clicking won't give you the option of saving an image.  This needs to be addressed.
	var myContainer = (e.target.className == 'zoomglass') ? e.target.offsetParent : e.currentTarget;
	//pX1 = e.pageX;  pY1 = e.pageY;
	//eX1 = e.offsetX;  eY1 = e.offsetY;
	if (this.history.init == -1) return false;  // We don't have our first map yet, so return
	this.coords.style.display = "block";
	var scales = this.getPixScaleXY();
	var scaleX = scales[0];
	var scaleY = scales[1];
	var lonMin = parseFloat(this.history.views[this.history.current]['extent'][0]);
	var latMax = parseFloat(this.history.views[this.history.current]['extent'][3]);
	var lon = this.lon180(lonMin + (e.offsetX * scaleX));
	var lat = latMax - (e.offsetY * scaleY);
	lonDMS = this.getDegMinSec(lon);
	latDMS = this.getDegMinSec(lat);
	lonSecs = lonDMS[2].toFixed(2);
	latSecs = latDMS[2].toFixed(2);
	// Uses padDigits utility function, defined at beginning of file
	lonstr = padDigits(lonDMS[0],4,"&nbsp;") + "&deg; " + padDigits(lonDMS[1],2,"&nbsp;") + "' " + padDigits(lonSecs,5,"&nbsp;") +'"';
	latstr = padDigits(latDMS[0],4,"&nbsp;") + "&deg; " + padDigits(latDMS[1],2,"&nbsp;") + "' " + padDigits(latSecs,5,"&nbsp;") +'"';
	this.coords.coordstext.innerHTML = "Lon:" + lonstr + "<br>Lat:" + latstr;
}

MapManager.prototype.enableDisplayCoords = function() {
	// This displays the lat/lon coordinates of current mouse position
	if (!this.displayCoordsCurried) this.displayCoordsCurried = window.curry(this, this.displayCoords);
	xRemoveEventListener(this.mapHolder, 'mousemove', this.displayCoordsCurried, false);
	xAddEventListener(this.mapHolder, 'mousemove', this.displayCoordsCurried, false);
}

MapManager.prototype.disableDisplayCoords = function() {
	// This disables displays the lat/lon coordinates of current mouse position
	xRemoveEventListener(this.mapHolder, 'mousemove', this.displayCoordsCurried, false);
}

MapManager.prototype.getPixScaleXY = function() {
	var currentview = this.history.views[this.history.current];
	var extent = currentview['extent'];
	var lonMin = parseFloat(extent[0]);
	var lonMax = parseFloat(extent[1]);
	var latMin = parseFloat(extent[2]);
	var latMax = parseFloat(extent[3]);
	var myLonSpan = this.getLonSpan(lonMin, lonMax);
	var myLatSpan = this.getLatSpan(latMin, latMax);
	var pxScaleX = myLonSpan / this.width;
	var pxScaleY = myLatSpan / this.height;
	return([pxScaleX, pxScaleY]);
}

MapManager.prototype.loadNewMap = function(result) {

	// Ideally should receive an image name myMapxxx.png and load it into proper div
	// Receives
	// 0- URL to map image
	// 1- URL to legend image
	// 2- URL to scalebar image
	// 3- URL to reference map image
	// 4- Lon Min
	// 5- Lon Max
	// 6- Lat Min
	// 7- Lat Max
	// 8- Map pixel width
	// 9- Map pixel height

	var mapInfo = result;
	var mapImg = mapInfo[0];
	var legendImg = mapInfo[1];
	var scaleImg = mapInfo[2];
	var refImg = mapInfo[3];
	var extent = mapInfo[4];
	lonMin = parseFloat(extent[0]);
	lonMax = parseFloat(extent[1]);
	latMin = parseFloat(extent[2]);
	latMax = parseFloat(extent[3]);
	extent = [lonMin,lonMax,latMin,latMax];
	mapWidth = parseFloat(mapInfo[5]);
	mapHeight = parseFloat(mapInfo[6]);
	
	clearInterval(this.intervalId);
	// Load new layer's image...
	for (var i = 0; i < this.layerHandler.layerList.length; i++) {
		if(this.layerHandler.layerList[i].config.update) {
			xGetElementById(this.layerHandler.layerList[i].config.layerId + '_img').src = mapImg[i];
			xGetElementById(this.layerHandler.layerList[i].config.layerId + '_legend').src = legendImg[i];
			
			// While this is set to false, layers will NOT be reprocessed
			this.layerHandler.layerList[i].config.update = false;
		}
		
	}	
	
	this.history.current++;
	if (this.history.init == -1) {
		this.history.init = 0;
		this.history.current = 0;
	}

	this.history.last = this.history.current;
	this.history.views[this.history.current] = new Array();
	var currentview = this.history.views[this.history.current];
	currentview['mapImg'] = mapImg;
	currentview['refImg'] = refImg;
	currentview['scaleImg'] = scaleImg;
	currentview['legendImg'] = legendImg;
	currentview['extent'] = extent;
	//currentview['layers'] = layers;
	//currentview['width'] = mapWidth;
	//currentview['height'] = mapHeight;
	this.history.views.length = this.history.current + 1;
	this.mapImg.className = "imgCenter";
	//this.mapImg.src = mapImg;
	
	//this.mapImg.width = "100%";
	//this.legendImg.src = legendImg;
	//this.scaleImg.src = scaleImg;
	//this.refImg.src = refimg;

	//Believe it or not, next line causes problems on IE.
	//this.mapImageDiv.innerHTML = '<img src="' + mapImg + '" width="100%" />';
	
	this.reset();
	

}

MapManager.prototype.errorCallback = function(result) {
	alert("Error in JSON RPC request!!!");
}

MapManager.prototype.requestMap = function(newextent) {
	var id = Math.floor(Math.random()*900000)+100000 + '' + Date.parse(Date()); // random num 100k-999k + epoch seconds
	var currentview = this.history.views[this.history.current];
	currentview.id = id;
	if (!(newextent)) {
		newextent = currentview['extent'];
	}
	var mapWidth = this.width;
	var mapHeight = this.height;
	var callback = window.curry(this, this.loadNewMap);
	
	this.toggleLoader('on');

	var mapService = this.mapService;
	var mapMethod = "mapper";

	sendJsonRpc(mapService, mapMethod, [mapWidth, mapHeight, newextent, this.layerHandler.layerList], callback, currentview.id);
	
}

MapManager.prototype.zoomMap = function(zoomFactor, imgx, imgy) {
	var id = Math.floor(Math.random()*900000)+100000 + '' + Date.parse(Date()); // random num 100k-999k + epoch seconds
	var currentview = this.history.views[this.history.current];
	currentview.id = id;
	extent = currentview['extent'];

	var mapWidth = this.width;
	var mapHeight = this.height;
	//var callback = this.loadNewMap;
	var callback = window.curry(this, this.loadNewMap);
	
	this.toggleLoader('on');
	var mapService = this.mapService;
	var mapMethod = "zoomer";
	for (var i = 0; i < this.layerHandler.layerList.length; i++) {
		this.layerHandler.layerList[i].config.update = true;
	}
	sendJsonRpc(mapService, mapMethod, [zoomFactor, imgx, imgy, mapWidth, mapHeight, extent, this.layerHandler.layerList], callback, currentview.id);
}

MapManager.prototype.zoomBoxMap = function(imgx1, imgy1, imgx2, imgy2) {
	var id = Math.floor(Math.random()*900000)+100000 + '' + Date.parse(Date()); // random num 100k-999k + epoch seconds
	var currentview = this.history.views[this.history.current];
	currentview.id = id;
	extent = currentview['extent'];
	var mapWidth = this.width;
	var mapHeight = this.height;
	//var callback = this.loadNewMap;
	var callback = window.curry(this, this.loadNewMap);
	
	this.toggleLoader('on')
	var mapService = this.mapService;
	var mapMethod = "zoomboxer";
	for (var i = 0; i < this.layerHandler.layerList.length; i++) {
		this.layerHandler.layerList[i].config.update = true;
	}
	sendJsonRpc(mapService, mapMethod, [imgx1, imgy1, imgx2, imgy2, mapWidth, mapHeight, extent, this.layerHandler.layerList], callback, currentview.id);
}

MapManager.prototype.defaultZoom = function() {
	if (this.history.init == -1) init = 0;
	else init = this.history.init;
	var initview = this.history.views[init];
	var extent = initview['extent'];
	this.requestMap(extent);
}

MapManager.prototype.toggleLoader = function(onoff) {
	
	if (onoff) {
		var currentview = this.history.views[this.history.current];
		this.loader.src = "/oe/images/logo/alternate.gif";
		xShow(this.loader);
		xShow(this.mapStatus);
		this.intervalId = setInterval("getStatus(" + currentview.id + ")", 5000);
		//getStatus(currentview.id);
		//setTimeout("getStatus(" + currentview.id + ")", 20000);
	} else {
		this.mapStatus.innerHTML = "";
		xHide(this.loader);
		xHide(this.mapStatus);
		this.loader.src = "";
	}
}

MapManager.prototype.reset = function() {
	xMoveTo(this.mapImageDiv, 0, 0);
	zbRemoveDocumentListeners();
	this.zoomBox.reset();
	this.toggleLoader(false);
}

MapManager.prototype.setLayers = function(layerObj) {
	var currentview = this.history.views[this.history.current];
	if (layerObj == currentview['layerObj']) {
		// do nothing, no layers have changed
	} else {
		this.requestMap();
	}
}

MapManager.prototype.backHistory = function() {
// 	if ((this.history.current > this.history.init) && (this.history.init > -1)) {
// 		this.history.current--;
// 		var view = this.history.views[this.history.current];
// 		this.mapLayer.setHTML('<img src="'+view['mapImg']+'" width='+this.mapw+' height='+this.maph+' hspace=0 vspace=0 border=0>');
// 		if (view['refImg']) this.refImg.setImg(view['refImg']);
// 		this.setLegend(view['legendImg']);
// 		this.latLon.setHTML('<font face="Verdana, Arial, Helvetica, sans-serif" size="2"><b><img src="graphics/clear.gif" width=5 height=1 hspace=0 vspace=0 border=0>Longitude: '+(Math.round(100*view['extent'][0])/100)+' to '+(Math.round(100*view['extent'][2])/100)+' <img src="graphics/clear.gif" width=30 height=1 hspace=0 vspace=0 border=0> Latitude: '+(Math.round(100*view['extent'][1])/100)+' to '+(Math.round(100*view['extent'][3])/100)+'</b></font>');
// 		parent.layers.setLayers(view['layers']);
// 	}
}

MapManager.prototype.nextHistory = function() {
// 	if (this.history.current < this.history.last) {
// 		this.history.current++;
// 		var view = this.history.views[this.history.current];
// 		this.mapLayer.setHTML('<img src="'+view['mapImg']+'" width='+this.mapw+' height='+this.maph+' hspace=0 vspace=0 border=0>');
// 		if (view['refImg']) this.refImg.setImg(view['refImg']);
// 		this.setLegend(view['legendImg']);
// 		this.latLon.setHTML('<font face="Verdana, Arial, Helvetica, sans-serif" size="2"><b><img src="graphics/clear.gif" width=5 height=1 hspace=0 vspace=0 border=0>Longitude: '+(Math.round(100*view['extent'][0])/100)+' to '+(Math.round(100*view['extent'][2])/100)+' <img src="graphics/clear.gif" width=30 height=1 hspace=0 vspace=0 border=0> Latitude: '+(Math.round(100*view['extent'][1])/100)+' to '+(Math.round(100*view['extent'][3])/100)+'</b></font>');
// 		parent.layers.setLayers(view['layers']);
// 	}
}

MapManager.prototype.zoomBoxed = function(e) {
// 	var target = e.getTarget();
// 	+'&imgbox='+target.zoomBox.getTLX()+'+'+target.zoomBox.getTLY()
// 	+'+'+target.zoomBox.getBRX()+'+'+target.zoomBox.getBRY()
// 	target.draw(options);
}

MapManager.prototype.zoomClicked = function(e) {
// 	var target = e.getTarget();
// 	if (target.actionState == 'query') {
// 		target.query(target.zoomBox.X1, target.zoomBox.Y1);
// 		return;
// 	}
// 	if (target.actionState == 'zoomin') var zoomdir = 1;
// 	if (target.actionState == 'pan') var zoomdir = 0;
// 	if (target.actionState == 'zoomout') var zoomdir = -1;
// 	var currentview = target.history.views[target.history.current];
// 	var datafile = currentview['layers'][0];
// 	var basemap = currentview['layers'][1];
// 	var yearindex = currentview['layers'][2];
// 	var monthindex = currentview['layers'][3];
// 	var dayindex = currentview['layers'][4];
// 	var cptfile = currentview['layers'][5];
// 	var catchnames = currentview['layers'][6];
// 	if (!catchnames) { catchnames = new Array(); }
// 	var options = '&datafile='+datafile
// 		+'&basemap='+basemap
// 		+'&yearindex='+yearindex
// 		+'&monthindex='+monthindex
// 		+'&dayindex='+dayindex
// 		+'&cptfile='+cptfile
// 		+'&catchnames='+catchnames.join('+')
// 		+'&imgext='+currentview['extent'].join('+')
// 		+'&imgsize='+target.mapw+'+'+target.maph
// 		+'&imgxy='+target.zoomBox.X1+'+'+target.zoomBox.Y1
// 		+'&mapsize='+target.mapw+'+'+target.maph
// 		+'&template='+target.loader
// 		+'&zoomdir='+zoomdir
// 		+'&zoomsize='+target.zoomsize;
// 	target.draw(options);
}

MapManager.prototype.pan = function(x, y) {
// 	var zoomdir = 0;
// 	var currentview = this.history.views[this.history.current];
// 	var datafile = currentview['layers'][0];
// 	var basemap = currentview['layers'][1];
// 	var yearindex = currentview['layers'][2];
// 	var monthindex = currentview['layers'][3];
// 	var dayindex = currentview['layers'][4];
// 	var cptfile = currentview['layers'][5];
// 	var catchnames = currentview['layers'][6];
// 	if (!catchnames) { catchnames = new Array(); }
// 	var options = '&datafile='+datafile
// 		+'&basemap='+basemap
// 		+'&yearindex='+yearindex
// 		+'&monthindex='+monthindex
// 		+'&dayindex='+dayindex
// 		+'&cptfile='+cptfile
// 		+'&catchnames='+catchnames.join('+')
// 		+'&imgext='+currentview['extent'].join('+')
// 		+'&imgsize='+this.mapw+'+'+this.maph
// 		+'&imgxy='+x+'+'+y
// 		+'&mapsize='+this.mapw+'+'+this.maph
// 		+'&template='+this.loader
// 		+'&zoomdir='+zoomdir
// 		+'&zoomsize='+this.zoomsize;
// 	this.draw(options);
}

MapManager.prototype.query = function(x, y) {
// 	var currentview = this.history.views[this.history.current];
// 	var datafile = currentview['layers'][0];
// 	var basemap = currentview['layers'][1];
// 	var options = '&query=true'
// 		+'&datafile='+datafile
// 		+'&basemap='+basemap
// 		+'&imgext='+currentview['extent'].join('+')
// 		+'&imgsize='+this.mapw+'+'+this.maph
// 		+'&imgxy='+x+'+'+y
// 		+'&template='+this.loadquery;
// 	this.draw(options);
// 	this.loadLayer.setVisible(false);
}

MapManager.prototype.refPan = function(x, y) {
// 	var currentview = this.history.views[this.history.current];
// 	var zoomdir = 0;
// 	var datafile = currentview['layers'][0];
// 	var basemap = currentview['layers'][1];
// 	var yearindex = currentview['layers'][2];
// 	var monthindex = currentview['layers'][3];
// 	var dayindex = currentview['layers'][4];
// 	var cptfile = currentview['layers'][5];
// 	var catchnames = currentview['layers'][6];
// 	if (!catchnames) { catchnames = new Array(); }
// 	var options = '&datafile='+datafile
// 		+'&basemap='+basemap
// 		+'&yearindex='+yearindex
// 		+'&monthindex='+monthindex
// 		+'&dayindex='+dayindex
// 		+'&cptfile='+cptfile
// 		+'&catchnames='+catchnames.join('+')
// 		+'&imgext='+currentview['extent'].join('+')
// 		+'&imgsize='+this.mapw+'+'+this.maph
// 		+'&refxy='+x+'+'+y
// 		+'&mapsize='+this.mapw+'+'+this.maph
// 		+'&template='+this.loader
// 		+'&zoomdir='+zoomdir
// 		+'&zoomsize='+this.zoomsize;;
// 	this.draw(options);
}

MapManager.prototype.mouseDown = function(e) {
// 	var target = e.getTarget();
// 	if (target.lockmap) return true;
// 	// For some reason, NS6/Mozilla calls MouseDown and MouseUp twice,
// 	// mouseme is a fix that makes sure the functions are only called once.
// 	if (target.mouseme) return true;
// 	target.mouseme = true;
// 	target.dragging = true;
// 	if (target.actionState == 'zoomin' || target.actionState == 'zoomout' || target.actionState == 'query') {
// 		// let the zoombox handle redrawing itself
// 		target.zoomBox.MouseDown(e);
// 	} else if (target.actionState == 'pan') {
// 		//don't move until mousemove
// 		target.panx = e.x;
// 		target.pany = e.y;
// 		target.zoomBox.drawCursor(e.x, e.y);
// 	}
// 	return false;
}

MapManager.prototype.mouseMove = function(e) {
// 	var target = e.getTarget();
// 	if (target.lockmap) return true;
// 	if (target.dragging) {
// 		// let the zoombox handle redrawing itself
// 		if (target.actionState == 'zoomout') {
// 			target.zoomBox.MouseMove(e, false);
// 		} else if (target.actionState == 'zoomin' || target.actionState == 'query') {
// 			target.zoomBox.MouseMove(e, true);
// 		} else if (target.actionState == 'pan') {
// 			// handle moving the map layer here
// 			target.zoomBox.drawCursor(target.mapw/2, target.maph/2);
// 			target.mapLayer.moveTo(target.mapx + (e.x - target.panx), target.mapy + (e.y - target.pany));
// 			target.mapLayer.setClip(new Array(target.pany - e.y, (target.panx - e.x) + target.mapw, (target.pany - e.y) + target.maph, target.panx - e.x));
// 		}
// 		return false;
// 	} else {
// 		return true;
// 	}
}

MapManager.prototype.mouseUp = function(e) {
// 	var target = e.getTarget();
// 	if (!(target.mouseme)) return true;
// 	target.mouseme = false;
// 	if (target.lockmap) return true;
// 	if (target.actionState == 'zoomin') {
// 		// let the zoombox draw the final box and evoke zoombox event
// 		target.zoomBox.MouseUp(e, true);
// 	} else if (target.actionState == 'zoomout' || target.actionState == 'query') {
// 		// let the zoombox draw the final cursor and evoke zoomclick event
// 		target.zoomBox.MouseUp(e, false);
// 	} else if (target.actionState == 'pan') {
// 		// handle moving the map layer here
// 		if (target.dragging) {
// 			target.mapLayer.moveTo(target.mapx + (e.x - target.panx), target.mapy + (e.y - target.pany));
// 			target.mapLayer.setClip(new Array(target.pany - e.y, (target.panx - e.x) + target.mapw, (target.pany - e.y) + target.maph, target.panx - e.x));
// 		}
// 		if ((target.panx == e.x) && (target.pany == e.y)) {
// 			target.zoomBox.drawCursor(e.x, e.y);
// 			target.pan(e.x, e.y);
// 		} else {
// 			target.zoomBox.drawCursor(target.mapw/2, target.maph/2);
// 			target.pan(Math.round(target.mapw/2) - (e.x - target.panx), Math.round(target.maph/2) - (e.y - target.pany));
// 			target.panx = null;
// 			target.pany = null;
// 		}
// 	}
// 	target.dragging = false;
// 	return false;
}

//////////////////////////////////
// End MapManager class definition
//////////////////////////////////
