/*
* Copyright Sawdays Publishing
* Written by Colin Kirsopp
* 20/01/2008
*
* This JavaScript handles the loading and display of summary markers and place pins.
* Marker data is loaded on-demand via Ajax calls, but cached locally for reuse.
*/


/*
* function FilteredMarkerManager
* Param gmap: the map object on which the markers are to be displayed
* Param opt_opts: option values object.
* 
* This is the constructor function for the MarkerManager object
*/
function FilteredMarkerManager(gmap, opt_opts) 
{
  	var me = this;

	opt_opts = opt_opts || {};			
	me.gmap_ = gmap;					// reference to the GMap object
	me.markerLevel = me.getMarkerLevel(gmap.getZoom());	// current marker array index
	me.orFilter_ = opt_opts.orFilter ? opt_opts.orFilter: 127;		// current property-type filter setting 
	me.andFilter_ = opt_opts.andFilter ? opt_opts.andFilter: 0; 	// current facilities & cost filter setting 

	me.icons = null;
	me.setupIcons();	// create array of icons for markers

	me.markers_ = new Array();				// array for holding different level of summary markers
	me.markers_[0] = new Object();  			// the Object acts as a sparse-array for loaded Continent summary markers
	me.markers_[1] = new Object();  			// the Object acts as a sparse-array for loaded Country summary markers
	me.markers_[2] = new Object();  			// the Object acts as a sparse-array for loaded Region summary markers
	me.markers_[3] = new Object();  			// the Object acts as a sparse-array for loaded CoDePro summary markers
	me.markers_[4] = new Object(); 				// the Object acts as a sparse-array for loaded place markers
	me.levelBlockSize = [360, 90, 20, 5, 1];	// latitude size  in degrees of blocks for each marker level

	me.summaryMarkers = new Array();			// array of all Summary markers. Used for updating counts after filter changes

	me.displayedTiles = []; 				// holds currently displayed marker tiles

	GEvent.bind(gmap, "moveend", me, me.mapMoved);  // register for callback on moveend Events
}

/*
* function mapMoved
* 
* This is called to handle moveend events
*/
FilteredMarkerManager.prototype.mapMoved = function()  
{
	var me = this;
	var bounds = me.gmap_.getBounds(); // get the lat & long bounds of the current displayed
	var level = me.getMarkerLevel(me.gmap_.getZoom()); // what marker level are we displaying for the current zoom?

	var tilesToShow = me.getTilesToShow(bounds, level); 	// which 'tiles' of markers need to be displayed?
	me.updateMarkers(tilesToShow, level);			// update the display with the required markers
}	

/*
* function getTilesToShow
* param bounds: lat & long bound of the current map display
* param level: marker level associated with the current zoom-level
* 
* Based on the map bounds and the marker-level decide which tiles need to be displayed
*/
FilteredMarkerManager.prototype.getTilesToShow = function(bounds, level)
{
	var me = this;

	var ne = bounds.getNorthEast();
	var sw = bounds.getSouthWest();
	var tileCount = Math.floor(360/me.levelBlockSize[level]);
	var north = Math.floor((ne.lat()+90)/(me.levelBlockSize[level]/2));
	var south = Math.floor((sw.lat()+90)/(me.levelBlockSize[level]/2));
	var east = Math.floor((ne.lng()+180)/me.levelBlockSize[level]);
	var west = Math.floor((sw.lng()+180)/me.levelBlockSize[level]);

	if(east < west) // dateline in map viewport
	{
		east = Math.floor((ne.lng()+180+360)/me.levelBlockSize[level]);
	}
	var tileArray = new Array();

	var count = 0;
	for(var i=south; i<=north;i++)
	{
		for(var j=west; j<=east; j++)
		{
			tileArray[count++] = i+'_'+(j%tileCount)+'_'+level;
		}
	}
	return tileArray;	
}
	
