/**
 * Generic Google Maps handling for Gutenberg Framework
 *
 * @author   Andy West
 *
 **/


/**
 * Class that acts as a wrapper for a GoogleMap
 *
 **/
function MapHandler()
{


	/**
	 * Holds an array of GIcon objects
	 *
	 **/
	this.Icons = new Array();

	
	/**
	 * Holds an array of different GutenMarkerType objects
	 *
	 **/
	this.MarkerTypes = new Array();


	/**
	 * Holds an array of GutenMarker objects
	 *
	 **/
	this.Markers = new Array();


	/**
	 * Calls all of the functions required to create the GMap2 object
	 * and adds Markers as defined in the ParsedXml global variable.
	 *
	 **/
	this.MakeMap = function()
	{
	    this.ReadBasicConfig();
		this.MakeIcons();
		this.MakeMarkerTypes();
		this.MakeMarkers();
		this.GoHome();
		this.PlaceMarkers();

		this.Map.enableScrollWheelZoom();
		this.Map.enableDoubleClickZoom();
		this.Map.enableContinuousZoom();

		if('undefined' != typeof(this.OnInitialised))
		{
			this.OnInitialised();
		}
	};


	/**
	 * Creates the GMap2 object and sets the starting point, zoom level and map type.
	 *
	 **/
	this.ReadBasicConfig = function()
	{
		var Config = ParsedXml.documentElement.getElementsByTagName("config");
		
		// containingelement is the id of the DOM element that the map will be
		// placed into
		this.Map = new GMap($(Config[0].getAttribute("containingelement")));
		
		// zoomcallback is the name of an external javascript function that
		// will be called when the map has finished any zoom operations
		this.ZoomCallBack = eval(Config[0].getAttribute("zoomcallback"));

		// holds a GPoint object for the default starting point of the map
		this.StartPoint = new GLatLng(  parseFloat(Config[0].getAttribute("startlat")),
										parseFloat(Config[0].getAttribute("startlng")));
												
		// the initial zoom level of the map
		this.StartZoom  = parseInt(Config[0].getAttribute('startzoom'),10);
		
	
		if ('undefined' != typeof MapTypes)
		{
//			var TypeCount = 4;
			
			if (-1 != MapTypes.indexOf('custom'))
			{
				this.Map.addMapType(GetCustomMap());
			}
//			else
//			{
//				TypeCount--;	
//			}

			if (-1 == MapTypes.indexOf('normal'))
			{
				this.Map.removeMapType(G_NORMAL_MAP);
//				TypeCount--;	
			}
			
			if (-1 == MapTypes.indexOf('satellite'))
			{
				this.Map.removeMapType(G_SATELLITE_MAP);
//				TypeCount--;	
			}
			
			if (-1 == MapTypes.indexOf('hybrid'))
			{
				this.Map.removeMapType(G_HYBRID_MAP);
//				TypeCount--;
			}
			
			
//			if (1< TypeCount)
//			{
//				this.Map.addControl(new GMapTypeControl());		
//			}
		}
		else
		{
			// :KLUDGE: Quick hack for now to show custom map and controls
			this.Map.addMapType(GetCustomMap());
		
//			this.Map.addControl(new GMapTypeControl());
		} 

		// the type of map ie. map, satellite or hybrid
		if ('normal'==Config[0].getAttribute("maptype"))
		{
			this.MapType = G_NORMAL_MAP;	
		}
		else if ('satellite'==Config[0].getAttribute("maptype"))
		{
			this.MapType = G_SATELLITE_MAP;	
		}
		else if ('hybrid'==Config[0].getAttribute("maptype"))
		{
			this.MapType = G_HYBRID_MAP;	
		}

		this.Map.setMapType(this.MapType);

		//this.Map.addControl(new GLargeMapControl());
		
		//Gets the name of the GeoCodeOverrideFunction to be called
		this.GeoCodeOverrideFunction = Config[0].getAttribute("geocodeoverride");
		
		// MoveEnd is the name of an external callback function that
		// will be called when the view is moved.
		GEvent.addListener(this.Map, "moveend", function() {eval(Config[0].getAttribute("moveendcallback"));});
		
		this.OnInitialised = eval(Config[0].getAttribute("oninitialised"));
		
		if ('undefined' != typeof(SliderButton))
		{
			GEvent.addListener(this.Map, "zoomend", function(a,b) { SliderButton.setValue(b) });
		}
	}


	/**
	 * Creates the array of Icons as specified in the XML
	 *
	 **/
	this.MakeIcons = function()
	{
		var Icons = ParsedXml.documentElement.getElementsByTagName("icon");
		for (var i = 0; i < Icons.length; i++)
		{
			// Assign to a temp variable as it doesn't seem to work if you add directly to the array
			var TempIcon = new GIcon();
			TempIcon.image            = Icons[i].getAttribute('image');
			TempIcon.shadow           = Icons[i].getAttribute('shadow');
			TempIcon.iconSize         = new GSize(parseInt(Icons[i].getAttribute('iconsizex')), parseInt(Icons[i].getAttribute('iconsizey')));
			TempIcon.shadowSize       = new GSize(parseInt(Icons[i].getAttribute('shadowsizex')), parseInt(Icons[i].getAttribute('shadowsizey')));
			TempIcon.iconAnchor       = new GPoint(parseInt(Icons[i].getAttribute('iconanchorx')), parseInt(Icons[i].getAttribute('iconanchory')));
			TempIcon.infoWindowAnchor = new GPoint(parseInt(Icons[i].getAttribute('infowindowanchorx')), parseInt(Icons[i].getAttribute('infowindowanchory')));
			
			this.Icons[this.Icons.length] = TempIcon;
		}
	}


	/**
	 * Creates the array of GutenMarkerTypes as specified in the XML
	 *
	 **/
	this.MakeMarkerTypes = function()
	{
		var MarkerTypes = ParsedXml.documentElement.getElementsByTagName("markertype");
		for (var i = 0; i < MarkerTypes.length; i++)
		{
		   this.MarkerTypes[this.MarkerTypes.length] = new GutenMarkerType(parseFloat(MarkerTypes[i].getAttribute("id")), this.Icons[parseInt(MarkerTypes[i].getAttribute("icon"))], MarkerTypes[i].getAttribute("uri"), MarkerTypes[i].getAttribute("overlayfunction"), parseInt(MarkerTypes[i].getAttribute("minzoom")), parseInt(MarkerTypes[i].getAttribute("maxzoom")));
		}
	}


	/**
	 * Creates the array of GutneMarkers as specified in the XML
	 *
	 **/
	this.MakeMarkers = function()
	{
		var Markers = ParsedXml.documentElement.getElementsByTagName("marker");
		for (var i = 0; i < Markers.length; i++)
		{
			var Point = new GLatLng(parseFloat(Markers[i].getAttribute("lat")), parseFloat(Markers[i].getAttribute("lng")));
			this.Markers[this.Markers.length] = new GutenMarker(Markers[i].getAttribute("id"), parseFloat(Markers[i].getAttribute("type")), Point);
		}
	}


	/**
	 * Iterates the array of Markers and places them on the map.  The PlaceMarker
	 * function used to put the markers on the map has been placed outside the class
	 * definition to get around a nasty scoping problem that I was unable to solve
	 * in any other way. @@TODO: Maker this use the MarkerManager object
	 *
	 **/
	this.PlaceMarkers = function()
	{
/*	   if (typeof(this.MarkerManager == 'undefined'))
	   {
	      this.MarkerManager = new GMarkerManager(this.Map);
	   }
*/
	   for(var i=0; i < this.Markers.length; i++)
	   {
		   PlaceMarker(this.Markers[i], this);
	   }
	   
	};


	/**
	 * Returns the map to the starting point and zoom level.
	 *
	 **/
	this.GoHome = function()
	{
		this.Map.setCenter(this.StartPoint, this.StartZoom);
		if (typeof(this.ZoomCallBack) != 'undefined')
		{
			this.ZoomCallBack(this);
		}
		this.Map.setMapType(this.MapType);
	};


	/**
	 * Starts panning in the specified direction.  Uses an interval to allow
	 * continuous scrolling rather than the default behaviour of scrolling half
	 * a screen and stopping.
	 *
	 **/
   this.panDirection = function(dx, dy)
   {
      this.Map.panDirection(dx, dy);
		this.Panner = setInterval('MyMap.Map.panDirection('+dx+','+dy+')',5);
   };


	/**
	 * Clears the interval used for panning so that you cannot end up with a
	 * massive buffer of movements after releasing the control to move the map
	 *
	 **/
   this.StopPanning = function()
   {
      clearInterval(this.Panner);
   };


	/**
	 * Zooms the map in one level and calls the ZoomCallBack function
	 *
	 **/
   this.ZoomIn = function()
   {
      this.Map.zoomIn();
      this.ZoomCallBack(this);
   };


	/**
	 * Zooms the map out one level and calls the ZoomCallBack function
	 *
	 **/
   this.ZoomOut = function()
   {
      this.Map.zoomOut();
      this.ZoomCallBack(this);
   };

	/**
	 * Returns the current zoom level of the map
	 *
	 **/
	this.getZoom = function()
	{
	   return this.Map.getZoom();
	};

	this.getCentre = function()
	{
		return this.Map.getCenter();
	}

	/**
	 * Returns a GutenMarkerType for the given TypeId
	 *
	 * @param    TypeId    int    The ID of the GutenMarkerType we are looking for
	 *
	 * @return   GutenMarkerType
	 *
	 **/
	 this.GetMarkerType = function(TypeId)
	 {
	   for (var i =0; i < this.MarkerTypes.length; i++)
	   {
	      if(this.MarkerTypes[i].TypeId == TypeId)
	      {
	         return this.MarkerTypes[i];
	      }
	   }
	 };
	 


	this.GetMarkerById = function(MarkerId)
	{
		for(var i=0; i < this.Markers.length; i++)
		{
			if (this.Markers[i].MarkerId == MarkerId)
			{
				return (this.Markers[i]);
			}
	 	}
	 	return false;
	}


	/**
	 * Function to ensure we only instantiate one GeoCoder
	 *
	 **/
	this.GetGeoCoder = function()
	{
	   if (typeof(this.GeoCoder == 'undefined'))
	   {
	      this.GeoCoder = new GClientGeocoder();
	   }
		return this.GeoCoder;
	}


	/**
	 *
	 *
	 **/
	this.GeoCodeAddress = function (Address)
	{
		this.GetGeoCoder().getLatLng( Address,
												 function(Point)
												 {
													if (!Point)
													{
														alert(Address + " not found");
													}
													else
													{
														AddressPoint = Point;
													}
												 }
												);

	};


	this.FindNearest = function (Address)
	{

		if (this.GeoCodeOverrideFunction != '')
		{
			eval(this.GeoCodeOverrideFunction)(Address);
		}
		else
		{
			this.GetGeoCoder().getLatLng( Address,
												 function(Point)
												 {
													if (!Point)
													{
															ShowAlert('no result', 'orig');
													}
													else
													{
														ShowAlert(Point,'orig')
													}
													
												 }
									 );
		}
	}
	

	/**
	 * Returns the class type so that it is nice and easy to identify the class
	 *
	 **/
	this.toString = function() {return 'MapHandler';};
} // End of MapHandler()




