// 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
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('MSR.CVE');

function CreateActiveXObject(objectName)
{
	if (window.ActiveXObject == undefined)
	{
		return null;
	}
	try
	{
		var newObject = new ActiveXObject(objectName);
		return newObject;
	}
	catch (dummy)
	{
		return null;
	}
}

var maxMSXMLVersionToTry = 7;

function CreateMSXMLObject(firstName, laterName)
{
	var newObject = null;
	for (var version = maxMSXMLVersionToTry; version > 2; version--)
	{
		newObject = CreateActiveXObject("MSXML2." + laterName + "." + version + ".0");
		if (newObject != null)
		{
//			alert("created " + laterName + " version " + version);
			return newObject;
		}
	}
	newObject = CreateActiveXObject("MSXML2." + laterName);
	if (newObject != null)
	{
		return newObject;
	}
	newObject = CreateActiveXObject("Microsoft." + firstName);
	return newObject;
}

function ExecuteHTTPRequest(request, url)
{
	var httpReq = null;
	if (typeof XMLHttpRequest != "undefined")
	{
		httpReq = new XMLHttpRequest();
	}
	if (httpReq == null)
	{
		httpReq = CreateMSXMLObject("XMLHTTP", "XMLHTTP");
	}
	if (httpReq == null)
	{
		return null;
	}
	httpReq.open(request, url, false);
	httpReq.send(null);
	return httpReq;
}

function CreateXMLDocument(xmlText)
{
	var xmlDocument = null;
	if (document.implementation != undefined
		&& document.implementation.createDocument != undefined)
	{
		xmlDocument = document.implementation.createDocument("", "", null);
	}
	if (xmlDocument == null)
	{
		xmlDocument = 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;
}


function xmlDOMFromText(xmlText)
{
	var xmlDocument = CreateXMLDocument(xmlText);
	if (xmlDocument == null)
	{
		alert("This application employs XML, which your browser does not support.");
		return null;
	}
	return xmlDocument;
}

function xmlDOMFromURLViaHTTP(url)
{
	var request = 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 xmlDOMFromText(request.responseText);
}

function xmlDOMFromURL(url)
{
	if (url.substring(0, 5) == "http:")
	{
		return xmlDOMFromURLViaHTTP(url);
	}
	else
	{
		var xmlDocument = CreateXMLDocument();
		xmlDocument.async = false;
		xmlDocument.load(url);
		return xmlDocument;
	}
}

function getFirstSubTag(node, subTagName)
{
	var subTagList = node.getElementsByTagName(subTagName);
	if (subTagList == null)
	{
		return null;
	}
	if (subTagList.length < 1)
	{
		return null;
	}
	return subTagList[0];
}

function getSubTagText(node, subTagName)
{
	var subTag = getFirstSubTag(node, subTagName);

	if (subTag == null)
	{
		return null;
	}
	else
	{
		return subTag.text;
	}
}

MSR.CVE.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);
		}
	}
}


// The ImportLayersFromAnchorHRef and ImportLayersFromDivText functions
// each produce a list of TileConfig objects from an XML description
// of MapCruncher output layers.  The returned list should be passed to
// MSR.CVE.AddLayersToMap, which in turn passes each TileConfig object to the
// MapControl's AddOrthoTileSource() function, thereby defining new tile layers.
//
// The ImportLayersFromAnchorHRef function allows the caller to specify
// the name of an XML file via the href attribute of an anchor element.
// This indirection ensures that the HTML document includes an explicit
// reference to the XML file, to preserve linking information.
//
// The ImportLayersFromDivText function allows the caller to specify
// XML text via the text content of a div element.  This is useful for
// compositors that programmatically assemble div text via server-side
// code, such as JScript or VBScript.

MSR.CVE.ImportLayersFromAnchorHRef = 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 = 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);
	}
	return MSR.CVE.ImportLayersFromDOM(dom, tileRoot, layerNamePrefix);
}

MSR.CVE.ImportLayersFromDivText = function(divId, layerNamePrefix)
{
	var divElement = document.getElementById(divId);
	if (divElement == null)
	{
		alert("Div with id " + divId + " not found in document");
		return null;
	}
	var dom = xmlDOMFromText(divElement.innerText);
	var tileRoot = "";
	return MSR.CVE.ImportLayersFromDOM(dom, tileRoot, layerNamePrefix);
}