/*
* function updateMarkers
* param tilesToShow: array containing the tiles that need to display
* param level: marker level associated with the current zoom-level
* 
* Update the markers displayed.  Download new data where necessary. Remove markers not on current display.
*/
FilteredMarkerManager.prototype.updateMarkers = function(tilesToShow, level)
{
  	var me = this;

// first workout if we nee to download any tile data
	var tileCount = 0;  // number of tiles we need to download
	var params = '?' + "action=loadMarkers&tiles="; // parameter list for Ajax request
	var first = true;
	for(var i=0; i<tilesToShow.length; i++)  // loop through the tiles we need to show
	{
		if(!me.markers_[level][tilesToShow[i]]) // if the tile is not already in the cache
		{
			if(!first) 
			{ 
				params += '+'; 
			}
			params += tilesToShow[i]	// add the tile to the parameter list for those to be downloaded
			tileCount++;				// keep track of how many tiles we need to download
			first = false;
		}	
	}
	if(tileCount >0) // if we need to download any tiles
	{
		if(level <4) // need to send current filter settings if requesting summary markers
		{
			params +='&orfilter='+me.orFilter_+'&andfilter='+me.andFilter_ ;
		}	
		var request = GXmlHttp.create();
		request.open('GET', '/maps/php/mapajax.php'+params, true);
		request.onreadystatechange = function() 
		{
			if (request.readyState == 4) // if Ajax responce received
			{
				var xmlDoc = request.responseXML;
				var mapdata = xmlDoc.documentElement;
				me.processMarkers(mapdata); // process new markers in XML data 
				me.refresh(tilesToShow); // update the display
			} 
		} 
		request.send(null);
	}
	else // we already have all the marker data we need
	{
		me.refresh(tilesToShow); // update the display
	}
	map.status("Done", 100); // show update status as done.
}


/*
* function processMarkers
* param mapdata: XML data for new markers
* 
* Process new marker data and add to marker tile cache
*/
FilteredMarkerManager.prototype.processMarkers = function(mapdata)
{
	var me = this;
	var tiles = mapdata.getElementsByTagName('tile'); // get array of tile data elements from XML data
	for(var i=0; i< tiles.length; i++) // loop through tile data elements
	{
		var tileName = tiles[i].getAttribute("tilename");
		var level = parseInt(tileName.substring(tileName.lastIndexOf('_')+1)); // extract level from tile name
		var children = tiles[i].childNodes; // get XML child nodes (long datasets may be split across multiple nodes)
		var nodeData = '';
		for(var j=0; j<children.length; j++) // loop through child nodes and concatenate data
		{
			if(children[j].nodeType == 4)
			{
				nodeData += children[j].nodeValue;
			}	
		}
		if(level == 4)
		{
			me.processPlaceMarkers(tileName, nodeData);
		}
		else
		{
			me.processSummaryMarkers(tileName, nodeData, level);		
		}
	}
}	

/*
* function processSummaryMarkers
* param tileName: name of the tile being processed
* param nodeData: marker data for this tile
* param level: marker level associated with the current zoom-level
* 
* Create markers from the marker data and add them to the tile cache.
*/
FilteredMarkerManager.prototype.processSummaryMarkers = function(tileName, nodeData, level)
{
	var me = this;
	var places = nodeData.split('\n');  // split nodeData into array of strings - one line per place
	var tileMarkers = new Array();
	for(var j=0; j<places.length; j++)
	{
		var placeData = places[j].split('\t'); // split data lines into individual data elements
		if(placeData.length==6) // only process if correct number of data per marker
		{
			var latlng = new GLatLng(placeData[3], placeData[4]); 
			var area_id = parseInt(placeData[0]); // area_id is primary key from maparea table - used for lookup
			var opts={ icon: me.icons[0],
					  clickable: true,
					  area_id: area_id,   
					  title: placeData[1],
					  labelText: placeData[2],
					  labelOffset: new GSize(-25, -11),
					  count: placeData[5]
					};
			var marker = new LabeledMarker(latlng, opts);
			me.summaryMarkers[placeData[0]] = marker; // add to list of summary markers (indexed by area_id)
			
			// add click event handler for the summary marker
			GEvent.addListener(marker, "click", function() {
				map.changeLocation(this.area_id_); // change the location of the map based on this area_id
			});
			tileMarkers[j] = marker;
		}
	}
	me.markers_[level][tileName] = tileMarkers; // add new summary markers to marker cache
}

