/*imap_client.js  v .6.2
*
* Client version of imap library. See imap.js for full documentation.
*
*/


//some globals
var map = null; //map object
var layerList = new Array(); //holds the list of layers that are on the created map
layerOrder = []; //holds a list of the layerIDs in the correct order for the legend
mapOptions = {}; //holds the options for the created map

//Functions used for creatings maps and for the control panel

/** 
*  Gets the map options from the mapList table, stores the options in mapOptions array
*  Calls setupMap once all options are stored
* 
* @param {Int} mapID ID of the map to get options for
*/
function getMapOptions(mapID){
	var xmlHttp = createAjaxObj();

	var url = "/php/getMapOptions.php?mapID=" + mapID + "&rnd=" + Math.random();
			
	xmlHttp.open("GET",url,true);
	
	xmlHttp.onreadystatechange=function(){
		if(xmlHttp.readyState==4){
			xmlOutput=xmlHttp.responseXML;
			
			var mapOptionsXML = xmlOutput.documentElement.getElementsByTagName('options');
			
			for (var i=0; i<mapOptionsXML.length; i++) {	
				 mapOptions['centerx'] = parseFloat(mapOptionsXML[i].getAttribute('centerx'));
				 mapOptions['centery'] = parseFloat(mapOptionsXML[i].getAttribute('centery'));
				 mapOptions['zoom']    = parseInt(mapOptionsXML[i].getAttribute('zoom'));	
				 mapOptions['legend']  = parseInt(mapOptionsXML[i].getAttribute('legend'));	
			}
			setupMap(mapID,mapOptions);
		}
	}	
	xmlHttp.send(null);
}
		
/**
* Initlializes some general markers, sets up and draws map, then calls getLayerList
*
*	@param {String} mapChoice Corresponds to a variable in mapList, specifies which parameters to load the map with.
*   @param {String} mapOptions Array that contains the x,y pair, zoom level and other map settings
*/
function setupMap(mapID,mapOptions){
	
	if (GBrowserIsCompatible()) {
		
		//geocoding object
		var geocoder = new GClientGeocoder();
		
		//restrict searching and panning to this bounding box
		var allowedBounds = new GLatLngBounds(new GLatLng(39.8480851,-75.395736), new GLatLng(40.15211,-74.863586));	
		
		var baseIcon = new GIcon();
		baseIcon.iconSize=new GSize(32,32);
		baseIcon.shadowSize=new GSize(16,16);
		baseIcon.iconAnchor=new GPoint(16,16);
		baseIcon.infoWindowAnchor=new GPoint(16,8);
		
		//icon for search result
		var geocodeIcon = new GIcon();
		geocodeIcon.image = 'http://maps.google.com/mapfiles/kml/pal5/icon14.png';
		geocodeIcon.iconSize=new GSize(32,32);
		geocodeIcon.shadowSize=new GSize(16,16);
		geocodeIcon.iconAnchor=new GPoint(16,16);
		geocodeIcon.infoWindowAnchor=new GPoint(16,8);
		
		map = new GMap2(document.getElementById("map")); 
		map.addControl(new GSmallMapControl()); 
		map.addControl(new GScaleControl()); 
		map.setCenter(new GLatLng(mapOptions.centerx, mapOptions.centery), mapOptions.zoom, G_PHYSICAL_MAP);
		
		G_PHYSICAL_MAP.getMinimumResolution = function () { return 11 }; 
		G_PHYSICAL_MAP.getMaximumResolution = function () { return 15 };
	
		getLayerList(mapID)
	} else {
		alert("Sorry, your browser is not compatible with Google Maps, or you have javascript disabled. Please enable javascript or download a more current browser to see the Interactive Map. Thank you.");
	}
}