function GetSourceMapInfo(crunchLayer, layerNode)
{
	crunchLayer.LegendList = new Array();
	crunchLayer.Bounds = new Array();
	var sourceMapRecordListNode = getFirstSubTag(layerNode, "SourceMapRecordList");
	var sourceMapNodeList = sourceMapRecordListNode.getElementsByTagName("SourceMapRecord");
	var maxZoom = 1;

	for (var ixSourceMap = 0; ixSourceMap < sourceMapNodeList.length; ixSourceMap++)
	{
		var thisMapZoom = parseInt(sourceMapNodeList[ixSourceMap].getAttribute("MaxZoom"));
		if (thisMapZoom > maxZoom)
		{
			maxZoom = thisMapZoom;
		}

		var mapRectNode = getFirstSubTag(sourceMapNodeList[ixSourceMap], "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))
			);
			crunchLayer.Bounds.push(mapRect);

			// now create a legend record with these bounds
			var legendNode = getFirstSubTag(sourceMapNodeList[ixSourceMap], "Legend");
			if (legendNode != null)
			{
				var legend = new Object();
				legend.Bounds = mapRect;
				legend.URL = legendNode.getAttribute("URL");
				legend.DisplayName = sourceMapNodeList[ixSourceMap].getAttribute("DisplayName")
							+ " "
							+ legendNode.getAttribute("DisplayName");
				legend.PopupType = legendNode.getAttribute("PopupType");

				var legendSizeNode = getFirstSubTag(legendNode, "Size");
				if (legendSizeNode != null)
				{
					legend.Width  = parseInt(legendSizeNode.getAttribute("Width"));
					legend.Height = parseInt(legendSizeNode.getAttribute("Height"));
					crunchLayer.LegendList.push(legend);
				}
			}
		}
	}
	crunchLayer.MinZoomLevel = 1;
	crunchLayer.MaxZoomLevel = maxZoom;
}


MSR.CVE.ImportLayersFromDOM = function(dom, tileRoot, layerNamePrefix)
{
	var crunchLayers = new Array();

	var layerNodes = dom.getElementsByTagName("Layer");
	
	for (var ixLayer = 0; ixLayer < layerNodes.length; ixLayer++)
	{
		var layerNode = layerNodes[ixLayer];

		// Define the MapStyle that will later be passed to SetMapStyle or
		// ActivateAlphaLayer.
		var layerName = layerNode.getAttribute("DisplayName");
		if (layerNamePrefix != null)
		{
			layerName = layerNamePrefix + layerName;
		}

		// Find naming scheme for the tiles
		var namingSchemeNode = getFirstSubTag(layerNode, "TileNamingScheme");
		var layerNamingScheme;
		if (namingSchemeNode == null || (layerNamingScheme = namingSchemeNode.getAttribute("Type")) == null)
		{
			alert("Layer " + layerName + ": no naming scheme specified");
			continue;
		}

		// All naming schemes have these attributes.  Some schemes might have additional, special ones.
		var layerFilePath = namingSchemeNode.getAttribute("FilePath");
		if (layerFilePath == null || layerFilePath == "")
		{
			layerFilePath = tileRoot;
		}
		var layerFilePrefix = namingSchemeNode.getAttribute("FilePrefix");
		var layerFileSuffix = namingSchemeNode.getAttribute("FileSuffix");
		var 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")
		{
			layerTilePath = layerFilePath + layerFilePrefix + "/%4" + layerFileSuffix;
			layerTilePath = layerTilePath.replace(/%20/g, " ");
		}
		else if (layerNamingScheme == "MC1")
		{
			alert("MC1 scheme not implemented yet");
			continue;
		}
		else if (layerNamingScheme == "CGI")
		{
			alert("CGI scheme not implemented yet");
			continue;
		}
		else
		{
			alert("XML specifies unknown tile naming scheme " + layerNamingScheme);
			continue;
		}

		var crunchLayer = new VETileSourceSpecification(layerName, layerTilePath);
		crunchLayer.NumServers = 1;
		crunchLayer.ID = layerName;

		// Compute this layer's zIndex.  We set both 'zIndex' and 'ZIndex' since
		// there have been bugs in the MapControl that use one or the other
		// inconsistently.
		crunchLayer.zIndex = layerNodes.length + 1 - ixLayer;
		crunchLayer.ZIndex = crunchLayer.zIndex;

		// Get the DefaultView: a LatLonZoom that defines a useful view window
		// for this layer
		var defaultViewNode = 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();

				defaultView.Lat = parseFloat(lat);
				defaultView.Lon = parseFloat(lon);
				defaultView.Zoom = parseInt(zoom);

				if (!isNaN(defaultView.Lat) && !isNaN(defaultView.Lon) && defaultView.Zoom > 0)
				{
					crunchLayer.DefaultView = defaultView;
				}
				else
				{
					//alert("Ignoring invalid default view for layer " + layerName);
				}
			}
		}

		// Get the bounds, legend list, etc.
		GetSourceMapInfo(crunchLayer, layerNode);

		//crunchLayer.TileSource = "3DManifest.xml";

		// Now add this layer to the layer list
		crunchLayers.push(crunchLayer);
	}

	return crunchLayers;
};


