/**
 * A Google maps API wrapper with buildin Geocoding.
 * The Map depends upon Prototype, Caching and AjaxRequest.
 *
 * @version 1.0.0
 * @author Gijs van de Nieuwegiessen
 */
var Map = Class.create(
{
	// Private properties
	_container: null,
	_gmap: null,
	_mapMarkers: [],
	_bounds: null,
	_options: {},
	_activeWindow: null,
	_defaultOptions: 
	{
		/**
		 * @var string  Width of the Google Map
		 */
		width: '500px',
		
		/**
		 * @var string  Height of the Google Map
		 */
		height: '500px',
		
		/**
		 * @var string Style of geocoding (client or service)
		 */
		geocodeStyle: 'client',

		/**
		 * @var string Geocoding service endpoint (if style is service)
		 */
		geocodeServiceUri: _AJAX_DEFAULT_ENDPOINT,
		
		/**
		 * @var string Geocoding service name (if style is service)
		 */
		geocodeServiceName: 'GeoService',
		
		/**
		 * @var string Geocoding service method (if style is service)
		 */
		geocodeServiceMethod: 'GeoCode',
		
		/**
		 * @var GLatLng Initial Lat Lng position of the GMap
		 */
		initLatLng:  new GLatLng(52.132633,5.291266),
		
		/**
		 * @var int Initial zoom level of the GMap
		 */
		initZoom: 6,
		
		/**
		 * @var array of GMapControls used by the GMap
		 */
		mapControls:  [new GSmallMapControl(), new GMapTypeControl()],
		
		/**
		 * @var GIcon The Marker icon
		 */
		markerIcon: new GIcon(G_DEFAULT_ICON),
		
		/**
		 * @var bool Indicates if the marker is clickable
		 */
		markerClickable: true,
		
		/**
		 * @var bool Show the index number along with the Name
		 */
		markerShowIndexInName: false,
		
		/**
		 * @var bool Show a button to zoom in on Marker
		 */
		markerShowZoomButton: true,
		
		/**
		 * @var bool Show a button that browses to a Directions page
		 */
		markerShowDirectionsButton: true,
		
		/**
		 * @var int Marker zoomed in level
		 */
		markerZoomInLevel: 16,
		
		/**
		 * @var int Zoom level that is used when 
		 * centering the Map to show all Markers
		 */
		markerBoundsZoomLevel: undefined,
		
		/**
		 * @var string Url of directions
		 */
		markerDirectionsUrl: "http://maps.google.nl/maps?saddr=&daddr={0}",
		
		/**
		 * @var Marker mouse over event handler 
		 */
		markerOnMouseClick: null,
		
		/**
		 * @var Marker mouse over event handler 
		 */
		markerOnMouseOver: null,
		
		/**
		 *  @var Marker mouse out event handler 
		 */
		markerOnMouseOut: null,
		
		/**
		 *
		 **/
		markerOpenWindowOnLoad: false,
		
		/**
		 * @var Display an info window when clicking on a Marker
		 */
		enableInfoWindow: true
	},
	
	/**
	 * Constructor
	 * @param string Id of the HTML container
	 * @param array of Options
	 * @usage new Map(htmlId[,options]);
	 */
	initialize: function(containerId, options)
	{
		// Setting up original options with default options
		Object.extend(this._options, this._defaultOptions);
		Object.extend(this._options, options);
		
		if (!GBrowserIsCompatible()) 
		{
			throw 'This Browser is not compatible with the Google Maps api';
			return;
		}
		
		if (!$(containerId)) 
		{
			throw 'The maps container with the id: ' + containerId + ' does not exist! Make sure the element exists or that this code is executed after the element is loaded! (body onload)';
		}
		
		this._bounds = new GLatLngBounds();
		this._container = $(containerId);

		this.SetWidth(this._options.width);
		this.SetHeight(this._options.height);
		
		this.bindMapEvents();
		this.preventMemoryLeaks();
		
		return this;
	},
	
	/**
	 * Display the Map
	 */
	Show: function(center)
	{
		this._gmap = new GMap2(this._container);
		this._gmap.enableDoubleClickZoom();
		this._gmap.setCenter(this._options.initLatLng, this._options.initZoom);
		
		this.addMapControls();
		
		this.Refresh(center);
	},
	
	Refresh: function(center)
	{
		var setCenter = (center==undefined) ? true : center;
		
		this.placeMapMarkers();
		
		if(setCenter)
		{
			this.setCenter();
		}
	},
	
	/**
	 * Add a collection of Map markers
	 * @param array MapMarker
	 */
	AddMapMarkers: function(markers)
	{
		for (var i = 0; i < markers.length; i++) 
		{
			var marker = markers[i];
			this.AddMapMarker(marker);
		}
	},
	
	/**
	 * Add a Map marker
	 * @param MapMarker marker
	 */
	AddMapMarker: function(marker)
	{
		Log('Adding a new marker');
		
		this._mapMarkers[this._mapMarkers.length] = marker;
	},
	
	/**
	 * Add a GOverlay to the Map
	 */
	AddOverlay: function(gobj)
	{
		this._gmap.addOverlay(gobj);
	},
	
	/**
	 * Remove a GOverlay from the Map
	 */
	RemoveOverlay: function(gobj)
	{
		this._gmap.removeOverlay(gobj);
	},
	
	/**
	 * Resize the width of the map
	 * @param width string
	 */
	SetWidth: function(width)
	{
		this._options.width = width;
		this._container.setStyle({'width': width});	
	},
	
	/**
	 * Resize the height of the map
	 * @param height string
	 */
	SetHeight: function(height)
	{
		
		this._options.height = height;
		this._container.setStyle({'height': height});	
	},
	
	/**
	 * @return latlng
	 */
	GetNorthEast: function()
	{
		var bounds = this._gmap.getBounds();
	    return bounds.getNorthEast();
	},
	
	/**
	 * @return latlng
	 */
	GetSouthWest: function()
	{
		var bounds = this._gmap.getBounds();
	    return bounds.getSouthWest();
	},
	
	/**
	 * Center map to display all markers.
	 */
	setCenter: function()
	{
		if(this._bounds.isEmpty())
		{
			return;
		}
		var zoomLevel = this._options.markerBoundsZoomLevel;
		
		if( !zoomLevel )
		{
			zoomLevel = this._gmap.getBoundsZoomLevel(this._bounds) - 1;
		}
		
		
		this._gmap.setZoom(zoomLevel);
		this._gmap.setCenter(this._bounds.getCenter());
	},
	
	/**
	 * Add GMap controls, if any...
	 */
	addMapControls: function()
	{
		for (var i = 0; i < this._options.mapControls.length; i++) 
		{
			this._gmap.addControl(this._options.mapControls[i]);
		}
	},
	
	/**
	 * Create all markers on the Map
	 */
	placeMapMarkers: function()
	{
		for (var i = 0; i < this._mapMarkers.length; i++) 
		{
			var marker = this._mapMarkers[i];
			
			// Huh?
			if(marker == null) continue;
			
			if (marker.Lat && marker.Lng) 
			{
				this.placeMapMarker(marker);
			}
			else 
			{
				// Search address and add Marker
				this.geocode(marker);
			}
		}
	},
	
	/**
	 * Place a marker on the Map
	 * @param MapMarker marker
	 */
	placeMapMarker: function(marker)
	{
		var point = new GLatLng(marker.Lat, marker.Lng);
		this._bounds.extend(point);
		
		var scope = this;
		var gmarker = new GMarker(point, {icon: this._options.markerIcon, clickable: this._options.markerClickable});
		
		if(this._options.enableInfoWindow)
		{
			GEvent.addListener(gmarker, "click", function(e)
			{
				this.openInfoWindowHtml(scope.createWindowHTML(marker));
			});
		}
		else 
		{
			GEvent.addListener(gmarker, "click", function(latlng)
			{
				if( !scope._options.markerOnMouseClick )
					return;
				
				scope._options.markerOnMouseClick(gmarker, marker, latlng);
			});
		}
				
		GEvent.addListener(gmarker, "mouseover", function() 
		{
			if( !scope._options.markerOnMouseOver )
				return;
			
			scope._options.markerOnMouseOver(gmarker, marker);
       	});  
		
		GEvent.addListener(gmarker, "mouseout", function() 
		{
			if( !scope._options.markerOnMouseOut )
				return;
					
			scope._options.markerOnMouseOut(gmarker, marker);
		}); 
		
		// Add marker to map
		this.AddOverlay(gmarker);
		
		
		if(this._options.markerOpenWindowOnLoad)
		{
			gmarker.openInfoWindowHtml(scope.createWindowHTML(marker));
		}
	},
	
	/**
	 * Try to Geocode a MapMarker that has no Lat/Lng
	 * @param MapMarker marker
	 */
	geocode: function(marker)
	{
		if (marker.Street.toLowerCase() == "postbus") 
		{
			// No way we can encode a PO box
			return;
		}
		
		var scope = this;
		
		switch (this._options.geocodeStyle)
		{
			case 'service':
				// JSON service geocoding
				AjaxRequest(this._options.geocodeServiceUri, this._options.geocodeServiceName, this._options.geocodeServiceMethod, 
				{
					index: marker.Index,
					id: marker.Id,
					street: marker.Street,
					housenumber: marker.Housenumber,
					postalcode: marker.Postalcode,
					city: marker.City
				}, 
				function(response)
				{
					var result = response.responseJSON;
					
					if (!result) 
					{
						throw 'AjaxRequest not receive a result ' + response.responseText;
						return;
					}
					
					if (!marker) 
					{
						throw 'AjaxRequest no marker.. ';
						return;
					}
					
					marker.Lng = result['lng'];
					marker.Lat = result['lat'];
					
					scope.placeMapMarker(marker);
					scope.setCenter();
				});
				break;
				
			default:
				
				// Clientside Geocoding
				var geocoder = new GClientGeocoder();
				var address = marker.Address();
				
				geocoder.getLatLng(address, function(point)
				{
					if ( !point ) 
					{
						throw 'GClientGeocoder no geocoded point.. ';
						return;
					};
					
					if ( !marker ) 
					{
						throw 'GClientGeocoder no marker.. ';
						return;
					};
					
					marker.Lng = point.lng();
					marker.Lat = point.lat();
					
					scope.placeMapMarker(marker);
					scope.setCenter();
				});
				break;
		}
	},
	
	/**
	 * Create the HTML shown inside a Info Window
	 * @param MapMarker marker
	 */
	createWindowHTML: function(marker)
	{
		var scope = this;
		
		if (!marker) 
		{
			return null;
		}
		
		var div = new Element('div', 
		{
			'class': 'map_window'
		});
		
		var name = marker.Name;
		
		if (this._options.markerShowIndexInName) 
		{
			// Show a name leading with the index number
			name = marker.Index + '. ' + name;
		}
		
		div.insert(new Element('h3').update(name));
		
		var ul = new Element('ul');
		div.insert(ul);
		
		if (marker.PhoneNumber) 
		{
			ul.insert(new Element('li').update("Tel. " + marker.PhoneNumber));
		}
		
		if (marker.Street) 
		{
			ul.insert(new Element('li').update(marker.Street + " " + marker.Housenumber));
		}
		
		if (marker.Postalcode) 
		{
			ul.insert(new Element('li').update(marker.Postalcode + " " + marker.City));
		}
		
		if (marker.Url) 
		{
			ul.insert(new Element('li').insert(new Element('a', 
			{
				href: (marker.Url.indexOf(':/') == -1) ? 'http://' + marker.Url : marker.Url
			}).update(marker.Url)));
		}
		
		var span = new Element('span', 
		{
			'class': 'map_window_controls'
		});
		
		if (this._options.markerShowZoomButton) 
		{
			span.insert(new Element('a', 
			{
				href: "javascript: return;"
			}).update("Zoom").observe('click', function(event)
			{
				scope.zoomToMarker(marker);
			}));
		}
		
		if (this._options.markerShowDirectionsButton) 
		{
			var routeUri = this._options.markerDirectionsUrl.replace("{0}", marker.Address());
			span.insert(new Element('a', 
			{
				href: routeUri
			}).update("Route"));
		}
		
		if (marker.DetailsUrl) 
		{
			span.insert(new Element('a', 
			{
				href: marker.DetailsUrl
			}).update("Meer info"));
		}
		
		div.insert(span);
		
		return div;
	},
	
	/**
	 * Zoom in on the Marker
	 * @param {Object} marker
	 */
	zoomToMarker: function(marker)
	{
		this._gmap.setCenter(new GLatLng(marker.Lat, marker.Lng), this._options.markerZoomInLevel);
	},
	
	
	bindMapEvents: function()
	{
		
	},
	
	/***
	 * Fix Internet Exploder memory leaks
	 */
	preventMemoryLeaks: function()
	{
		Event.observe(window, 'unload', function()
		{
			GUnload();
		});
	}
});

