// CrunchControl.js
//
// Copyright (c) 2006 Microsoft Corporation.  All rights reserved.
// Authors: John Douceur and Jeremy Elson
// Version: 3.3
// Date: 31 May 2006
//
// Revision History:
//
// 1.0 - 2006.05.16 (JD & JE): first public release
// 1.1 - 2006.05.18 (JD): support for XML files on remote HTTP servers
// 3.0 - 2006.05.23 (JD): ImportLayersFromAnchorHRef replaces ImportLayersFromCrunchFile
// 3.1 - 2006.05.24 (JD): added ImportLayersFromDivText
// 3.2 - 2006.05.25 (JD): use document.load() for files not accessed via HTTP
// 3.3 - 2006.05.31 (JD): added PopUpAlertsFromDivParagraphs
// 3.3 - 2007       (JH, JE): Added automatic legend support
// 4.0 - 2007.05.23 (JE): modified to support v4 of the VE API
// 5.1 - 2007.06.19 (JH): factored into objects

function _RegisterNamespaces()
{
	for (var i=0;i<arguments.length;i++)
	{
		var astrParts = arguments[i].split(".");
		var root = window;
		for (var j=0; j < astrParts.length; j++)
		{
			if (!root[astrParts[j]]) 
			{
					root[astrParts[j]] = new Object(); 
			}
			root = root[astrParts[j]];
		}
	}
}
_RegisterNamespaces('VE.MapCruncher');

VE.MapCruncher.maxMSXMLVersionToTry = 7;

VE.MapCruncher.CreateActiveXObject = function(objectName)
{
	if (window.ActiveXObject == undefined)
	{
		return null;
	}
	try
	{
		var newObject = new ActiveXObject(objectName);
		return newObject;
	}
	catch (dummy)
	{
		return null;
	}
}


VE.MapCruncher.CreateMSXMLObject = function(firstName, laterName)
{
	var newObject = null;
	for (var version = VE.MapCruncher.maxMSXMLVersionToTry; version > 2; version--)
	{
		newObject = VE.MapCruncher.CreateActiveXObject("MSXML2." + laterName + "." + version + ".0");
		if (newObject != null)
		{
//			alert("created " + laterName + " version " + version);
			return newObject;
		}
	}
	newObject = VE.MapCruncher.CreateActiveXObject("MSXML2." + laterName);
	if (newObject != null)
	{
		return newObject;
	}
	newObject = VE.MapCruncher.CreateActiveXObject("Microsoft." + firstName);
	return newObject;
}

VE.MapCruncher.ExecuteHTTPRequest = function(request, url)
{
	var httpReq = null;
	if (typeof XMLHttpRequest != "undefined")
	{
		httpReq = new XMLHttpRequest();
	}
	if (httpReq == null)
	{
		httpReq = VE.MapCruncher.CreateMSXMLObject("XMLHTTP", "XMLHTTP");
	}
	if (httpReq == null)
	{
		return null;
	}
	httpReq.open(request, url, false);
	httpReq.send(null);
	return httpReq;
}

VE.MapCruncher.CreateXMLDocument = function(xmlText)
{
	var xmlDocument = null;
	if (document.implementation != undefined
		&& document.implementation.createDocument != undefined)
	{
		xmlDocument = document.implementation.createDocument("", "", null);
	}
	if (xmlDocument == null)
	{
		xmlDocument = VE.MapCruncher.CreateMSXMLObject("XMLDOM", "DOMDocument");
	}
	if (xmlDocument == null)
	{
		return null;
	}

	if (xmlText == null)
	{
		return xmlDocument;
	}
	if (typeof xmlDocument.loadXML != "undefined")
	{
		xmlDocument.loadXML(xmlText);
		return xmlDocument;
	}
	if (typeof DOMParser != "undefined")
	{
		var domParser = new DOMParser();
		var stagingDoc = domParser.parseFromString(xmlText, "text/xml");
		for (var ixChild = 0; ixChild < stagingDoc.childNodes.length; ixChild++)
		{
			var subtree = xmlDocument.importNode(stagingDoc.childNodes[ixChild], true);
			xmlDocument.appendChild(subtree);
		}
		return xmlDocument;
	}
	return null;
}


VE.MapCruncher.xmlDOMFromText = function(xmlText)
{
	var xmlDocument = VE.MapCruncher.CreateXMLDocument(xmlText);
	if (xmlDocument == null)
	{
		alert("This application employs XML, which your browser does not support.");
		return null;
	}
	return xmlDocument;
}

