/*
 * Singleton: PolyTile
 * Author:    Stratosphere HPC
 *
 * manages a collection of PolyTile Layers.
 */

var PolyTile = new Object();
PolyTile.layerList       = [];
PolyTile.activeLayer     = null;
PolyTile.map             = null;

PolyTile.domain          = "polytile.com";
PolyTile.tileHandler     = "tile.php";
PolyTile.identifyHandler = "identify.php";
PolyTile.isPng           = false;
PolyTile.siteid          = "doctorconnect";

PolyTile.setContext = function(map, lotusNotes, busySymbol, targetSymbol, corporateLogo, siteLogo, outerMetro, opacitySelector, countryCode) {
	PolyTile.map             = map;
	PolyTile.lotusNotes      = lotusNotes;
	PolyTile.busySymbol      = busySymbol;
	PolyTile.targetSymbol    = targetSymbol;
	PolyTile.opacitySelector = opacitySelector;
	PolyTile.corporateLogo   = corporateLogo;
	PolyTile.siteLogo        = siteLogo;
	PolyTile.outerMetro      = outerMetro;
	PolyTile.countryCode     = countryCode;
}

PolyTile.createLayer = function(id, name, mapid, theme, opacity, dissolve, infoWindowHTML) {
  var layer = new PolyTileLayer(id, name, mapid, theme, opacity, dissolve, infoWindowHTML);
  PolyTile.layerList[PolyTile.layerList.length] = layer;
  return layer;
};

PolyTile.getNumberOfLayers = function() {
	return PolyTile.layerList.length;
}

PolyTile.getLayer = function(i) {
	return PolyTile.layerList[i];
}

PolyTile.getLayerById = function(id) {
	for (var i=0; i<PolyTile.layerList.length; i++) {
		if (PolyTile.layerList[i].id == id) {
			return PolyTile.layerList[i];
		}
	}
	return null;
}

PolyTile.moveLayerToTop = function(layer) {
	var j = 0;
	for (var i=0; i<PolyTile.layerList.length; i++) {
		if (PolyTile.layerList[i] == layer) {
			j = i;
			break;
		}
	}
	
	for (var i=j; i>0; i--) {
		PolyTile.layerList[j] = PolyTile.layerList[j-1];
	}
	PolyTile.layerList[0] = layer;
}

PolyTile.setActiveLayer = function(layer) {
	PolyTile.activeLayer = layer;
	PolyTile.updateLegend();
	if (PolyTile.activeLayer.input != null) {
		PolyTile.activeLayer.input.checked=true;
		PolyTile.activeLayer.input.defaultChecked=true;
	}
}

PolyTile.identify = function(overlay, point) {
	PolyTile.identifyRegion([point]);
}

PolyTile.identifyRegion = function(info) {
	if (PolyTile.activeLayer != null) {
		PolyTile.activeLayer.identifyRegion(info);
	}
}

PolyTile.updateLegend = function() {
	PolyTile.legendControl.update();
}

PolyTile.setOverlayOpacity = function(opacity) {
	if (PolyTile.activeLayer != null) {
	  PolyTile.map.removeOverlay(PolyTile.activeLayer.overlay);
	  PolyTile.activeLayer.setOpacity(opacity);
	  PolyTile.map.addOverlay(PolyTile.activeLayer.overlay);
	}
}

PolyTile.selectOpacity = function(opacity) {
	if (PolyTile.opacitySelector != null) {
		var select = document.getElementById(PolyTile.opacitySelector);
		if (select != null) {
			for (var i=0; i<select.options.length; i++){
				 if (select.options[i].value == opacity) {
					 select.options[i].selected = true;
				 }
			}
		}
	}
}

/*
 * Class:  PolyTileLayer
 * Author: Stratosphere HPC
 *
 * PolyTile layer displayed as a GTileLayerOverlay with support for polygon identify function
 *
 * Constructor Parameters: 
 * id              abbreviation
 * name            full name
 * mapid           PolyTile map identifier
 * theme           PolyTile polygon theme identifier
 * opacity         between 0 and 1
 * dissolve        whether to dissolve underlying area boundaries
 * infoWindowHTML  function to render info window HTML
 */