/**
* Creates a new mapLayer object, used to manage/track layers on the map and their attributes
* 
*	@constructor
*	@param {Number} ID ID of layer
*	@param {String} type Type of layer (KML,point,polygon,etc)
*	@param {String} URL Location of the layer, if it is a KML
*	@param {String} name Reference name of the layer
*	@param {String} title Name of the of layer, for display on website
*	@param {Number} display 0 for don't display layer, 1 for display layer
*	@param {Number} numRecords Number of records the layer has
*	@param {Array} mArray Array containing all of the layer's markers
*	@param {String} data The layers records in XML format
*	@param {String} legendIcon Image to displayed next to the layer title in the mapSidebar
*/
function mapLayer (ID,type,URL,name,title,description,display,numRecords,mArray,data,legendIcon,geoXml) {
	this.ID = ID;
	this.type = type;
	this.URL = URL;
	this.name = name;
	this.title = title;
	this.description = description;
	this.display = display;
	this.numRecords = numRecords;
	this.mArray = mArray;
	this.data = data;
	this.legendIcon = legendIcon;
	this.geoXml = geoXml;
}

/**
* get an XML list of layers that should be drawn for the current map
* Then create a layer object with the attributes from the retrieved layer. Store
* the layer object in hash so that key:value is layerID:layerObj. Then call
* getLayer() for each point-based layer that is on the map. Once all of those layers 
* have been created, addKmlLayers is called to add the KML layers to the map
*
*	@param {Number} mapID identifier of the current map
*/
function getLayerList(mapID) {
	var xmlHttp = createAjaxObj();

	var url = "/php/getLayerList.php?mapID=" + mapID + "&rnd=" + Math.random();
			
	xmlHttp.open("GET",url,true);
	
	xmlHttp.onreadystatechange=function(){
		if(xmlHttp.readyState==4){
		
		xmlOutput=xmlHttp.responseXML;
		
		var layers = xmlOutput.documentElement.getElementsByTagName('layer');
		
		for (var i=0; i<layers.length; i++) {
			layerID = layers[i].getAttribute('layerID');
			layerName = layers[i].getAttribute('layerName');
			layerTitle = layers[i].getAttribute('layerTitle');
			layerType = layers[i].getAttribute('type');
			layerURL = "http://www.phillywatersheds.org/kml/" + layers[i].getAttribute('URL');
			legendIcon = "http://www.phillywatersheds.org/img/legendIcons/" + layers[i].getAttribute('legendIcon');
			layerDesc = layers[i].getAttribute('description');
			layerDisplay = layers[i].getAttribute('display');
			var mArray = [];
			
			//create new layer object for the current layer
			var layerObj = new mapLayer(layerID,layerType,layerURL,layerName,layerTitle,layerDesc,layerDisplay,0,mArray,'',legendIcon,'');
			
			//store layer object in layerList array
			layerList[i]= layerObj;
			
			if (layerType == 'point') {
			getLayer(layerList[i].name); }
		}
		addKmlLayers();
	}
	}
	xmlHttp.send(null);
}

/**
* Gets layer data from database. This only applies to point/marker based layers and not KMLs
*	@param {String} layerName layer to be retrieved
*/
function getLayer(layer) {
	var xmlHttp = createAjaxObj();
	
	var url = "/php/getLayer.php?layer=" + layer.name + "&rnd=" + Math.random();
	
	xmlHttp.open("GET",url,true);
	
	xmlHttp.onreadystatechange=function(){
		if(xmlHttp.readyState==4){
		xmlOutput=xmlHttp.responseXML;
		
		//store xmlOutput as layerObj.data
		layer.data = xmlOutput;
		//call create layer
		createLayer(layer);
		}	
	}
	xmlHttp.send(null);
}

/**
* Parses the XML file containing the layer data, calls createMarker for each record to create a marker,
* then adds the marker to the map. Also stores the markers into the layer objects marker array
*	@param {String} layerID Layer to be created
*/
function createLayer(layer) {
	layerData = layer.data;
	layerMArray = layer.mArray;
	var record = layerData.documentElement.getElementsByTagName("record");
	
	for (var i = 0; i < record.length; i++) {
			
		var ID = record[i].getAttribute("ID");
		var title = record[i].getAttribute("title");
		var address = record[i].getAttribute("address");
		var description = record[i].getAttribute("description");
		var lat = record[i].getAttribute("lat");
		var lng = record[i].getAttribute("lng");
		var img = record[i].getAttribute("img");
		var icon = record[i].getAttribute("marker");
		var type = record[i].getAttribute("type");

		html = "<b>" + title + "</b><br>" + address + "<br>" + description; 
		
		baseIcon.image = "marker/" + icon;
		
		var point = new GLatLng(parseFloat(lat),parseFloat(lng));
		var marker = createMarker(point,html,baseIcon);
		layerMArray.push(marker);
	}
	if (mapOptions.legend == 1){
 		addLayerCheckbox(layer);
	}
}