VE.MapCruncher.xmlDOMFromURLViaHTTP = function(url)
{
	var request = VE.MapCruncher.ExecuteHTTPRequest("GET", url);
	if (request == null)
	{
		alert("This application requires an XMLHttpRequest object, which your browser does not provide.");
		return null;
	}
	if (request.status != 200)
	{
		alert("File " + url + " could not be retrieved.  Server returned status " + request.status + " (" + request.statusText + ")");
		return null;
	}
	return VE.MapCruncher.xmlDOMFromText(request.responseText);
}

VE.MapCruncher.xmlDOMFromURL = function(url)
{
	if (url.substring(0, 5) == "http:")
	{
		return VE.MapCruncher.xmlDOMFromURLViaHTTP(url);
	}
	else
	{
		var xmlDocument = VE.MapCruncher.CreateXMLDocument();
		xmlDocument.async = false;
		xmlDocument.load(url);
		return xmlDocument;
	}
}

VE.MapCruncher.getFirstSubTag = function(node, subTagName)
{
	var subTagList = node.getElementsByTagName(subTagName);
	if (subTagList == null)
	{
		return null;
	}
	if (subTagList.length < 1)
	{
		return null;
	}
	return subTagList[0];
}

VE.MapCruncher.getSubTagText = function(node, subTagName)
{
	var subTag = VE.MapCruncher.getFirstSubTag(node, subTagName);

	if (subTag == null)
	{
		return null;
	}
	else
	{
		return subTag.text;
	}
}

VE.MapCruncher.PopUpAlertsFromDivParagraphs = function(divId)
{
	var divElement = document.getElementById(divId);
	if (divElement == null)
	{
		alert("Div with id " + divId + " not found in document");
		return null;
	}
	for (var ixChild = 0; ixChild < divElement.childNodes.length; ixChild++)
	{
		var childNode = divElement.childNodes[ixChild];
		if (childNode.nodeType == 1 && childNode.nodeName.toLowerCase() == "p") // paragraph element
		{
			alert(childNode.innerText);
		}
	}
}

VE.MapCruncher.GetMapBounds = function(lmap)
{
	var mtop = 0;
	var mleft = 0;
	var centerPixel = lmap.LatLongToPixel(lmap.GetCenter());

	if (centerPixel == null)
	{
		return null;
	}

	var mbottom = 2*centerPixel.y;
	var mright = 2*centerPixel.x;

	// in 3D mode, this throws exceptions; just return null.
	try {
		var topLeft  = lmap.PixelToLatLong(new VEPixel(mleft, mtop));
		var botRight = lmap.PixelToLatLong(new VEPixel(mright, mbottom));
	}
	catch (dummy)
	{
		return null;
	}

	if (topLeft == null || botRight == null)
	{
		return null;
	}

	return new VELatLongRectangle(topLeft, botRight);
}

//
// Parse the URL and possibly call ActivateAlphaLayer, SetMapStyle
// and SetCenterAndZoom.  The URL should be of the format:
// YourPage.html?lat=12.34&lon=56.789&zoom=12&style=a&alpha=Some%20Layer&alpha=Another%20Layer
//
VE.MapCruncher.BoundsOverlap = function(bb0,bb1)
{
	var latIntersect =
		   (bb1.BottomRightLatLong.Latitude <= bb0.BottomRightLatLong.Latitude && bb0.BottomRightLatLong.Latitude <= bb1.TopLeftLatLong.Latitude)
		|| (bb1.BottomRightLatLong.Latitude <= bb0.TopLeftLatLong.Latitude && bb0.TopLeftLatLong.Latitude <= bb1.TopLeftLatLong.Latitude)
		|| (bb0.BottomRightLatLong.Latitude <= bb1.BottomRightLatLong.Latitude && bb1.BottomRightLatLong.Latitude <= bb0.TopLeftLatLong.Latitude)
		|| (bb0.BottomRightLatLong.Latitude <= bb1.TopLeftLatLong.Latitude && bb1.TopLeftLatLong.Latitude <= bb0.TopLeftLatLong.Latitude);
	var lonIntersect =
		   (bb1.TopLeftLatLong.Longitude <= bb0.TopLeftLatLong.Longitude && bb0.TopLeftLatLong.Longitude <= bb1.BottomRightLatLong.Longitude)
		|| (bb1.TopLeftLatLong.Longitude <= bb0.BottomRightLatLong.Longitude && bb0.BottomRightLatLong.Longitude <= bb1.BottomRightLatLong.Longitude)
		|| (bb0.TopLeftLatLong.Longitude <= bb1.TopLeftLatLong.Longitude && bb1.TopLeftLatLong.Longitude <= bb0.BottomRightLatLong.Longitude)
		|| (bb0.TopLeftLatLong.Longitude <= bb1.BottomRightLatLong.Longitude && bb1.BottomRightLatLong.Longitude <= bb0.BottomRightLatLong.Longitude);

	return latIntersect && lonIntersect;
}