// This function is no longer used in v3.1
MSR.CVE.AddLayersToMap = function(mapControl, crunchLayers)
{
};


function FindLayer(layerList, layerName)
{
	var crunchLayer = null;

	for (var ixLayer = 0; ixLayer < layerList.length; ixLayer++)
	{
		if (layerList[ixLayer].ID == layerName)
		{
			return layerList[ixLayer];
		}
	}

	return null;
}

MSR.CVE.ActivateAlphaLayer = function(mapControl, layerList, layerName, optionalOpacity)
{
	if (mapControl == null || layerList == null || layerName == null)
	{
		return;
	}

	var crunchLayer = FindLayer(layerList, layerName);

	if (crunchLayer == null)
	{
		alert("Trying to activate unknown layer " + layerName);
		return;
	}

	if (optionalOpacity == null)
	{
		crunchLayer.Opacity = 1;
	}
	else
	{
		crunchLayer.Opacity = optionalOpacity;
	}
	mapControl.AddTileLayer(crunchLayer, true);
};

MSR.CVE.DeactivateAlphaLayer = function(mapControl, layerName)
{
	if (mapControl == null || layerName == null)
	{
		return;
	}

	mapControl.DeleteTileLayer(layerName);
};

// Given a name of a layer and a pointer to a MapControl,
// set the view to be the "best view" for viewing that layer.
MSR.CVE.SetCenterAndZoomForLayer = function(mapControl, layerList, layerName)
{
	var crunchLayer = FindLayer(layerList, layerName);

	if (crunchLayer == null)
	{
		alert("SetCenterAndZoomForLayer: can't find layer " + layerName);
		return;
	}

	var defaultView = crunchLayer.DefaultView;
	mapControl.SetCenterAndZoom(new VELatLong(defaultView.Lat, defaultView.Lon), defaultView.Zoom);
};


//
// 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
//
MSR.CVE.ApplyPermalink = function(map, 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;
		}
    }
	map.ClearAlphaLayers();

	// 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)
				{
					map.SetMapStyle(value);
				}
				break;

			case "alpha":
				if (value != null)
				{
					map.ActivateAlphaLayer(value);
					var checkBoxElement = document.getElementById("checkbox:" + value);

					if (checkBoxElement != null)
					{
						checkBoxElement.checked = true;
					}
				}
				break;
		}
	}

	if (lat != null && lon != null && zoom != null)
	{
		map.SetCenterAndZoom(new VELatLong(lat, lon), zoom);
		return true;
	}
	return false;
};


// The code below supports automatic legends.

var legendList = null;
var legendCallbackActive = false;
var legendMapControl = null;

function FindLegend(displayName)
{
	for (var ixLegend = 0; ixLegend < legendList.length; ixLegend++)
	{
		if (legendList[ixLegend].DisplayName == displayName)
		{
			return legendList[ixLegend];
		}
	}

	return null;
}

function BoundsOverlap(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;
}