function ShowAlert(Point,WhereFrom)
{
	
	if (Point != 'no result')
	{
	   // Roughly a half way around the earth.  As far as you can go.
		var ShortestDistance = 20040000;

		for (var i =0; i < MyMap.Markers.length; i++)
		{
			var x = Point.distanceFrom(MyMap.Markers[i].Point);
			if (x < ShortestDistance)
			{
				var NearestMarker = MyMap.Markers[i];
				ShortestDistance = x;
			}
		}
		ReportNearest(NearestMarker);
	}
	else
	{
	   // TODO :: un-hard-code this
		document.getElementById('search_message').innerHTML = 'Your address could not be found.';
	}	
}




/**
 * Class to hold data relevant to the different types of Marker we want to show
 * on our map
 *
 **/
function GutenMarkerType(TypeId, Icon, HtmlUrl, OverlayFunction, MinZoom, MaxZoom)
{
	this.TypeId = TypeId;
	this.Icon = Icon;
	this.HtmlUrl = HtmlUrl;
	this.OverlayFunction = OverlayFunction;
	this.MinZoom = MinZoom;
	this.MaxZoom = MaxZoom;


	/**
	 * Returns the icon associated with this MarkerType
	 *
	 **/
	this.GetIcon = function()
	{
	   return this.Icon;
	};


	/**
	 * Returns the class type so that it is nice and easy to identify the class
	 *
	 **/
	this.toString = function()
	{
		var ObjectString = 'GutenMarkerType\n\n';
		ObjectString += 'TypeId: '+this.TypeId+'\n';
		//ObjectString += 'Icon: '+this.Icon+'\n';
		ObjectString += 'HtmlUrl: '+this.HtmlUrl;
		return ObjectString;
	};

} // End of GutenMarkerType()