/* ////////////////////////////////////////////////////////////////////// */

VE.MapCruncher.CrunchedLayerManager = function(map)
{
	this.map = map;
	this.legendDivs = null;
	this.selectedLegendDivs = null;
	// only one layerList at a time for now
	this.layerList = null;
	this.controlManager = new VE.MapCruncher.ControlManager(this.map);

	this.ImportLayersFromAnchorHRef = function(anchorId, layerNamePrefix)
	{
		this.layerList = new VE.MapCruncher.LayerList(this);
		this.layerList.FromAnchorHRef(anchorId, layerNamePrefix);
		this.CreateLegendDivs();
		this.layerList.StartAutomaticLegends();
	}

	this.CreateLegendDivs = function()
	{
	//	var selectedLegendCloseClosureName =
	//		VE.MapCruncher.Closures.CreateFromMethod(
	//			this.layerList, this.layerList.DisplayLegend, null).fullName;
	//	this.selectedLegendDivs = this.CreateControl(
	//		"selectedLegend", "Current Legend",
	//		new VE.MapCruncher.LayoutSpec("right", 5, "bottom", 40),
	//		true, selectedLegendCloseClosureName, 390, 45, 600);
	//	this.legendDivs = this.CreateControl(
	//		"legend", "Legends",
	//		new VE.MapCruncher.LayoutSpec("right", 5, "bottom", 5),
	//		true, null, 700, 14, 290);
	//	// hide popup panel
	//	this.layerList.DisplayLegend(null);
	}

	this.CreateControl = function(idBase, title, layoutSpec, hasFoldButton, closeClosureName, left, top, width)
	{
		var boxElt = document.createElement("div");
		boxElt.style.background = "white";
		boxElt.style.border = "2px solid #0000c0";
		boxElt.style.position = "absolute";
		boxElt.style.top = top+"px";
		boxElt.style.left = left+"px";
		boxElt.style.width = width+"px";
		boxElt.style.display = "block";
		boxElt.id = "VE_MapCruncher_"+idBase+"_box";

		var protoBodyElt = document.createElement("div");
		protoBodyElt.id = "VE_MapCruncher_"+idBase+"_body";
		boxElt.appendChild(protoBodyElt);
		document.body.appendChild(boxElt);
		this.controlManager.AddControl(
			boxElt.id,
			title,
			layoutSpec,
			hasFoldButton,
			closeClosureName);

		// AddControl copies content from protoBodyElt. Fetch inner div.
		var bodyElt = document.getElementById(protoBodyElt.id);
		var titleElt = document.getElementById("title_"+boxElt.id);

		var cp = new VE.MapCruncher.ControlParts(boxElt, bodyElt, titleElt);
		return cp;
	}

	this.ApplyPermalink = function(form)
	{
		var lat;
		var lon;
		var zoom;

		var argStartIndex = document.URL.indexOf('?');

		if (argStartIndex < 0)
		{
			return false;
		}

		// Clear all checkboxes in the form we were passed
		for (var i = 0; i < form.childNodes.length; i++)
		{
			if (form.childNodes[i].checked)
			{
				form.childNodes[i].checked = false;
			}
		}
		//this.map.ClearAlphaLayers();  // not supported in v5 api, apparently. TODO should we be doing something else here?

		// Parse the argument string
		var argString = unescape(document.URL.substring(argStartIndex+1));
		var argArray = argString.split("&");

		for (var i = 0; i < argArray.length; i++)
		{
			var arg = argArray[i];
			var attr;
			var value;

			if (arg.indexOf("=") >= 0)
			{
				var attrValueArray = arg.split("=");
				attr = attrValueArray[0];
				value = attrValueArray[1];
			}
			else
			{
				attr = arg;
				value = null;
			}

			switch (attr)
			{
				case "lat":
					if (value != null)
					{
						lat = parseFloat(value);
					}
					break;

				case "lon":
					if (value != null)
					{
						lon = parseFloat(value);
					}
					break;

				case "zoom":
					if (value != null)
					{
						zoom = parseInt(value);
					}
					break;

				case "style":
					if (value != null)
					{
						this.map.SetMapStyle(value);
					}
					break;

				case "alpha":
					if (value != null)
					{
						this.layerList.find(value).Activate(this.map);
						var checkBoxElement = document.getElementById("checkbox:" + value);

						if (checkBoxElement != null)
						{
							checkBoxElement.checked = true;
						}
					}
					break;
			}
		}

		if (lat != null && lon != null && zoom != null)
		{
			this.map.SetCenterAndZoom(new VELatLong(lat, lon), zoom);
			return true;
		}
		return false;
	};

	return this;
}