function GetMapBounds()
{
	var mtop = 0;
	var mleft = 0;
	var centerPixel = legendMapControl.LatLongToPixel(legendMapControl.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  = legendMapControl.PixelToLatLong(new VEPixel(mleft, mtop));
		var botRight = legendMapControl.PixelToLatLong(new VEPixel(mright, mbottom));
	}
	catch (dummy)
	{
		return null;
	}

	if (topLeft == null || botRight == null)
	{
		return null;
	}

	return new VELatLongRectangle(topLeft, botRight);
}

function MapPanned(e)
{
	UpdateLegendList(e);
}


var currentLegend = null;

function DisplayLegend(legendName)
{
	// toggle behavior
	if (currentLegend == legendName)
	{
		legendName = null;
	}

	var selectedLegendBoxDiv = document.getElementById("selectedLegendBox");
	var selectedLegendDiv = document.getElementById("selectedLegendBody");
	var closeBox = document.getElementById("closeLegendBox");

	currentLegend = legendName;

	if (currentLegend == null)
	{
		//debugMsg("hiding legend");
		selectedLegendBoxDiv.style.visibility = "hidden";
		selectedLegendDiv.innerHTML = "";
		return;
	}

	var legend = FindLegend(currentLegend);

	if (legend == null)
	{
		selectedLegendBoxDiv.style.visibility = "hidden";
		selectedLegendDiv.innerHTML = "";
		return;
	}


	selectedLegendBoxDiv.style.visibility = "visible";

	var legendTitleDiv = document.getElementById("selectedLegendTitle");
	legendTitleDiv.innerHTML = legendName;

	if (legend.Width!=null)
	{
		selectedLegendBoxDiv.style.width = legend.Width;
		selectedLegendBoxDiv.style.height =
			legend.Height +
			closeBox.offsetHeight +
			selectedLegendDiv.offsetLeft; // actually want border width.  close enough.
	} else {
		selectedLegendBoxDiv.style.width = 400;
		selectedLegendBoxDiv.style.height = 400;
	}

	if (legend.PopupType=="html")
	{
		selectedLegendDiv.innerHTML =
			"<p><iframe width=\""
			+legend.Width
			+"\" height=\""
			+legend.Height+"\" "
			+" src=\""+legend.URL
			+"\"><a href=\""
			+legend.URL
			+"\">(alt) Click here to get to legend</a></iframe>\n";
	}
	else
	{
		selectedLegendDiv.innerHTML =
			"<a href=\"" + legend.URL +"\">"
			+"<img width=\""+legend.Width+"px\""
			+" height=\""+legend.Height+"px\""
			+" src=\""+legend.URL
			+"\">"
			+"</a>\n";
	}
}

function UpdateLegendList(e)
{
	var legendDiv = document.getElementById("legendBody");
	var legendsFound = false;
	legendDiv.innerHTML = "<b>Legends:</b>";

	//var mapBounds = legendMapControl.GetMapView();
	var mapBounds = GetMapBounds();

	for (ixLegend = 0; mapBounds != null && ixLegend < legendList.length; ixLegend++)
	{
		var legend = legendList[ixLegend];

		if (!BoundsOverlap(legend.Bounds, mapBounds))
		{	
			continue;
		}

		legendsFound = true;

		legendDiv.innerHTML += '<br><a href=' + "'" + 'javascript:DisplayLegend("'
			+ legend.DisplayName
			+'");' + "'" + '>'
			+ legend.DisplayName
			+"</a>";
	}

	var legendBoxDiv = document.getElementById("legendBox");
	if (legendsFound == true)
	{
		legendBoxDiv.style.visibility = "visible";
	}
	else
	{
		legendBoxDiv.style.visibility = "hidden";
	}
}


MSR.CVE.StartAutomaticLegends = function(mapControl, layerList)
{
	if (mapControl == null)
		return;

	legendMapControl = mapControl;

	if (legendList == null)
	{
		legendList = new Array();
	}
	for (var ixLayer = 0; ixLayer < layerList.length; ixLayer++)
	{
		var layer = layerList[ixLayer];

		for (var ixLegend = 0; ixLegend < layer.LegendList.length; ixLegend++)
		{
			var legend = layer.LegendList[ixLegend];
			legendList.push(legend);
		}
	}

	if (mapControl != null && legendCallbackActive == false && legendList.length > 0)
	{
		legendCallbackActive = true;
		map.AttachEvent("onendpan", MapPanned);
		map.AttachEvent("onendzoom", MapPanned);
	}
};