/*
* function processPlaceMarkers
* param tileName: name of the tile being processed
* param nodeData: marker data for this tile
* 
* Create markers from the marker data and add them to the tile cache.
*/		
FilteredMarkerManager.prototype.processPlaceMarkers = function(tileName, nodeData)
{
	var me = this;
	var places = nodeData.split('\n'); // split nodeData into array of strings - one line per place

	var mapState = new Cookie("mapstate"); // read cookies
	var placeToHighlight = mapState.placeID;  // used to show the InfoWindow if loading a marker which should be highlighted
	
	var tileMarkers = new Array();
	for(var j=0; j<places.length; j++)
	{
		var placeData = places[j].split('\t'); // split data lines into individual data elements
		if(placeData.length==6) // only process if correct number of data per marker
		{
			var point = new GLatLng(placeData[2], placeData[3]);
			var marker = new GMarker(point, { icon: me.icons[parseInt(placeData[1])+1]});
			marker.placeid = placeData[0]; // index from map_places table
			marker.andFilter = placeData[4]; // set andFilter value for this marker
			marker.orFilter = placeData[5]; // set orFilter value for this marker

			// add callback function for when marker are clicked.  Setup Ajax call to get WindowInfo.
			GEvent.addListener(marker, "click",
				function(latlng) {
					var theMarker = this;
					
					// Calculate lat / lng for bounding box for marker at this zoom level
					var projection = me.gmap_.getCurrentMapType().getProjection();
					var markerPoint = projection.fromLatLngToPixel(latlng, me.gmap_.getZoom());
					var topLeftPoint = new GPoint(markerPoint.x - 10, markerPoint.y - 24);
					var bottomRightPoint = new GPoint(markerPoint.x + 10, markerPoint.y + 10);
					var topLeftLatLng = projection.fromPixelToLatLng(topLeftPoint, me.gmap_.getZoom(), false); 
					var bottomRightLatLng = projection.fromPixelToLatLng(bottomRightPoint, me.gmap_.getZoom(), false); 
					
					var lat_lower = bottomRightLatLng.lat();
					var lat_upper = topLeftLatLng.lat();
					var lng_lower = topLeftLatLng.lng();
					var lng_upper = bottomRightLatLng.lng();
					
					// set params to fetch marker info from marker bounding box
					var params = "?action=markerInfo&lat_lower="+lat_lower+"&lat_upper="+lat_upper+"&lng_lower="+lng_lower+"&lng_upper="+lng_upper+"&orfilter="+me.orFilter_+'&andfilter='+me.andFilter_ ; // parameter list for Ajax call
					var request = GXmlHttp.create();
					request.open('GET', '/maps/php/mapajax.php'+params, true);
					request.onreadystatechange = function() 
					{
						if (request.readyState == 4 && request.status == 200) // if Ajax response available
						{
							var xmlDoc = request.responseXML;	
							var root = xmlDoc.documentElement;
							if(root && root.childNodes){
								var placeCount = root.childNodes.length;
								if(placeCount == 1){
									me.showPlace(theMarker, root.childNodes[0]);
								}else{
									me.showPlaces(theMarker, root.childNodes);
								}
							}else{ // this shouldn't happen!
								alert(request.responseText);
							}
						} 
					} // end Ajax callback function	
					request.send(null);
				}); // end marker click callback function
			tileMarkers[j] = marker; // add marker to the tile
			if(marker.placeid == placeToHighlight) //if property selected
			{ 
				var selectedMarker = marker;
				var params = "?action=markerInfoHtml&placeid="+placeToHighlight; // parameter list for Ajax call
				var request = GXmlHttp.create();
				request.open('GET', '/maps/php/mapajax.php'+params, true);
				request.onreadystatechange = function() 
				{
					if (request.readyState == 4 && request.status == 200) // if Ajax response available
					{
						selectedMarker.openInfoWindowHtml('<div class="infowindow">'+request.responseText+'</div>');
					} 
				} // end Ajax callback function	
				request.send(null);
			}
		}
	}
	me.markers_[4][tileName] = tileMarkers; // add tile markers to marker-cache
}