VE.MapCruncher.ControlParts = function(box, body, title)
{
	this.box = box;
	this.body = body;
	this.title = title;
	return this;
}

VE.MapCruncher.LayerList = function(crunchedLayerManager)
{
	this.crunchedLayerManager = crunchedLayerManager;
	this.list = new Array();
	this.legendMap = new Array();	// quick reference by ID
	this.legendList = new Array();	// sorted in xml order
	this.nextID = 0;

    this.FromAnchorHRef = function(anchorId, layerNamePrefix)
    {
	    var anchorElement = document.getElementById(anchorId);
	    if (anchorElement == null)
	    {
		    alert("Anchor with id " + anchorId + " not found in document");
		    return null;
	    }
	    var xmlFileName = anchorElement.href;
	    var dom = VE.MapCruncher.xmlDOMFromURL(xmlFileName);
	    var tileRoot = "";

	    // Get the URL prefix where this XML file is located.  The tiles are
	    // rooted in the same place, unless the layer specifies a FilePath.
	    if (xmlFileName.lastIndexOf('/') > 0)
	    {
		    tileRoot = xmlFileName.substring(0, xmlFileName.lastIndexOf('/') + 1);
	    }

	    this.FromDom(dom, tileRoot, layerNamePrefix);
    }

	this.FromDom = function(dom, tileRoot, layerNamePrefix)
	{
		this.tileRoot = tileRoot;

		var layerNodes = dom.getElementsByTagName("Layer");
		
		for (var ixLayer = 0; ixLayer < layerNodes.length; ixLayer++)
		{
			var layerNode = layerNodes[ixLayer];
			try
			{
				var layer = new VE.MapCruncher.Layer();
				layer.FromDom(this, layerNode, layerNamePrefix);
				this.add(layer);
			}
			catch (e)
			{
				alert("exception "+e);
				throw(e);	// TODO
			}
		}
	}

	this.NextZIndex = function()
	{
		if (this.list.length==0)
		{
			// TODO ugly hardcoded constant, because we want each new
			// layer to be *above* the layer before.
			// (Can zIndices be negative?)
			// TODO jonh sets this to less than 60, which is the v5
            // of polygons in v5 (via Derrick Quan).
            return 59;
		}
		else
		{
			return this.list[this.list.length-1].zIndex - 1;
		}
	}

	this._AllocateID = function()
	{
		var id = this.nextID;
		this.nextID += 1;
		return id;
	}

	// Internal callback used during construction.
	this._AddLegend = function(legend)
	{
		legend.ID = this._AllocateID();
		this.legendMap[legend.ID] = legend;
		this.legendList.push(legend);
	}

	this.add = function(crunchedLayer)
	{
		this.list.push(crunchedLayer);
	}

	this.find = function(layerReferenceName)
	{
		for (var ixLayer = 0; ixLayer < this.list.length; ixLayer++)
		{
			if (this.list[ixLayer].layerReferenceName == layerReferenceName)
			{
				return this.list[ixLayer];
			}
		}
		alert("No layer "+layerReferenceName);
		throw "No layer "+layerReferenceName;
	}

	this.FindLegend = function(legendID)
	{
		return this.legendMap[legendID];
	}

	// *
	// The code below supports automatic legends.
	// *

	this.currentLegendID = null;		// legend currently being displayed.

	this.StartAutomaticLegends = function()
	{
		if (this.legendMap.length == 0)
		{
			return;
		}

		var closure = VE.MapCruncher.Closures
			.CreateFromMethod(this, this.UpdateLegendList);
		this.crunchedLayerManager.map.AttachEvent("onendpan", closure);
		this.crunchedLayerManager.map.AttachEvent("onendzoom", closure);
	}

	this.HideCurrentLegend = function()
	{
		var selectedLegendDivs = this.crunchedLayerManager.selectedLegendDivs;
		selectedLegendDivs.box.style.visibility = "hidden";
		selectedLegendDivs.body.innerHTML = "";

		this.crunchedLayerManager.controlManager.Fold(
			selectedLegendDivs.box.id, false);
		this.crunchedLayerManager.controlManager.Fold(
			this.crunchedLayerManager.legendDivs.box.id, true);
		return;
	}

	this.DisplayLegend = function(legendID)
	{
		// toggle behavior
		if (this.currentLegendID == legendID)
		{
			legendID = null;
		}
		var selectedLegendDivs = this.crunchedLayerManager.selectedLegendDivs;

		this.currentLegendID = legendID;

		if (this.currentLegendID == null)
		{
			this.HideCurrentLegend();
			return;
		}

		var legend = this.FindLegend(this.currentLegendID);

		if (legend == null)
		{
			this.HideCurrentLegend();
			return;
		}

		selectedLegendDivs.box.style.visibility = "visible";

		selectedLegendDivs.title.innerHTML = legend.DisplayName;

		// TODO scale popup max size to window size.
		var popupSize = new Object();
		popupSize.Width = 600;
		popupSize.Height = 400;
		if (legend.Width!=null)
		{
			popupSize.Width = Math.min(legend.Width+100, popupSize.Width);
			popupSize.Height = Math.min(legend.Height, popupSize.Height);
		}

		selectedLegendDivs.body.style.width = popupSize.Width;
		selectedLegendDivs.body.style.height =
			popupSize.Height +
			selectedLegendDivs.body.offsetLeft; // actually want border width.  close enough.

		if (legend.PopupType=="html")
		{
			selectedLegendDivs.body.innerHTML =
				"<p><iframe width=\""+popupSize.Width+"\" height=\""+popupSize.Height+"\" "
				+" src=\""+legend.URL
				+"\"><a href=\""
				+legend.URL
				+"\">(alt) Click here to get to legend</a></iframe>\n";
		}
		else
		{
			selectedLegendDivs.body.innerHTML =
				"<a href=\"" + legend.URL +"\">"
				+"<img width=\""+legend.Width+"px\""
				+" height=\""+legend.Height+"px\""
				+" src=\""+legend.URL
				+"\">"
				+"</a>\n";
		}

	//	this.crunchedLayerManager.controlManager.Fold(
	//		selectedLegendDivs.box.id, true);
	//	this.crunchedLayerManager.controlManager.Fold(
	//		this.crunchedLayerManager.legendDivs.box.id, false);
	}

	this.UpdateLegendList = function(e)
	{
	//	var legendDiv = this.crunchedLayerManager.legendDivs.body;
	//	var legendBoxDiv = this.crunchedLayerManager.legendDivs.box;
	//	if (legendDiv==null || legendBoxDiv==null)
	//	{
	//		return;
	//	}
		var legendsFound = false;
	//	legendDiv.innerHTML = "";

		//var mapBounds = legendMapControl.GetMapView();
		var mapBounds = VE.MapCruncher.GetMapBounds(this.crunchedLayerManager.map);

		for (ixLegend = 0; mapBounds != null && ixLegend < this.legendList.length; ixLegend++)
		{
		//	var legend = this.legendList[ixLegend];

		//	if (!VE.MapCruncher.BoundsOverlap(legend.Bounds, mapBounds))
		//	{	
		//		continue;
		//	}

			legendsFound = false;

	//		var closureName = VE.MapCruncher.Closures.CreateFromMethod(this, this.DisplayLegend, legend.ID).fullName;
	//		legendDiv.innerHTML += '<a href=' + "'" + 'javascript:'
	//			+ closureName
	//			+ '();' + "'" + '>'
	//			+ legend.DisplayName
	//			+"</a><br>";
		}

		if (legendsFound == true)
		{
		//	legendBoxDiv.style.visibility = "visible";
		}
		else
		{
		//	legendBoxDiv.style.visibility = "hidden";
		}
		//this.crunchedLayerManager.controlManager.UpdateOneControlLayout(
		//	this.crunchedLayerManager.legendDivs.box);
	}
}