/**
* Creates a marker object 
* 	@param {GPoint} Gpoint The lat/lng coordinates for the marker, in the form of a Google Maps point object
*	@param {String} html The marker's infowindow contents
*	@param {GIcon} icon The marker icon to be used  
*/
function createMarker(point,html,icon) {
	var marker = new GMarker(point, icon);
	GEvent.addListener(marker, "click", function() {
	  marker.openInfoWindowHtml(html);
	});
	return marker;
}

/**
* Searches through layerList, looking for KML layers. Once a KML file is found
* addLayerCheckBox is called. We also look for header layers here since
* they need to be added in the correct order.
*/
function addKmlLayers(){
	var x = layerList.length;
		
	for(var i=0; i<x; i++){
		if (layerList[i].type == 'kml' || layerList[i].type == 'header'){
			addLayerCheckbox(layerList[i]);
		}
	}
}

//holds the current layer group ID (the header layerID)
//we use this to assign a group ID to non-header layers so that
//we can show/hide them with toggleLayerGroup
var currentLayerGroup = 0;

/**
* Adds a checkbox to the map mapSidebar to control a layer being drawn/not drawn
*  We also add header layers here too since they have to be
*  added in the appropriate order with the rest of the layers.
*	@param {String} layer Layer check box to be created.
*/
function addLayerCheckbox(layer) {
	var layerTR = document.createElement("tr");
	var inputTD = document.createElement("td");
	
	if(layer.type == 'header') {		
		var nameTD = document.createElement("td");
		nameTD.setAttribute('colspan',2);
		var headSpan = document.createElement("span");
		headSpan.setAttribute('id','layerHeader' + layer.ID);
		var h2 = document.createElement("h2");

		var name = document.createTextNode(layer.title);
		h2.appendChild(name);
		headSpan.appendChild(h2);
		
		var toggleTD = document.createElement("td");
		var toggleImg = document.createElement("img");
		toggleImg.src = "/img/legendIcons/legend_expand.png";
		toggleImg.setAttribute('onclick',"toggleLayerGroup(" + layer.ID + ");");
		toggleImg.onclick = function() { toggleLayerGroup(layer.ID);}; //for ie7
		toggleImg.setAttribute('id','layerHeaderImg' + layer.ID);
		toggleTD.appendChild(toggleImg);
		
		nameTD.appendChild(headSpan);
		layerTR.appendChild(toggleTD);
		layerTR.appendChild(nameTD);
		
		//section for layer group display status
		//1 is default, layers will be hidden
		layerGroupStatus[layer.ID] = 0;
		currentLayerGroup = layer.ID;
		
	} else {	
		var input = document.createElement("input");
		input.type = "checkbox";
		input.id = layer.ID;
		
		if (layer.type == 'point') {
			input.onclick = function () { toggleLayer(layer.ID, this.checked) };
		} else if (layer.type == 'kml') {
			input.onclick = function () { toggleKML (layer, this.checked) };
		}
		
		if (layer.display == 1){ 	
			input.checked = true  
		};
		
		inputTD.appendChild(input);
		
		var nameTD = document.createElement("td");
		var imgTD = document.createElement("td");
		var nameA = document.createElement("a");
		
		var legendIcon = layer.legendIcon;
		var legendIcon_img = document.createElement("img");
		legendIcon_img.setAttribute("src", legendIcon);
		var name = document.createTextNode(layer.title);
		
		nameA.appendChild(name);
		nameTD.appendChild(nameA);
	
		imgTD.appendChild(legendIcon_img);
		
		layerTR.appendChild(inputTD);
		layerTR.appendChild(nameTD);
		layerTR.appendChild(imgTD);
		layerTR.className = 'layerGroup' + currentLayerGroup; //for ie7
		layerTR.setAttribute('class','layerGroup' + currentLayerGroup);
		if (currentLayerGroup != 0) { //if the layer is part of a layer group (by saying its not part of 0, which indicated no group), hide the layer by default
			layerTR.style.display = 'none';
		}
	}
	
	if (mapOptions.legend == 1){
		document.getElementById("mapSidebar").appendChild(layerTR);
		
		//now that checkbox is added, see if its checked, if it is, draw layer
		var layerCheckBox = document.getElementById(layer.ID);
		if (layer.type == 'kml' && layerCheckBox.checked == true) {
			toggleKML(layer, true)
		}
	} else {
		toggleKML(layer, true)
	}
}