FilteredMarkerManager.prototype.showPlace = function(marker, placeElement)
{
	var html = '<div class="infowindow">'+placeElement.nodeValue+'</div>';
	marker.openInfoWindowHtml(html);
}

FilteredMarkerManager.prototype.showPlaces = function(marker, placeElementArray)
{
	var me = this;
	var frontTabHtml = '<div class="infowindow" style="height:100px;overflow:auto;border:1px solid #999;">';
	for (var iNode = 0; iNode < placeElementArray.length; iNode++) {
		var node = placeElementArray[iNode];
		var placeName = node.childNodes[0].childNodes[0].nodeValue;
		var placeId = node.childNodes[2].childNodes[0].nodeValue;
		var markerImg = this.icons[parseInt(node.childNodes[1].childNodes[0].nodeValue)+1].image;
		frontTabHtml += '<p style="margin:3px;padding:2px 2px 2px 30px;background: url('+ markerImg +') no-repeat 3px 0px;"><a href="javascript:map.markerManager.showPlaceDetails(\''+placeId+'\');">'+placeName+'</a></p>';
	}
	frontTabHtml += '</div>';
	var backTabHtml = '<div class="infowindow"><p>Select a Place</p></div>';
	// open infoWindow containing Ajax reponse

	var projection = this.gmap_.getCurrentMapType().getProjection();
	var markerPoint = projection.fromLatLngToPixel(marker.getLatLng(), this.gmap_.getZoom());

	var swPoint = new GPoint(markerPoint.x - 10, markerPoint.y + 10);
	var nePoint = new GPoint(markerPoint.x + 10, markerPoint.y - 24);
	var swLatLng = projection.fromPixelToLatLng(swPoint, this.gmap_.getZoom(), false); 
	var neLatLng = projection.fromPixelToLatLng(nePoint, this.gmap_.getZoom(), false); 
	var rectOverlay = new Rectangle(marker, new GLatLngBounds(swLatLng, neLatLng), 2, '#000000', '#ffff00', 0.5);
	this.gmap_.addOverlay(rectOverlay);
	marker.openInfoWindowTabsHtml([new GInfoWindowTab('Places',frontTabHtml), new GInfoWindowTab('Details',backTabHtml)]);
	var infoWindowListener = GEvent.addListener(me.gmap_, 'infowindowclose' , function(){
		GEvent.removeListener(infoWindowListener);
		me.gmap_.removeOverlay(rectOverlay);
	});
}

FilteredMarkerManager.prototype.showPlaceDetails = function(placeId)
{
	var me = this;
	var params = "?action=markerInfoHtml&placeid="+placeId; // parameter list for Ajax call
	var request = GXmlHttp.create();
	request.open('GET', '/maps/php/mapajax.php'+params, true);
	request.onreadystatechange = function() 
	{
		if (request.readyState == 4) // if Ajax response available
		{
			var infoWindow = me.gmap_.getInfoWindow();
			if(infoWindow){
				var tabContainers = infoWindow.getContentContainers(); 
				tabContainers[1].innerHTML = '<div class="infowindow">'+request.responseText+'</div>';
				infoWindow.selectTab(1);
			}
		} 
	} // end Ajax callback function	
	request.send(null);	
}