VE.MapCruncher.Layer = function()
{
	this.FromDom = function(layerList, layerNode, layerNamePrefix)
	{
		this.active = false;
		this.layerList = layerList;

		// Define the MapStyle that will later be passed to SetMapStyle or
		// ActivateAlphaLayer.
		this.layerDisplayName = layerNode.getAttribute("DisplayName");
		this.layerReferenceName = layerNode.getAttribute("ReferenceName");
		if (layerNamePrefix != null)
		{
			this.layerReferenceName = layerNamePrefix + this.layerReferenceName;
		}

		// Find naming scheme for the tiles
		var namingSchemeNode = VE.MapCruncher.getFirstSubTag(layerNode, "TileNamingScheme");
		var layerNamingScheme = null;
		if (namingSchemeNode != null)
		{
			layerNamingScheme = namingSchemeNode.getAttribute("Type");
		}
		if (layerNamingScheme == null)
		{
			throw("Layer " + layerReferenceName + ": no naming scheme specified");
		}

		// All naming schemes have these attributes.  Some schemes might have additional, special ones.
		var layerFilePath = namingSchemeNode.getAttribute("FilePath");
		if (layerFilePath == null || layerFilePath == "")
		{
			layerFilePath = layerList.tileRoot;
		}
		this.filePath = layerFilePath;

		var layerFilePrefix = namingSchemeNode.getAttribute("FilePrefix");
		var layerFileSuffix = namingSchemeNode.getAttribute("FileSuffix");

		this.layerTilePath = "";
		// Create a GenerateFilename function based on the tile naming scheme.
		// The naming schemes define the relationship between a TileX/TileY/Zoom
		// and a tile URL.
		if (layerNamingScheme == "VE")
		{
			this.layerTilePath = layerFilePath + layerFilePrefix + "/%4" + layerFileSuffix;
			this.layerTilePath = this.layerTilePath.replace(/%20/g, " ");
		}
		else if (layerNamingScheme == "MC1")
		{
			throw("MC1 scheme not implemented yet");
		}
		else if (layerNamingScheme == "CGI")
		{
			throw("CGI scheme not implemented yet");
		}
		else
		{
			throw("XML specifies unknown tile naming scheme " + layerNamingScheme);
		}

		this.ID = layerList._AllocateID();

		this.veTileSourceSpecification =
			new VETileSourceSpecification(this.layerReferenceName, this.layerTilePath);

		this.veTileSourceSpecification.NumServers = 1;
		this.veTileSourceSpecification.ID = this.ID;

		// We set both 'zIndex' and 'ZIndex' since
		// there have been bugs in the MapControl that use one or the other
		// inconsistently.
		this.zIndex = layerList.NextZIndex();
		this.veTileSourceSpecification.zIndex = this.zIndex;
		this.veTileSourceSpecification.ZIndex = this.zIndex;

		// Get the DefaultView: a LatLonZoom that defines a useful view window
		// for this layer
		var defaultViewNode = VE.MapCruncher.getFirstSubTag(layerNode, "DefaultView");
		if (defaultViewNode != null)
		{
			var lat = defaultViewNode.getAttribute("lat");
			var lon = defaultViewNode.getAttribute("lon");
			var zoom = defaultViewNode.getAttribute("zoom");

			if (lat != null && lon != null && zoom != null)
			{
				var defaultView = new Object();

				var Lat = parseFloat(lat);
				var Lon = parseFloat(lon);
				defaultView.Zoom = parseInt(zoom);

				if (!isNaN(Lat) && !isNaN(Lon) && defaultView.Zoom > 0)
				{
					defaultView.veLatLong = new VELatLong(Lat, Lon);
					this.DefaultView = defaultView;
				}
				else
				{
					//alert("Ignoring invalid default view for layer " + layerReferenceName);
					this.DefaultView = null;
				}
			}
		}

		// Get the bounds, legend list, etc.
		this.sourceMapList = new VE.MapCruncher.SourceMapList();
		this.sourceMapList.FromDom(layerList, this, layerNode);
		this.veTileSourceSpecification.MinZoomLevel = 1;
		this.veTileSourceSpecification.MaxZoomLevel = this.sourceMapList.maxZoom;
	}

	this.Activate = function(mapControl, optionalOpacity)
	{
		if (optionalOpacity == null)
		{
			optionalOpacity = 1;
		}

		this.veTileSourceSpecification.Opacity = optionalOpacity;

		if (this.active)
		{
			// TODO not sure if this will actually update the display
			// to reflect an opacity change.
			return;
		}

		mapControl.AddTileLayer(this.veTileSourceSpecification, true);
	}

	this.Deactivate = function(mapControl)
	{
		mapControl.DeleteTileLayer(this.veTileSourceSpecification.ID);
	};

	// Given a name of a layer and a pointer to a MapControl,
	// set the view to be the "best view" for viewing that layer.
	this.SetDefaultView = function(mapControl)
	{
		if (this.DefaultView==null)
		{
			alert("No default view available for this layer.");
			return;
		}
		mapControl.SetCenterAndZoom(
			this.DefaultView.veLatLong, this.DefaultView.Zoom);
	}

}