/**
 *
 *
 **/
function GutenMarker(MarkerId, Type, Point)
{
	this.MarkerId = MarkerId;
	this.MarkerType = Type;
	this.Point = Point;

	this.prototype = new GMarker(Point);


	/**
	 * Gets the icon appropriate for this Marker, based on it's Type
	 *
	 **/
	this.GetIcon = function(MarkerTypes)
	{
	   return MarkerTypes[this.MarkerType].GetIcon();
	};


	/**
	 * Returns the GutenMarkerType for this GutenMarker
	 *
	 **/
	this.GetMarkerType = function()
	{
	   var MarkerType = MyMap.GetMarkerType(this.MarkerType);
		return MarkerType;
	};


	/**
	 * Returns the Url for the ajax request for the Markers Html info window
	 *
	 **/
	this.GetAjaxUrl = function()
	{
	   var MarkerType = this.GetMarkerType();
		return MarkerType.HtmlUrl + this.MarkerId;
	};
	 
	 
	/**
	 * Returns the class type so that it is nice and easy to identify the class
	 *
	 **/
	this.toString = function() {
		var ObjectString = 'GutenMarker\n\n';
		ObjectString += 'MarkerId: '+this.MarkerId+'\n';
		ObjectString += 'MarkerType: '+this.MarkerType+'\n';
		ObjectString += 'Point: '+this.Point;
		return ObjectString;
	};
	
	/**
	 * Returns the function name to call for making the popup window
	 */	 	 	
	this.GetOverlayFunction = function()
	{
		var MarkerType = this.GetMarkerType();
		return MarkerType.OverlayFunction;
	};
	

	/**
	 * Returns the minimum zoom level this marker should be shown at
	 */	 	 	
	this.GetMinZoom = function()
	{
		var MarkerType = this.GetMarkerType();
		return MarkerType.MinZoom;
	};


	/**
	 * Returns the maximim zoom level this marker should be shown at
	 */	 	 	
	this.GetMaxZoom = function()
	{
		var MarkerType = this.GetMarkerType();
		return MarkerType.MaxZoom;
	};

}  // End of GutenMarker()