/**
 * Marker displayed on the Map
 */
var MapMarker = Class.create(
{
	Index: -1,
	Id: null,
	Name: null,
	PhoneNumber: null,
	Street: null,
	Housenumber: null,
	Postalcode: null,
	City: null,
	Url: null,
	DetailsUrl: null,
	Lat: null,
	Lng: null,
	
	/**
	 * Constructor
	 * @param int index
	 * @param string id
	 * @param string name
	 * @param string phoneNumber
	 * @param string street
	 * @param string housenumber
	 * @param string postalcode
	 * @param string city
	 * @param string url
	 * @param string detailsUrl
	 * @param string lat
	 * @param string lng
	 */
	initialize: function(index, id, name, phoneNumber, street, housenumber, postalcode, city, url, detailsUrl, lat, lng)
	{
		this.Index = index;
		this.Id = id;
		this.Name = name;
		this.PhoneNumber = phoneNumber;
		this.Street = street;
		this.Housenumber = housenumber;
		this.Postalcode = postalcode;
		this.City = city;
		this.Url = url;
		this.DetailsUrl = detailsUrl;
		this.Lat = lat;
		this.Lng = lng;
	},
	
	Address: function()
	{
		return this.Street + ' ' + this.Housenumber + ' ' + this.City;
	}
});