VE.MapCruncher.SourceMapList = function()
{
	this.FromDom = function(layerList, layer, layerNode)
	{
		this.layer = layer;
		this.Bounds = new Array();

		var sourceMapRecordListNode =
			VE.MapCruncher.getFirstSubTag(layerNode, "SourceMapRecordList");
		var sourceMapNodeList = sourceMapRecordListNode.getElementsByTagName("SourceMapRecord");
		this.maxZoom = 1;

		for (var ixSourceMap = 0; ixSourceMap < sourceMapNodeList.length; ixSourceMap++)
		{
			sourceMapInfo = new VE.MapCruncher.SourceMapInfo();
			sourceMapInfo.FromDom(layerList, this, sourceMapNodeList[ixSourceMap]);
			if (sourceMapInfo.maxZoom > this.maxZoom)
			{
				this.maxZoom = sourceMapInfo.maxZoom;
			}
		}
	}
}

VE.MapCruncher.SourceMapInfo = function()
{
	this.FromDom = function (layerList, sourceMapList, sourceMapNode)
	{
		this.displayName = sourceMapNode.getAttribute("DisplayName");
		this.maxZoom = parseInt(sourceMapNode.getAttribute("MaxZoom"));

		var mapRectNode = VE.MapCruncher.getFirstSubTag(sourceMapNode, "MapRectangle");
		if (mapRectNode != undefined)
		{
			var latLonList = mapRectNode.getElementsByTagName("LatLon");
			var lat0 = latLonList[0].getAttribute("lat");
			var lon0 = latLonList[0].getAttribute("lon");
			var lat1 = latLonList[1].getAttribute("lat");
			var lon1 = latLonList[1].getAttribute("lon");

			// note, we have to reverse the order of the lats -- MapCruncher emits maprectangles
			// as bottom-left, top-right; VELatLongRectangles use Top-Left, Bottom-Right
			var mapRect = new VELatLongRectangle(
				new VELatLong(parseFloat(lat1), parseFloat(lon0)),
				new VELatLong(parseFloat(lat0), parseFloat(lon1))
			);
			sourceMapList.Bounds.push(mapRect);

			// now create a legend record with these bounds
			var legendNode = VE.MapCruncher.getFirstSubTag(sourceMapNode, "SourceMapLegendFrame");
			if (legendNode != null)
			{
				var legend = new VE.MapCruncher.Legend();
				legend.FromDom(legendNode, this, mapRect, sourceMapList.layer.filePath);
				layerList._AddLegend(legend);
			}
		}
	}

	// Todo would be nice to add this function per SourceMap
	// this.SetDefaultView = function(mapControl)
}