//holds the show/hide status of each layer group
var layerGroupStatus = {};

/**
* Toggles whether a layer group under a header (in the map legend) is shown/hidden
*	@param {String} layerID Identifies the layer group to toggle 
*/
function toggleLayerGroup(layerID) {
	
	if (layerGroupStatus[layerID] == 0) {
		layerGroupStatus[layerID] = 1;
		
        layers = getElements('layerGroup' + layerID,"tr");
		var x = layers.length;
		
		for(var i=0; i<x; i++){
			var layer=layers[i];
			layer.style.display = '';
		}
		
		var img = document.getElementById('layerHeaderImg' + layerID);
		img.setAttribute('src','/img/legendIcons/legend_shrink.png');
	} else {
		layerGroupStatus[layerID] = 0;	
		
		layers = getElements('layerGroup' + layerID,"tr");
		var x = layers.length;
		
		for(var i=0; i<x; i++){
			var layer=layers[i];
			layer.style.display = 'none';
		}
		
		var img = document.getElementById('layerHeaderImg' + layerID);
		img.setAttribute('src','/img/legendIcons/legend_expand.png');
	}
}



/**
* Toggles whether a GPoint layer is drawn on the map
*	@param {String} layerID Layer check box to be created
*	@param {Boolean} checked The status of the check box, checked or not checked
*/
function toggleLayer(layer, checked) {
	var markerArray = layer.mArray;
	
	if (checked) {
		for(var i=0; i<markerArray.length; i++){
			//make sure we use the correct marker icon, since every time a marker is drawn, it looks at this value. without this line, 
			//the icon for every layer would be set to the icon of the layer that was created last
			baseIcon.image = layer.legendIcon;
			map.addOverlay(markerArray[i]);
		}
		layerList[layerID].shown = 1;
	}else{
		for(var i=0; i<markerArray.length; i++){
			map.removeOverlay(markerArray[i]);
		}
		layer.shown = 0;
	}
}

/**
* Toggles whether a KML layer is drawn on the map
*	@param {String} layerID Layer check box to be created
*	@param {Boolean} checked The status of the check box, checked or not checked
*/
function toggleKML(layer, checked) {
	if (checked) {		
		var geoXml = new GGeoXml(layer.URL);
		layer.geoXml = geoXml
		map.addOverlay(geoXml);
	} else {
		map.removeOverlay(layer.geoXml);
 	}
}

/**
* Takes an address string and returns the lat/lng coordinates, which are then used to
* create a marker to show the address's geographic location. 
* Written by Google, slightly modified to restrict searches to Philadelphia
*	@param {String} address The address to be geocoded
*/
function AddressSearch(address) {
	geocoder.getLatLng(
		address,
		function(point) {
		if (!point) {
			alert("Address not found. Please check the format of your address.");
		} else {
				var marker = createMarker(point,address,geocodeIcon);
			if(allowedBounds.contains(marker.getPoint())){
				var marker = createMarker(point,address,geocodeIcon);
				map.setCenter(point, 13);
				map.addOverlay(marker);
				GEvent.trigger(marker,"click");
			}else{
				alert("Please restrict your search to the Philadelphia area.");
			}
		}
		}
);
}