function PolyTileLayer(id, name, mapid, theme, opacity, dissolve, infoWindowHTML, legend) {
  this.id = id;
  this.name = name;
  this.mapid = mapid;
  this.theme = theme;
  this.opacity = opacity;
  this.dissolve = dissolve;
  this.infoWindowHTML = infoWindowHTML;
  
  this.nextLayer = null;
  
  this.tilelayer = new GTileLayer();
  this.tilelayer.getTileUrl = function(tile,zoom) {
    var k = (tile.x + tile.y) % 4;
    return "http://w" + k + "." + PolyTile.domain + "/" + PolyTile.tileHandler + "?id=" + mapid + "&z=" + zoom + "&x=" + tile.x + "&y=" + tile.y + "&c=" + theme + "&n=" + id + "&d=" + (dissolve ? "1" : "0") + "&s=" + PolyTile.siteid;
  };

  this.tilelayer.isPng = function() {
    return PolyTile.isPng;
  };
 
  this.tilelayer.getOpacity = function() {
    return opacity;
  };

  this.overlay = new GTileLayerOverlay(this.tilelayer);	
}

PolyTileLayer.prototype.setOpacity = function(opacity) {
	this.opacity = opacity;
	this.tilelayer.getOpacity = function() {
		return opacity;
	};
}

PolyTileLayer.prototype.setInfoWindowHTML = function(infoWindowHTML) {
	this.infoWindowHTML = infoWindowHTML;
}

PolyTileLayer.prototype.enableClickWrap = function() {
  if (this.clickWrapId != null && !this.clickWrapShown) {
	  this.clickWrapShown = true;
	  document.getElementById(this.clickWrapId).style.display = "block";
  }
}

PolyTileLayer.prototype.disableClickWrap = function() {
  if (this.clickWrapId != null) {
	  document.getElementById(this.clickWrapId).style.display = "none";
	  this.clickWrapShown = false;
  }
}

PolyTileLayer.prototype.linkCheckBox = function(clickWrapLayer) {
  
  this.controlId = this.id + "Check";
  this.input = document.getElementById(this.controlId);
  this.clickWrapShown = false;
  this.clickWrapId = clickWrapLayer;
  this.previousLayer = null;
  
  var layerId = this.id;
  
  this.clickFunction = function() {
	  var layer = PolyTile.getLayerById(layerId);
	  
	  layer.enableClickWrap();
	  
	  map.clearOverlays();
	  PolyTile.activeLayer = null;
	  
	  if (layer.input.checked) {
		  for (var i=0; i<PolyTile.getNumberOfLayers(); i++) {
			  var x = PolyTile.getLayer(i);
			  if (x.id != layer.id) {
				  var y = document.getElementById(x.controlId);
                  if (y.checked) {
                     layer.previousLayer = x;
                  }
                  y.checked=false;
              } else {
	              map.addOverlay(x.overlay);
	              PolyTile.activeLayer = x;
	              PolyTile.selectOpacity(x.opacity);
	          }
	      }
	  }
	  PolyTile.updateLegend();
  };
	  
  this.cancelFunction = function() {
	  var layer = PolyTile.getLayerById(layerId);
	  
	  layer.disableClickWrap();
			
	  map.clearOverlays();
	  PolyTile.activeLayer = null;
	  
	  var previousLayer = layer.previousLayer == null ? PolyTile.getLayerById('RA') : layer.previousLayer;
	  
	  for (var i=0; i<PolyTile.getNumberOfLayers(); i++) {
		  var x = PolyTile.getLayer(i);
		  var y = document.getElementById(x.controlId);
		  if (x != previousLayer) {
	          y.checked=false;
	      } else {
              y.checked=true;
	          map.addOverlay(x.overlay);
	          PolyTile.activeLayer = x;
	          PolyTile.selectOpacity(x.opacity);
	      }
	  }
      PolyTile.updateLegend();
  };	  
 
  GEvent.addDomListener(this.input, "click", this.clickFunction);
}

PolyTileLayer.prototype.getIdentifyUrl = function(point) {
    return "http://www." + PolyTile.domain + "/" + PolyTile.identifyHandler + "?y=" + point.lat() + "&x=" + point.lng() + "&z=" + map.getZoom() + "&c=" + this.theme + "&id=" + this.mapid + "&s=" + PolyTile.siteid;
}

PolyTileLayer.prototype.setNextLayer = function(layer) {
	this.nextLayer = layer;
}