VE.MapCruncher.Legend = function()
{
	this.FromDom = function(legendNode, sourceMapInfo, mapRect, filePath)
	{
		this.Bounds = mapRect;
		this.URL = legendNode.getAttribute("URL");
		if (filePath!=null)
		{
			this.URL = filePath + this.URL;
		}
		this.DisplayName = sourceMapInfo.displayName;
		this.PopupType = "html";
		this.Width = parseInt(legendNode.getAttribute("Width"));
		this.Height =parseInt(legendNode.getAttribute("Height"));
	}
}

VE.MapCruncher.ClosuresClass = function()
{
	this.NextClosureId = 0;
	this.CreateFromMethod = function(object, method)
	{
		this.NextClosureId += 1;
		var localName = "Closure"+VE.MapCruncher.NextClosureId;
		var staticArgs = new Array();
		for (var i=2; i<arguments.length; i++)
		{
			staticArgs.push(arguments[i]);
		}
		var closureFunc = function() {
			var finalArgs = staticArgs.concat(arguments);
			return method.apply(object, finalArgs);
		}
		VE.MapCruncher[localName] = closureFunc;
		closureFunc.fullName = "VE.MapCruncher."+localName;
		return closureFunc;
	}
	return this;
}
VE.MapCruncher.Closures = VE.MapCruncher.ClosuresClass();