/**
 * Custom controls
 */
var ZoeknedZoomControl = Class.create(new GControl(),{
	initialize: function(zoomMap) {
		
		if(zoomMap == undefined) return null;
		
		var ctrlContainer = new Element('div');
		
		var btnZoomin = new Element('img', {'src': '/Templates/V3/Public/images/btn_map_zoomin.png', 'class' : 'map_button'});
		GEvent.addDomListener(btnZoomin, 'click', function() {
			zoomMap.zoomIn();
		});
		
		var btnZoomout = new Element('img', {'src': '/Templates/V3/Public/images/btn_map_zoomout.png', 'class' : 'map_button'});
		GEvent.addDomListener(btnZoomout, 'click', function() {
			zoomMap.zoomOut();
		});
		
		ctrlContainer.insert(btnZoomin);
		ctrlContainer.insert(btnZoomout);
		
		zoomMap.getContainer().appendChild(ctrlContainer);
		
		return ctrlContainer;	
	},
	getDefaultPosition: function() {
		return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(2, 2));
	}
});

var ZoeknedTypeControl = Class.create(new GControl(), {
	initialize: function(map) {
	
		if(map == undefined) return null;
		
		map.addMapType(G_NORMAL_MAP);
		map.addMapType(G_SATELLITE_MAP);
		map.addMapType(G_HYBRID_MAP);
		
		var container = new Element('div');
		
		var btnMapType = new Element('img', {'src': '/Templates/V3/Public/images/btn_map_maptype.png', 'class' : 'map_button'});
		
		GEvent.addDomListener(btnMapType, 'click', function() {
			var currentType = map.getCurrentMapType();
			var newType;
			
			switch(currentType)
			{
				case G_NORMAL_MAP:
					newType = G_SATELLITE_MAP;
					break;
				case G_SATELLITE_MAP:
					newType = G_HYBRID_MAP;
					break;
				case G_HYBRID_MAP:
					newType = G_NORMAL_MAP;
					break;
			}
			map.setMapType(newType);
		});
	
		container.insert(btnMapType);
		
		map.getContainer().appendChild(container);
		
		return container;	
	},
	getDefaultPosition: function() {
		return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(2, 2));
	}
});