GutenMarker.prototype = GMarker.prototype;//(new GLatLng(1,1));


/**
 * Extend the GOverlay class with our own class to allow for more control over
 * the functionality.
 *
 **/
function GutenOverlay(Width, Height, PointXOffset, PointYOffset, FocalPoint, AjaxWrapper) {
  this.Width = Width;
  this.Height = Height;
  this.PointXOffset = PointXOffset;
  this.PointYOffset = PointYOffset;
  this.FocalPoint = FocalPoint;
  this.div_ = AjaxWrapper;
}

GutenOverlay.prototype = new GOverlay();


/**
 * Adds the overlay div to the map pane
 *
 **/
GutenOverlay.prototype.initialize = function(Map) {
	// Add the overlay to the correct PANE of the map
	Map.getPane(G_MAP_FLOAT_PANE).appendChild(this.div_);
	this.map_ = Map;
}


/**
 * Remove the overlay DIV from the map pane
 *
 **/
GutenOverlay.prototype.remove = function() {
  this.div_.parentNode.removeChild(this.div_);
}


/**
 * Copy the overlay
 *
 **/
GutenOverlay.prototype.copy = function() {
  return new GutenOverlay(this.Width, this.Height, this.PointXOffset, this.PointYOffset, this.FocalPoint);
}


/**
 * Redraw the overlay based on the current projection and zoom level
 *
 **/
GutenOverlay.prototype.redraw = function(force) {
  // We only need to redraw if the coordinate system has changed
  if (!force) return;

  // Get the pixel position of the focal point of the overlay
  var OverlayCoords = this.map_.fromLatLngToDivPixel(this.FocalPoint);

  // Now position our DIV based on the DIV coordinates of our bounds
  this.div_.style.left = (OverlayCoords.x - this.PointXOffset) + "px";
  this.div_.style.top = (OverlayCoords.y - this.PointYOffset) + "px";
}


function GetCustomMap() {
	Copyrights = new GCopyrightCollection('Custom Map');
	
	var TileSet = new GTileLayer(Copyrights, 2, 6);

	TileSet.getTileUrl = function(tile, zoom){
		QuadTile = TileToQuadKey(tile.x, tile.y, zoom);		
		// :TODO: Unhardcode this. and maybe take a param to allow the function 
		// to handle multiple types of custom maps
		tileUrl = '/static/images/custom_map/' + QuadTile + '.png';
		return tileUrl;
	};

	TileSet.isPng = function() { return true; }
	TileSet.getOpacity = function() { return 1.0; }

	var MapType = new GMapType(
		[TileSet],
		new GMercatorProjection(18),
		'Custom',
		{
			shortName:'NT',
			tileSize:256,
			maxResolution:6,
			minResolution:2
		}
	);

	return MapType;
}

function TileToQuadKey(tx, ty, zl) { 
   var quad; 
   quad = ""; 
   for (var i = zl; i > 0; i--){ 
       var mask = 1 << (i - 1); 
       var cell = 0; 
       if ((tx & mask) != 0) 
           cell++; 
       if ((ty & mask) != 0) 
           cell += 2; 
       quad += cell; 
   } 
   return quad; 
}