VE.MapCruncher.ControlManager = function(map)
{
	this.AddShim = function(el,sid)  //add iframe shim
	{
		if (map.GetMapMode() == VEMapMode.Mode3D)
		{
			var s = document.createElement("iframe");
			s.id = sid;
			s.frameBorder = "0";
			s.style.position = "absolute";
			s.style.zIndex = "1";
			s.style.top  = el.offsetTop;
			s.style.left = el.offsetLeft;
			s.width  = el.offsetWidth;
			s.height = el.offsetHeight;
			s.allowtransparency = "true";
			s.scrolling="no";
			s.className="Shim";
			el.shimElement = s;
			el.parentNode.insertBefore(s, el);
		}
	}

	this.Decorate = function(el, title, hasFoldButton, closeClosureName)
	{
		var qElId = "'"+el.id+"'";
		var foldId = "fold_"+el.id;
		var qFoldId = "'"+foldId+"'";
		var titleId = "title_"+el.id;
		var qTitleId = "'"+titleId+"'";

		var foldHTML = '';
		if (hasFoldButton)
		{
			var foldClosureName = VE.MapCruncher.Closures.CreateFromMethod(this, this.Fold, el.id, "toggle").fullName;
			var foldLinkText = "javascript:"+foldClosureName+"()";
			var foldHTML = '<a href="'+foldLinkText+'">'
				+'<font color="white"><b>'
				+'v'
				+'</b></font>'
				+'</a>';
		}

		var closeHTML = '';
		if (closeClosureName)
		{
			var closeLinkText = "javascript:"+closeClosureName+"()";
			closeHTML =
				'<a href="'+closeLinkText+'">'
				+'<font color="white"><b>'
				+'x'
				+'</b></font>'
				+'</a>';
		}

		el.innerHTML = ''
			+'<div style="background:blue">'
			+'<table width="100%">'
			//+'<table width="'+el.width+'px" border=1>'
			+'<tr><td>'
			+'<font color="white"><b><div id='+qTitleId+'>'
			+title
			+'</div></b></font>'
			+'</td>'
			+'<td width="10" align="right">'
			+foldHTML
			+'&nbsp;'
			+closeHTML
			+'</td>'
			+'</tr></table>'
			+'</div>'
			+'<div id='+qFoldId+'>'+el.innerHTML+'</div>';
		var foldDiv = document.getElementById(foldId);
		foldDiv.style.display = "block";
	}

	this.Fold = function(elId, newState)
	{
		var foldId = "fold_"+elId;
		var foldDiv = document.getElementById(foldId);
		if (newState=="toggle")
		{
			if (foldDiv.style.display!="block")
			{
				newState = true;
			}
			else
			{
				newState = false;
			}
		}
		if (newState)
		{
			foldDiv.style.display = "block";
		}
		else
		{
			foldDiv.style.display = "none";
		}

		this.UpdateOneControlLayout(document.getElementById(elId));
	}

	this.Reshim = function(elId)
	{
		if (this.map.GetMapMode() == VEMapMode.Mode3D)
		{
			this.RemoveShim("shim_"+elId);
			el = document.getElementById(elId);
			this.AddShim(el, "shim_"+elId);
		}
	}

	this.Close = function(elId)
	{
		var divElt = document.getElementById(elId);
		divElt.style.visibility = "hidden";
	}

	this.RemoveShim = function(sid) //remove iframe shim
	{
		var msh = document.getElementById(sid);
		if (msh!=null) { msh.parentNode.removeChild(msh); }
		msh = null;
	}

	this.ChangeModeHandler = function() //Change from 2d to 3d etc
	{
		for (var controlIdIdx in this.controlList)
		{
			if (this.map.GetMapMode() == VEMapMode.Mode3D)
			{
				var controlId = this.controlList[controlIdIdx];
				control = document.getElementById(controlId);
				this.AddShim(control, "shim_"+controlId);
			}else{
				this.RemoveShim("shim_"+controlId);
			}
		}
	}

	this.AddControl = function(controlId, title, layoutSpec, hasFoldButton, closeClosureName)
	{
		control = document.getElementById(controlId);
		if (layoutSpec==null)
		{
			layoutSpec = new VE.MapCruncher.LayoutSpec(
				"left", control.style.left, "top", control.style.top);
		}
		control.layoutSpec = layoutSpec;
		this.Decorate(control, title, hasFoldButton, closeClosureName);
		this.controlList.push(control.id);
		this.map.AddControl(control);
		this.UpdateOneControlLayout(control);
	}

	this.UpdateControlLayout = function()
	{
		for (var cIndex in this.controlList)
		{
			var controlId = this.controlList[cIndex];
			var control = document.getElementById(controlId);
			this.UpdateOneControlLayout(control);
		}
	}
	this.UpdateOneControlLayout = function(control)
	{
		var mapDiv = document.getElementById(this.map.ID);
		var layoutSpec = control.layoutSpec;
		if (layoutSpec.horizontalLayout == "right")
		{
			control.style.left = (parseInt(mapDiv.style.width) - parseInt(control.offsetWidth) - parseInt(layoutSpec.hOffset))+"px";
		}
		else if (layoutSpec.horizontalLayout == "left")
		{
			control.style.left = (parseInt(layoutSpec.hOffset))+"px";
		}
		if (layoutSpec.verticalLayout == "bottom")
		{
			control.style.top = (parseInt(mapDiv.style.height) - parseInt(control.offsetHeight) - parseInt(layoutSpec.vOffset))+"px";
		}
		else if (layoutSpec.verticalLayout == "top")
		{
			control.style.top = (parseInt(layoutSpec.vOffset))+"px";
		}
		this.Reshim(control.id);
	}

	// Currently, this class can manage just one VEMap at a time.
	this.Init = function(map)
	{
		this.map = map;
		this.controlList = new Array();
		var closure = VE.MapCruncher.Closures.CreateFromMethod(this, this.ChangeModeHandler);
		this.map.AttachEvent('oninitmode', closure);
	}
	this.Init(map);
	return this;
}

VE.MapCruncher.LayoutSpec = function(
	horizontalLayout,
	hOffset,
	verticalLayout,
	vOffset)
{
	this.horizontalLayout = horizontalLayout;
	this.hOffset = hOffset;
	this.verticalLayout = verticalLayout;
	this.vOffset = vOffset;
	return this;
}