/**
 * 
 */
var HtmlOverlay = Class.create(new GOverlay(), {
	
	_options: {},
	_defaultOptions: {
		map: null,
		point: null,
		box: null,
		position: null,
		width: null,
		height: null,
		ondraw: null,
		className: 'overlay'
	},
	initialize: function(options) {
		
		if(options.map == null) return;
		
		// Setting up original options with default options
		Object.extend(this._options, this._defaultOptions);
		Object.extend(this._options, options);
		
		this.ondraw = this._options.ondraw;
		this._options.box = new Element('div', {'class': this._options.className}).update(this.ondraw());
		this._options.map.getPane(G_MAP_MAP_PANE).appendChild(this._options.box);
	},
	remove: function() {
		this._options.box.parentNode.removeChild(this._options.box);
	},
	copy: function() {
		return new CustomOverlay(this._options);
	},
	redraw: function(force) {
	    if (force) {
	    	var pos = this._options.map.fromLatLngToDivPixel(this._options.position);
	    	var w = this._options.box.getWidth();
	    	var h = this._options.box.getHeight();
	    	
	    	this._options.box.setStyle({
	    			position: 'absolute',
	    			left: (pos.x - (w/2 + 4)) + 'px',
	    			top: (pos.y - (h)) + 'px'
	    	});
	    }
	},
	/**
	 * Overridden to provide custom HTML behavior
	 **/
	ondraw: function() {
	}
});

/** 
 * Clientside debug logging 
 * @param msg string
 */
function Log(msg)
{
	if (eval("typeof console == 'object'")) 
	{
		var id = (this.id) ? this.id : 'Onbekend';
		console.log(id + ' - ' + msg);
	}
};