PolyTileLayer.prototype.identifyRegion = function(info) {
	var layer = this;
	new JSN(layer.getIdentifyUrl(info[0]), function(region) {
		info[info.length] = region;
		if (layer.nextLayer != null) {
		    layer.nextLayer.identifyRegion(info);
		} else {
			PolyTile.map.openInfoWindowHtml(info[0], layer.infoWindowHTML(info), false);
		}
	});
}

 /*
  * Class:  SimpleList
  * Author: Stratosphere HPC
  *
  * Basic List for URL processing
  */

 function SimpleList() {
 	this.list = [];
 }

 SimpleList.prototype.insert = function(s) {
 	for (var i=0; i<this.list.length; i++) {
 		if (this.list[i] == s) {
 			return i;
 		}
 	}
 	this.list[this.list.length] = s;
 	return this.list.length-1;
 }

 SimpleList.prototype.insertAll = function(list, j) {
 	for (var i=0; i<list.length; i++) {
 		list[i][j][1] = this.insert(list[i][j][0]);
 	}
 }

 /*
  * Class:  Filter
  * Author: Stratosphere HPC
  *
  * Basic Filter for URL processing
  */

 function Filter(type, args) {
 	this.type = type;
	if (type == "equality") {
		this.key = args[0];
		this.value = args[1];
	} else if (type == "range") {
		this.key = args[0];
		this.low = args[1];
		this.high = args[2];
	} else if (type == "key") {
		this.key = args[0];
	}
 }

 /*
  * Class:  GooglePrint
  * Author: Stratosphere HPC
  *
  * Server Fused Image to compress all Tile URLs into single URL
  */

 function GooglePrint() {
 }

 GooglePrint.prototype.getMapTileUrlStrings = function() {
 	  var result = [];
 	  var n=0;
 	  var div1 = map.getContainer().childNodes[0].childNodes[0];
 	  for (var i=0; i<div1.childNodes.length; i++) {
 		  var div2 = div1.childNodes[i];
 		  if (div2.style.display=="none" || div2.style.visibility=="hidden") {
 			  continue;
 		  }
 		  for (var j=0; j<div2.childNodes.length; j++) {
 			  var div3 = div2.childNodes[j];
 			  
 			  for (var k=0; k<div3.childNodes.length; k++) {
 				  var div4 = div3.childNodes[k];
 				  if (div4.childNodes.length > 0) {
 					  for (var t=0; t<div4.childNodes.length; t++) {
 						  var img = div4.childNodes[t];
 						  if (img.src != null) {
 							  result[n++] = img.src;
 						  }
 					  }
 				  } else if (div4.src != null) {
 					  result[n++] = div4.src;
 				  }
 			  }
 		  }
 	  }
 	  return result;
 	}

 GooglePrint.prototype.getMapTileUrls = function () {
 		var urlString = this.getMapTileUrlStrings();
 		var url = [];
 		var n=0;
 		for (var i=0; i<urlString.length; i++) {
 			var u = this.parseUrl(urlString[i]);
 			if (u != null && u[4].length > 0) {
 				url[n] = u;
 				url[n][5] = [u[0][0] + u[1][0] + "." + u[2][0] + "/" + u[3][0],-1];
 				url[n][6] = [u[0][0] + u[2][0] + "/" + u[3][0],-1];
 				n++;
 			}
 		}
 		return url;
 	}

 GooglePrint.prototype.breakIntoArguments = function(s) {
 	  var result = [];
 	  var t=0;
 	  while (s.length > 0) {
 	    var i = s.indexOf('=');
 		if (i < 1) {
 		  return [];
 		} else {
 		  var k = s.substring(0,i);
 		  s = s.substring(i+1);
 		  var j = s.indexOf('&');
 	      var v = "";
 		  if (j < 0) {
 		    v = s;
 			s = "";
 		  } else {
 			v = s.substring(0,j);
 			s = s.substring(j+1);
 		  }
 		  result[t++] = [k,v,-1];
 	    }
 	  }
 	  return result;
 	}

 GooglePrint.prototype.parseUrl = function(url) {
 	  var i = url.indexOf('/');
 	  if (i < 0) {
 	    return null;
 	  }
 		
 	  while (i < url.length && url.charAt(i) == '/') {
 	    i++;
 	  }
 		
 	  var protocol = url.substring(0,i);
 	  var remainder = url.substring(i);
 		
 	  i = remainder.indexOf('.');
 	  if (i < 0) {
 	    return null;
 	  }
 	  var alias = remainder.substring(0,i);
 	  if (i+1 >= remainder.length) {
 	    return null;
 	  }
 	  remainder = remainder.substring(i+1);
 		
 	  i = remainder.indexOf('/');
 	  if (i < 0) {
 	    return null;
 	  }
 	  var domain = remainder.substring(0,i);
 	  if (i+1 >= remainder.length) {
 	    return null;
 	  }
 	  remainder = remainder.substring(i+1);
 	  
 	  i = remainder.indexOf('=');
 	  var path = "";
 	  if (i < 0) {
 	    path = remainder;
 	    remainder = "";
 	  } else {
 	    while (remainder.charAt(i) != '/' && remainder.charAt(i) != '?' && i >= 0) {
 		  i--;
 		}
 		path = remainder.substring(0,i+1);
 		remainder = remainder.substring(i+1);
 	  }
 	  var args = this.breakIntoArguments(remainder);
 	  return [[protocol,-1],[alias,-1],[domain,-1],[path,-1],args];
 	}

 GooglePrint.prototype.getTileCoordinates = function(p) {
 		var tx = Math.floor(p.x / 256);
 		var ty = Math.floor(p.y / 256);
 		var dx = p.x - 256*tx;
 		var dy = p.y - 256*ty;
 		return [tx,ty,dx,dy];
 	}

 GooglePrint.prototype.getBounds = function() {
 		var bounds = map.getBounds();
 		var south = bounds.getSouthWest().lat();
 		var west  = bounds.getSouthWest().lng();
 		var north = bounds.getNorthEast().lat();
 		var east  = bounds.getNorthEast().lng();
 		
 		var size   = map.getSize();
 		var width  = size.width;
 		var height = size.height;
 		
 		var proj = map.getCurrentMapType().getProjection();
 		var zoom = map.getZoom();
 		var nw   = this.getTileCoordinates(proj.fromLatLngToPixel(new GLatLng(north,west), zoom));
 		var se   = this.getTileCoordinates(proj.fromLatLngToPixel(new GLatLng(south,east), zoom));
 		if (se[0] < nw[0]) {
 			se[0] = Math.pow(2,zoom) - 1;
 			se[2] = 255;
 			width = 256*se[0]+se[2]  - (256*nw[0]+nw[2]);
 		}
 		if (se[1]  < nw[1]) {
 			se[1]  = Math.pow(2,zoom) - 1;
 			se[3]  = 255;
 			height = 256*se[1]+se[3] - (256*nw[1]+nw[3]);
 		} 		
 		var wt   = se[0] - nw[0] + 1;
 		var ht   = se[1] - nw[1] + 1;
 		
 		this.width = width;
 		this.height = height;
 		
 		return [nw,se,wt,ht,width,height];
 	}
 
 GooglePrint.prototype.insideBounds = function(infoPoint) {
	 var bounds = this.getBounds();
     var proj = map.getCurrentMapType().getProjection();
	 var zoom = map.getZoom();
	 var pt = proj.fromLatLngToPixel(new GLatLng(infoPoint.y, infoPoint.x),zoom);
	 
	 var maxX = 256*bounds[1][0] + bounds[1][2];
	 var minX = 256*bounds[0][0] + bounds[0][2];
	 var maxY = 256*bounds[1][1] + bounds[1][3];
	 var minY = 256*bounds[0][1] + bounds[0][3];
	 
	 return [pt.x >= minX && pt.x <= maxX && pt.y >= minY && pt.y <= maxY, pt.x - minX, pt.y - minY];
 }
 	
 GooglePrint.prototype.filterUrl = function(u,f) {
 		if (u[4] == null || u[4].length == null) {
 			return;
 		}
 		
 		for (var i=0; i<u[4].length; i++) {
 			if (f.type == "equality") {
 	  		  if (u[4][i][0] == f.key && u[4][i][1] == f.value) {
 				  return true;
 			  }
 			} else if (f.type == "range") {
 			  if (u[4][i][0] == f.key && (u[4][i][1] >= f.low && u[4][i][1] <= f.high)) {
 				  return true;
 			  }
 			} else if (f.type == "key") {
 			  if (u[6][1] == f.key) {
 				  return true;
 			  }
 			}
 		}
 		return false;
 	}

 GooglePrint.prototype.filterUrlList = function(url,f) {
 		var result = [];
 		for (var i=0; i<url.length; i++) {
 			if (this.filterUrl(url[i],f)) {
 				result[result.length] = url[i];
 			}
 		}
 		return result;
 	}

 GooglePrint.prototype.compressMapTileUrls = function() {
 		var url = this.getMapTileUrls();

 		url = this.filterUrlList(url, new Filter("equality", ["z", map.getZoom()]));
 		var bounds = this.getBounds(); 
 		url = this.filterUrlList(url, new Filter("range", ["x", bounds[0][0], bounds[1][0]]));
		url = this.filterUrlList(url, new Filter("range", ["y", bounds[0][1], bounds[1][1]]));

 		var protocol = new SimpleList();
 		protocol.insertAll(url,0);
 		
 		var alias = new SimpleList();
 		alias.insertAll(url,1);
 		
 		var domain = new SimpleList();
 		domain.insertAll(url,2);
 		
 		var path = new SimpleList();
 		path.insertAll(url,3);
 		
 		var page = new SimpleList();
 		page.insertAll(url,5);
 		
 		var key = new SimpleList();
 		key.insertAll(url,6);
 			
 		var result = bounds[0][0] + "&" + bounds[0][1] + "&" + bounds[0][2] + "&" + bounds[0][3] + "&" + bounds[4] + "&" + bounds[5] + "&" + bounds[2] + "&" + bounds[3] + "&";
 		var m=0;
 		for (var i=0; i<key.list.length; i++) {
 			var set = this.filterUrlList(url, new Filter("key", [i]));
 			var tuple = "";
 			if (set.length > 0) {
 				var constant = [];
 				for (var j=0; j<set[0][4].length; j++) {
 					var x = new SimpleList();
 					for (var k=0; k<set.length; k++) {
 						x.insert(set[k][4][j][1]);
 					}
 					if (j > 0) {
 						tuple += "&";
 					}
 					constant[j] = x.list.length == 1;
 					tuple += x.list.length == 1 ? set[0][4][j][0] + "=" + set[0][4][j][1] : set[0][4][j][0];
 				}
 				var variables = "";
 				var n=0;
 				for (var k=0; k<set.length; k++) {
 					if (n > 0) {
 						variables += "&";
 					}
 					variables += set[k][1][0];
 					n++;
 					for (var j=0; j<set[k][4].length; j++) {
 						if (!constant[j]) {
 							variables += "&" + set[k][4][j][1];
 							n++;
 						}	
 					}
 				}
 				if (m > 0) {
 					result += "?";
 				}
 				result += key.list[i] + "&" + tuple + "?" + variables;
 				m++;
 			}
 		}
 		return result;
 	}

 /*---------------------------------------------------------------------------*/
 /* PRINT WINDOW */

 PolyTile.openPrintWindow = function(dimensions, overlays, infoPt, infoHTML) {
 	var print   = new GooglePrint();
 	var url     = print.compressMapTileUrls();
 	var width   = print.width;
 	var height  = print.height;
 	
 	var clipOriginX = 0;
 	var clipOriginY = 0;
 	if (dimensions != null) {
 		if (width > dimensions[0] || height > dimensions[1]) {
 			clipOriginX = width  > dimensions[0] ? (width  - dimensions[0]) >> 1 : 0;
 			clipOriginY = height > dimensions[1] ? (height - dimensions[1]) >> 1 : 0;
 			width  = Math.min(width,  dimensions[0]);
 			height = Math.min(height, dimensions[1]);
 			url = 'clip&' + clipOriginX + '&' + clipOriginY + '&' + width + '&' + height + '&' + url;
 		}
 	}
 	
	if (infoPt != null) {
 		var inside = print.insideBounds(infoPt);
 		if (inside[0]) {
 			var originX = inside[1] - 15 - clipOriginX;
 			var originY = inside[2] - 15 - clipOriginY;
 			if (overlays == null) {
 				overlays = new Array();
 			}
 			overlays.push([PolyTile.targetSymbol,originX,originY,33]);
 		}
	}
 	
 	if (overlays != null) {
 		overlayURL = 'overlay&' + overlays.length + '&';
 		for (var i=0; i<overlays.length; i++) {
 			overlayURL += overlays[i][0] + '&' + overlays[i][1] + '&' + overlays[i][2] + '&' + overlays[i][3] + '&';
 		}
 		url = overlayURL + url;
 	}
 	
 	var mapType = PolyTile.map.getCurrentMapType();
 	var left    =  5 + Math.floor(width/2) - 128;
 	var top     =  5 + Math.floor(height/2);
 	
 	var content = "<div style='position:relative; width:100%'><div style='position:relative; width:100%; height:54px' id='topBanner'><img src='" + PolyTile.corporateLogo + "' width='242' height='54' style='position:absolute;'/><img src='" + PolyTile.siteLogo + "' width='242' height='54' style='position:absolute; right:0px'/></div>" +
 	"<div style='position:relative; margin-top:5px; border: solid black 1px; width: " + width + "px; height: " + height + "px;'>" +
 	  "<img onload='openPrintDialog()' src='http://www.polytile.com/print.php?" + url + "&s=doctorconnect'></img>" +
 	  "<div id='busy' style='position:absolute; top:" + top + "px; left:" + left + "px; z-index:1'>" + 
 	      "<img style='margin-left:112px' src='" + PolyTile.busySymbol + "'><br>" + 
 	      "<span class='wait'>Please wait for the map to be generated</span>" +
 	  "</div>" +
 	"</div>" +
 	(infoHTML != null ? infoHTML : "") + 
 	"</div>";
 		
 	var printwindow = window.open('','Console',
 			  'width=' + (width+100) + ',height=' + (height+100)
 			  +',menubar=1'
 			  +',toolbar=1'
 			  +',status=1'
 			  +',scrollbars=1'
 			  +',resizable=1');
 	 	
 	var style = '<style type="text/css">'
 	  +'.iw {'
 	  +'font-size:   10pt;'
 	  +'font-family: sans-serif;'
 	  +'}'
 	  +'.iwbs {'
 	  +'font-size:   9pt;'
 	  +'font-family: sans-serif;' 
 	  +'font-weight: bold;'
 	  +'}'
 	  +'.iwb {'
 	  +'font-size:   10pt;'
 	  +'font-family: sans-serif;' 
 	  +'font-weight: bold;'
 	 +'}'
 	  +'.wait {' 
 	  +'font-size:   10pt;' 
 	  +'font-family: sans-serif;' 
 	  +'font-weight: bold;'
 	  +'}'	
 	  +'.copyrightlabel {' 
 	  +'font-size:   8pt;' 
 	  +'font-family: sans-serif;' 
 	  +'}'	
 	  +'</style>';
 	
 	var script = '<script type="text/javascript">'
 	  +'function openPrintDialog() {'
 	  +'  var busy = document.getElementById("busy");'
 	  +'  busy.innerHTML = "";'
 	  +'  self.print();'
 	  +'}'
 	  +'</script>'
 	
     printwindow.document.writeln(
 			  '<html><head><title>' + (PolyTile.activeLayer == null ? "" : PolyTile.activeLayer.name) + '</title>' + style + script + '</head>'
 			   +'<body style="margin:5px" bgcolor=white onLoad="self.focus()">'
 			   +content
 			   +'</body></html>'
 			 );
    printwindow.document.close();
 }
 