/*
* function filterChanged
* param andFilter: new value of the andFilter from the user interface
* param orFilter: new value of the orFilter from the user interface
* 
* Called when the user changes any of the filter settings
*/			
FilteredMarkerManager.prototype.filterChanged = function(andFilter, orFilter)
{
	var me = this;

	me.andFilter_ = andFilter; // update the andFilter setting
	me.orFilter_ = orFilter; // update the orFilter setting
	var level = me.getMarkerLevel(me.gmap_.getZoom()); // get marker level corresponding to current map zoom level

	// Get new counts for summery markers
	var params = "?action=summarycounts"; // parameters for Ajax call
	params += "&andfilter="+andFilter;
	params += "&orfilter="+orFilter;
	var request = GXmlHttp.create();
	request.open('GET', '/maps/php/mapajax.php'+params, true);
	request.onreadystatechange = function() 
	{
		if (request.readyState == 4) // if Ajax response available
		{
			var xmlDoc = request.responseXML;
			var mapdata = xmlDoc.documentElement;
			var markers = mapdata.getElementsByTagName('areacount'); // get array of XML elements for map_areas
			for(var i=0; i< markers.length; i++)
			{
				var area_id = markers[i].getAttribute("area_id"); // set area_id from XML attribute
				var count = markers[i].getAttribute("count"); // set count from XML attribute
				if(me.summaryMarkers[area_id]){ 
					me.summaryMarkers[area_id].changeCount(count); // update count on marker
				}				
			} 
			// need to update current markers
			var toShow = me.displayedTiles.concat();  // clone the currently displayed tiles as the tiles to show
			me.removeAll(); // clear existing markers
			me.refresh(toShow); // re-add markers using new filter settings
		} 
	} //function	
	request.send(null);
}


/*
* function refresh
* param tilesToShow: array of tilenames to be shown
* 
* Redraws the markers onto the map from the specified tiles
*/			
FilteredMarkerManager.prototype.refresh = function(tilesToShow)
{
	var me = this;
	var zoom = me.gmap_.getZoom();
	me.removeDisplayedMarkers(tilesToShow); // remove any currently displayed markersthat aren't on the tilesToShow
	for(var i=0; i<tilesToShow.length; i++)
	{
		if(!me.tileIsIn(me.displayedTiles, tilesToShow[i])) // if already displayed don't add
		{
			var tileLevel = parseInt(tilesToShow[i].slice(-1)); // extract marker level from tile name
			var tileMarkers = me.markers_[tileLevel][tilesToShow[i]]; // marker array for this tile
			if(tileMarkers)
			{
				for(var j=0; j<tileMarkers.length; j++)
				{
					if(tileMarkers[j] instanceof LabeledMarker)
					{
						if(tileMarkers[j].count > 0) // only add summary markers if it contains places matching the current filter
						{
							me.gmap_.addOverlay(tileMarkers[j]);				
						}	
					}
					else
					{
						// only add place marker pins if the place matched the current filter settings
						if(((tileMarkers[j].orFilter & me.orFilter_) > 0) && ((tileMarkers[j].andFilter & me.andFilter_) == me.andFilter_))
						{
							me.gmap_.addOverlay(tileMarkers[j]);
						}				
					}
				}
			}
		}
	}	
	me.displayedTiles = tilesToShow;  	// update record of currently shown tiles 
	me.mapZoom_ = zoom;			// update record of currently displayed zoom level

}

/*
* function removeAll
* 
* Removes all currently displayed markers from the map. Used when the filter settings are changed
*/			
FilteredMarkerManager.prototype.removeAll = function()
{
	var me = this;
	for(var i=0; i<me.displayedTiles.length; i++)
	{
		var tileLevel = parseInt(me.displayedTiles[0].slice(-1));
		var tileMarkers = me.markers_[tileLevel][me.displayedTiles[i]];
		if(tileMarkers)
		{
			for(var j=0; j<tileMarkers.length; j++)
			{
				me.gmap_.removeOverlay(tileMarkers[j]);
			}
		}
	}
	me.displayedTiles.length = 0;  // empty displayTiles array
}