/**
* Adds and address search box to the specified HTML div
*	@param {String} div The HTML div to add the search box to
*/
function addSearchBox(div){	
	var content = '<form onsubmit="AddressSearch(this.address.value); return false">' +
		'<input type="text" name="address" size="25" value=""/>' +
		'<input type="submit" value="Search"/>' +
	'</form><br>';
	
	document.getElementById(div).innerHTML = content;
}

/**
* If map is moved outside of a bounding box initialized on the map page, the map is
* moved back within the box defined by allowedBounds
* Thank you Mike Williams
* http://econym.googlepages.com/range.htm
*/
function checkBounds() {
	//Perform the check and return if OK
	if (allowedBounds.contains(map.getCenter())) {
        return;
    }
    // It`s not OK, so find the nearest allowed point and move there
    var C = map.getCenter();
    var X = C.lng();
    var Y = C.lat();

    var AmaxX = allowedBounds.getNorthEast().lng();
    var AmaxY = allowedBounds.getNorthEast().lat();
    var AminX = allowedBounds.getSouthWest().lng();
    var AminY = allowedBounds.getSouthWest().lat();

    if (X < AminX) {X = AminX;}
    if (X > AmaxX) {X = AmaxX;}
    if (Y < AminY) {Y = AminY;}
    if (Y > AmaxY) {Y = AmaxY;}
	
    map.setCenter(new GLatLng(Y,X));
}

/**
* Iterates through an array looking for the given element, if that element is found, returns the index of that element
* 	@param {String/Number} v The element to find in the array
*/
Array.prototype.has=function(v){
	for (i=0;i<this.length;i++){
		if (this[i]==v) return i;
	}
	return false;
}


/**
* Tests if the given element is contained in the array
*	@param {String/Number} element The element to test for in the array
*/
Array.prototype.contains = function (element) {
	for (var i = 0; i < this.length; i++) {
		if (this[i] == element) {
			return true;
		}
	}
	return false;
};	


//From Javascript: The Definitive Guide, http://www.davidflanagan.com/javascript5/display.php?n=15-4&f=15/04.js
/**
 * getElements(classname, tagname, root):
 * Return an array of DOM elements that are members of the specified class,
 * have the specified tagname, and are descendants of the specified root.
 * 
 * If no classname is specified, elements are returned regardless of class.
 * If no tagname is specified, elements are returned regardless of tagname.
 * If no root is specified, the document object is used.  If the specified
 * root is a string, it is an element id, and the root
 * element is looked up using getElementsById()
 */
function getElements(classname, tagname, root) {
    // If no root was specified, use the entire document
    // If a string was specified, look it up
    if (!root) root = document;
    else if (typeof root == "string") root = document.getElementById(root);
    
    // if no tagname was specified, use all tags
    if (!tagname) tagname = "*";

    // Find all descendants of the specified root with the specified tagname
    var all = root.getElementsByTagName(tagname);

    // If no classname was specified, we return all tags
    if (!classname) return all;

    // Otherwise, we filter the element by classname
    var elements = [];  // Start with an emtpy array
    for(var i = 0; i < all.length; i++) {
        var element = all[i];
        if (isMember(element, classname)) // isMember() is defined below
            elements.push(element);       // Add class members to our array
    }

    // Note that we always return an array, even if it is empty
    return elements;

    // Determine whether the specified element is a member of the specified
    // class.  This function is optimized for the common case in which the 
    // className property contains only a single classname.  But it also 
    // handles the case in which it is a list of whitespace-separated classes.
    function isMember(element, classname) {
        var classes = element.className;  // Get the list of classes
        if (!classes) return false;             // No classes defined
        if (classes == classname) return true;  // Exact match

        // We didn't match exactly, so if there is no whitespace, then 
        // this element is not a member of the class
        var whitespace = /\s+/;
        if (!whitespace.test(classes)) return false;

        // If we get here, the element is a member of more than one class and
        // we've got to check them individually.
        var c = classes.split(whitespace);  // Split with whitespace delimiter
        for(var i = 0; i < c.length; i++) { // Loop through classes
            if (c[i] == classname) return true;  // and check for matches
        }

        return false;  // None of the classes matched
    }
}