/*
 * Class: JSN
 * Author: Stratosphere HPC
 *
 * uses JSONP to make a request from a REST API.
 *
 * Constructor Parameters: 
 * url               end point of the REST service
 * responseHandler   callback function for JSON response
 */
 
 function JSN(url, responseHandler) {
   this.url = url;
   
   // allocate unique identifier for this call
   this.allocateId();
   
   // build a script node
   this.script = document.createElement('script');
   this.script.setAttribute('src', this.buildURL());
   this.script.setAttribute('type', 'text/javascript');
   
   // define the callback function
   var node = this.script;
   
   window[this.id] = function(obj) {
     // process the results
     responseHandler(obj);
     
     // remove the script from the html document <head>
	 var head = document.getElementsByTagName('head');
	 head[0].removeChild(node);
   }
   
   // insert script into <head> of html document
   var head = document.getElementsByTagName('head'); 
   head[0].appendChild(this.script);
  }
 
 // allocate a unique identifier to this request instance
 JSN.prototype.allocateId = function() {
   
   // try for a maximum 100 times to avoid random number clashes
   for (var i=0; i<100; i++) {
     var rand = Math.ceil( Math.random() * 1000000000);
     this.id = 'JSONPRequestCallback' + rand;
     
     // check if function already exists
     if (window[this.id] == null) {
       break;
     }
   }
 } 
 
 // append the callback function to the supplied url
 JSN.prototype.buildURL = function () {
   var urlCallback = this.url + "&callback=" + this.id;
   return urlCallback;
 }
 
 /*---------------------------------------------------------------------------*/
 /* Convert Placemark to a flat address structure */

 function Address(placemark) {
	
 	this.text        = placemark.address;
 	this.accuracy    = null;
 	this.countryName = "";
 	this.countryCode = "";
 	this.state       = "";
 	this.locality    = "";
 	this.street      = "";
 	this.postcode    = "";
 	this.point       = null;
 	this.bounds      = null;
 	
 	if (placemark.Point) {
 		this.point = new GLatLng(placemark.Point.coordinates[1],placemark.Point.coordinates[0]);
 	}
 	
 	if (placemark.ExtendedData) {
 	    this.bounds = 
 		    new GLatLngBounds(
 			    new GLatLng(placemark.ExtendedData.LatLonBox.south, placemark.ExtendedData.LatLonBox.west), 
 				new GLatLng(placemark.ExtendedData.LatLonBox.north, placemark.ExtendedData.LatLonBox.east));
 	}
 	
 	if (placemark.AddressDetails) {
 		this.accuracy = placemark.AddressDetails.Accuracy;
 		
 		if (PolyTile.countryCode == "au") {
 		
	 		if (placemark.AddressDetails.Country) {
	
	 			this.countryName = placemark.AddressDetails.Country.CountryName;
	 			this.countryCode = placemark.AddressDetails.Country.CountryNameCode;
	 			
	 			if (placemark.AddressDetails.Country.PostalCode) {
	 				this.postcode = placemark.AddressDetails.Country.PostalCode.PostalCodeNumber;
	 			}			
	 			
	 			if (placemark.AddressDetails.Country.AdministrativeArea) {
	 				this.state = placemark.AddressDetails.Country.AdministrativeArea.AdministrativeAreaName;
	
	 				if (placemark.AddressDetails.Country.AdministrativeArea.Thoroughfare) {
	 					this.street = placemark.AddressDetails.Country.AdministrativeArea.Thoroughfare.ThoroughfareName;
	 				}
	 				
	 				if (placemark.AddressDetails.Country.AdministrativeArea.PostalCode) {
	 					this.postcode = placemark.AddressDetails.Country.AdministrativeArea.PostalCode.PostalCodeNumber;
	 				}	
	 				
	 				if (placemark.AddressDetails.Country.AdministrativeArea.Locality) {
	 					this.locality = placemark.AddressDetails.Country.AdministrativeArea.Locality.LocalityName;
	 					
	 					if (placemark.AddressDetails.Country.AdministrativeArea.Locality.Thoroughfare) {
	 						this.street = placemark.AddressDetails.Country.AdministrativeArea.Locality.Thoroughfare.ThoroughfareName;
	 					}
	 					
	 					if (placemark.AddressDetails.Country.AdministrativeArea.Locality.PostalCode) {
	 						this.postcode = placemark.AddressDetails.Country.AdministrativeArea.Locality.PostalCode.PostalCodeNumber;
	 					}
	 				}
	 			}
	 		}
 		}
 	}
 	
 	// remove streets that are numbers (road codes)
 	if (this.street.match(/^\w?\d+$/)) {
 		this.street = "";
 	}
 }

 /*---------------------------------------------------------------------------*/
 /* Convert Placemark list into a one-line summary and flat address structure */

 function AddressSummary(placemark)  {
    
	this.result = "";
    this.address = null;
    
    if (placemark.Placemark && PolyTile.countryCode == "au") {
      
       /*var summary = "["; 
       for (var i=0; i<placemark.Placemark.length; i++) {
           if (i > 0) {
             summary += " | ";
           }
           summary += placemark.Placemark[i].address;
       }
       summary += "]";
       alert(summary); */  

       var stateAbbr = [];
       stateAbbr["New South Wales"] = "NSW";
       stateAbbr["Victoria"] = "VIC";
       stateAbbr["Queensland"] = "QLD";
       stateAbbr["South Australia"] = "SA";
       stateAbbr["Western Australia"] = "WA";
       stateAbbr["Tasmania"] = "TAS";
       stateAbbr["Northern Territory"] = "NT";
       stateAbbr["Australian Capital Territory"] = "ACT";

       for (var i=0; i<placemark.Placemark.length; i++) {
         if (placemark.Placemark[i].address.length > 0) {
      	   
      	   this.address = new Address(placemark.Placemark[i]);
      	   	        	   
           this.result = placemark.Placemark[i].address;
           var australia = this.result.indexOf(", Australia");
           if (australia > 0) {
	           this.result = this.result.substring(0,australia);
	           if (this.result.length == 4) {
	             var numeric = true;
	             for (var j=0; j<4; j++) {
	               numeric = numeric && this.result.charAt(j) >= '0' && this.result.charAt(j)  <= '9';
	             }
	             if (numeric) {
	               var originalPostcode = this.result;
	               this.result = 'Postcode ' + this.result;
	               if (i+1 < placemark.Placemark.length) {
	                 var stateName = placemark.Placemark[i+1].address;
	                 var aussie = stateName.indexOf(", Australia");
	                 if (aussie > 0) {
	                   stateName = stateName.substring(0,aussie);
	                 }
	                 var abbr = stateAbbr[stateName];
	                 if (abbr != null && abbr.length > 0) {
	                   this.result += ", " + abbr;
	                 }
	                 //alert("Postcode [" + originalPostcode + "] --> [" + this.result + "]");
	               }
	             }
	           } else if (map.getZoom() < 8) {
	             var comma1 = this.result.indexOf(",");
	             if (comma1 > 0) {
	               if (i+1 < placemark.Placemark.length) {
	                 var suburbName = placemark.Placemark[i+1].address;
	                 var aussie = suburbName.indexOf(", Australia");
	                 if (aussie > 0) {
	                   suburbName = suburbName.substring(0,aussie);
	                 }
	                 //alert("Address [" + this.result + "] --> [" + suburbName + "]");
	                 this.result = suburbName;
	               }
	             }
	           }
	
	           var comma = this.result.indexOf(",");
	           // remove roads that are codes
	           if (this.result.match(/^\w?\d+\,\s/) && comma > 0) {
	             var roadRemoved = this.result.substring(comma+2);
	             //alert("Road [" + this.result + "] --> [" + roadRemoved + "]");
	             this.result = roadRemoved;
	           }
           }
           break;
         }
       }
    } 	
}

 /*---------------------------------------------------------------------------*/