/*
* function removeDisplayedMarkers
* param tilesToShow: array of tilenames for tile that need to be displayed after this update
*
* Removes all currently displayed markers from the map. Used when the filter settings are changed
*/			
FilteredMarkerManager.prototype.removeDisplayedMarkers = function(tilesToShow)
{
	var me = this;
	// remove current markers
	for(var i=0; i<me.displayedTiles.length; i++) // loop through displayed tiles
	{
		var tileLevel = parseInt(me.displayedTiles[i].slice(-1)); // get marker level from tilename
		if(!me.tileIsIn(tilesToShow, me.displayedTiles[i])) // if still needs to be displayed, don't remove
		{
			var tileMarkers = me.markers_[tileLevel][me.displayedTiles[i]]; // get marker for tile
			if(tileMarkers)
			{
				for(var j=0; j<tileMarkers.length; j++)
				{
					me.gmap_.removeOverlay(tileMarkers[j]); // remove marker from map
				}
			}
		}
	}
}

/*
* function tileIsIn
* param tileArray: array of tilenames to check
* param tile: tilenamesearched for
*
* Returns true if tile is within tileArray, otherwise false
*/			
FilteredMarkerManager.prototype.tileIsIn = function(tileArray, tile)
{
	var isIn = false;
	for(var i=0; i<tileArray.length && isIn==false; i++)
	{
		if(tileArray[i] == tile)
		{
			isIn = true;
		}
	}
	return isIn;
}

/*
* function getMarkerLevel
* param zoom: zoom level from the map
*
* Returns marker level corresponding to the zoom level
*/			
FilteredMarkerManager.prototype.getMarkerLevel = function(zoom)
{
	switch(zoom)
	{
		case 0: case 1: case 2:
			var level = 0; break;  // continent level
		case 3: case 4:
			var level = 1; break; // country level
		case 5: case 6: 
			var level = 2; break; // region level
		case 7: case 8: 
			var level = 3; break; // CoDePro level
		default: 
			var level = 4; break; // place marker level
	}
	return level;
}

/*
* function setupIcons
*
* creates the icons used for the marker display
*/			
FilteredMarkerManager.prototype.setupIcons = function()
{
	var me = this;
	var yellowIcon = new GIcon();
	yellowIcon.image = "/maps/i/map_pins/marker_yellow.png";
	yellowIcon.shadow = "/maps/i/map_pins/shadow.png";
	yellowIcon.shadowSize = new GSize(37,34);
	yellowIcon.iconSize = new GSize(20,34);
	yellowIcon.iconAnchor = new GPoint(12,34);
	yellowIcon.infoWindowAnchor = new GPoint(8,14);

	var blueIcon = new GIcon(yellowIcon); // use yellow icon as the base for other icons - just change the pin image
	blueIcon.image = "/maps/i/map_pins/marker_blue.png";
	var greenIcon = new GIcon(yellowIcon);
	greenIcon.image = "/maps/i/map_pins/marker_green.png";
	var orangeIcon = new GIcon(yellowIcon);
	orangeIcon.image = "/maps/i/map_pins/marker_orange.png";
	var brownIcon = new GIcon(yellowIcon);
	brownIcon.image = "/maps/i/map_pins/marker_brown.png";
	var purpleIcon = new GIcon(yellowIcon);
	purpleIcon.image = "/maps/i/map_pins/marker_purple.png";
	var redIcon = new GIcon(yellowIcon);
	redIcon.image = "/maps/i/map_pins/marker_red.png";
	var yellowBlueIcon = new GIcon(yellowIcon);
	yellowBlueIcon.image = "/maps/i/map_pins/marker_blue_yellow.png";
	var purpleBlueIcon = new GIcon(yellowIcon);
	purpleBlueIcon.image = "/maps/i/map_pins/marker_purple_blue.png";
	var orangeBlueIcon = new GIcon(yellowIcon);
	orangeBlueIcon.image = "/maps/i/map_pins/marker_orange_blue.png";

	var areaicon = new GIcon(); // 'post-it' icon for summary markers
	areaicon.image = '/maps/i/area_marker.png';
	areaicon.iconSize = new GSize(51, 36);
	areaicon.iconAnchor = new GPoint(24, 16);
	areaicon.infoWindowAnchor = new GPoint(24, 16);

	me.icons = new Array(areaicon, yellowIcon, blueIcon, redIcon, orangeIcon, brownIcon, purpleIcon, greenIcon, yellowBlueIcon, purpleBlueIcon, orangeBlueIcon);
}