function LegendControl() {
}

LegendControl.prototype = new GControl();

LegendControl.prototype.update = function() {
	var html = PolyTile.activeLayer == null ? '' : '<img src="' + PolyTile.lotusNotes + 'AttachmentsByTitle/' + PolyTile.activeLayer.id + '-Legend.gif/$FILE/' + PolyTile.activeLayer.id + '-Legend.gif"/>';
	this.container.innerHTML = html;
}

LegendControl.prototype.initialize = function(map) {
   this.container = document.createElement("div");

   this.update();

   map.getContainer().appendChild(this.container);
   PolyTile.legendControl = this;
   return this.container;
 }

LegendControl.prototype.getDefaultPosition = function() {
   return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(320, 7));
}
 
function SearchControl() {
}

SearchControl.prototype = new GControl(false, true);

SearchControl.prototype.initialize = function(map) {
   var container = document.createElement("div");
   container.id = "container";
   container.innerHTML = '<div id="map-search">'+
    '<h1>Search the Map</h1>'+
    '<div id="buttonWrapper">'+
      '<h1 id="help"><a href="' + PolyTile.lotusNotes + 'Content/locator/$File/help.pdf">Help</a></h1>'+
      '<h1 id="print"><a href="#" onclick="openPrintWindow()">Print</a></h1>'+
    '</div>'+
    '<p id="instructions">Click on the map for geographic and incentive information, or use the search to find an address.</p><p><strong>For the most accurate result enter your full street address.</strong></p>'+
    '<input id="textInput" type="text" name="Search" size="25"/>'+
    '<input id="searchButton" type="submit" value="Search Map"/>'+
    '<div id="hide">'+
      '<h1>Choose your Layer</h1>'+
        '<fieldset>'+
          '<div><input type="checkbox" id="RACheck" name="layer" value="ra"/><label>RA</label></div>'+
          '<div><input type="checkbox" id="DWSCheck" name="layer" value="dws"/><label>DWS</label></div>'+
          '<div><input type="checkbox" id="MetroCheck" name="layer" value="metro"/><label>Metro </label></div>'+
        '</fieldset>'+
        '<p>Choose between <a href="' + PolyTile.lotusNotes + 'Content/RA-intro" target="_blank">Remoteness Area</a> (RA), <br/><a href="' + PolyTile.lotusNotes + 'Content/dwsFactsheet" target="_blank">Districts of Workforce Shortage</a> (DWS) or <br/><a href="' + PolyTile.outerMetro + '" target="_blank">Metropolitan</a> and <a href="http://www.mbsonline.gov.au/telehealth" target="blank">Telehealth</a> (Metro) maps.</p>'+
    '</div>'+
  '</div>';
 
   map.getContainer().appendChild(container);
   return container;
 }

 SearchControl.prototype.getDefaultPosition = function() {
   return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(0,0));
 }
