/*
 * $LastChangedDate: 2011-11-23 12:41:46 +0100 (Wed, 23 Nov 2011) $
 * $Rev: 48 $
 * $Author: hc $
 */

/****************************************************************************************************************/

if (Object.isUndefined(DRK)) { var DRK = {}; }
if (Object.isUndefined(DRK.Maps)) { DRK.Maps = {}; }

/****************************************************************************************************************/

DRK.Console = Class.create({
	_openWindow: function() {
		this._window = window.open('','logwindow');
	},
	log: function(str) {
		if (this._window === undefined) { this._openWindow(); }
		this._window.document.writeln('<div>'+str+'</div>');
	}
});

if ((typeof(console) == 'undefined') && (document.location.search.match(/debug/) !== null)) {
	console = new DRK.Console();
}

/****************************************************************************************************************/

DRK.Maps = Class.create({
	_log: function(str) {
		try {
			console.log(this.toString()+': '+str);
		} catch(e) {
		}
	}
});

/****************************************************************************************************************/

DRK.Maps.Manager = Class.create(DRK.Maps,{
	initialize: function() {
		this._maps = new Hash();
		this._map = undefined;
		this._buildView = undefined;
		this._idprefix = 'drk_maps_';

		this._log('DRK.Maps [$Rev: 48 $]');
	},
	toString: function() {
		return 'DRK.Maps.Manager';
	},
	getIDPrefix: function() {
		return this._idprefix;
	},
	addMap: function(id, options) {
		this._map = new DRK.Maps.Map(this, id, options);
		this._maps.set(id, this._map);
		this._log('Added '+this._map.toString());
		return this._map;
	},
	// For debugging purposes:
	getMap: function(id) {
		return this._maps.get(id);
	},
	addView: function(id, title, options) {
		if (this._map === undefined) {
			throw 'Add a map before adding a view';
		}
		this._view = this._map.addView(id, title, options);
		return this._view;
	},
	addDataSource: function(id, title, options) {
		if (this._view === undefined) {
			throw 'Add a view before adding dataSource';
		}
		return this._view.addDataSource(id, title, options);
	},
	init: function() {
		this._mapsToGo = this._maps.size();
		this._maps.each(function(pair){
			var map = pair.value;
			map.init(this._onMapInit.bind(this));
		},this);
	},
	_onMapInit: function() {
		// Keep count
		this._mapsToGo--;
		if (this._mapsToGo) { return; }
		// We're done
		this._onMapsInit();
	},
	_onMapsInit: function() {
		this._log('All maps initialized');
		document.observe('custom:contentrefreshed',this._onContentRefreshed.bindAsEventListener(this));
	},
	_onContentRefreshed: function(evt) {
		this.resize(evt);
	},
	fireEvent: function(name, memo) {
		var fullEventName = 'drk_maps_'+name;
		this._log('Firing document event: '+fullEventName+' ('+Object.toJSON(memo)+')');
		document.fire(fullEventName, memo);
	},
	resize: function(e) {
		var memo = (e)?e.memo:undefined;
		
		if ( memo === undefined )
		{
			this._maps.each(function(pair){
				var map = pair.value;

				this._log('Resize ' + map.toString());
				map.resize();
			},this);
		}
		else
		{
			e.memo.mapIds.each(function(mapId){
				var map = this.getMap(mapId);
				this._log('Resize ' + map.toString());
				map.resize();
			},this);
		}
	}
});

/****************************************************************************************************************/

DRK.Maps.Map = Class.create(DRK.Maps,{
	initialize: function(manager, id, options) {
		this._manager = manager;
		this._id = id;
		this._options = Object.extend({
			'hideViews':			false,
			'hideViewsIfSingle':	true,
			'showSearchBox':		false,
			'searchBox': {
				'searchLabel':		'Hjælp til i nærheden af dig',
				'searchTip':		'Søg på adresse, by eller postnr.',
				'searchButton':		'SØG'
			},	
			'markup':			'<div class="drk_maps_map_tabs"><ul id="#{_tabsid}"></ul></div>'+
								'<div class="drk_maps_map_search"></div>'+
								'<div class="drk_maps_map_body"></div>',
			'actionClassName':	'drk_maps_action',
			'delayedInit': false
		}, options || {});

		this._view = undefined;
		this._views = new Hash();

		this._defaultView = undefined;
		this._selectedView = undefined;

		this._idprefix = this._manager.getIDPrefix()+'map_'+this._id+'_';

		this._tabsid = this._idprefix+'tabs';

		// Allow for undefined (anonymous) ids
		this._anonymousId = 1;
		
		this._mapInitialized = false;
	},
	_search: function(str) {
		// Postnummer?
		if (str.match(/^\d{4}$/) !== null) {
			str += ', Denmark';
		}

		this._log('Searching for "'+str+'" ..');
		var gc = new google.maps.Geocoder();
		gc.geocode({
			'address':	str,
			'latLng':	this._googleMap.getCenter()
		},this._onSearchResultsReady.bind(this));
	},
	_onSearchResultsReady: function(results) {
		if (results.length == 0) {
			this._log('No search matches');
			return;
		}
		if (results.length > 1) {
			this._log('Search gave more than one result');
		}

		// Pick first result
		var result = results[0];

		var location = result.geometry.location;
		var center = new google.maps.LatLng(location.lat(), location.lng());
		this._googleMap.setCenter(center);
		this._googleMap.fitBounds(result.geometry.viewport);
	},
	toString: function() {
		return 'DRK.Maps.Map["'+this._id+'"]';
	},
	getIDPrefix: function() {
		return this._idprefix;
	},
	addView: function(id, title, options) {
		this._view = new DRK.Maps.View(this, id, title, options);
		this._views.set(id, this._view);
		if (this._defaultView === undefined) {
			this._defaultView = this._view;
		}
		this._log('Added '+this._view.toString());
		return this._view;
	},
	// For debugging purposes:
	getView: function(id) {
		if (id === undefined) {
			return this._selectedView;
		} else {
			return this._views.get(id);
		}
	},
	init: function(callback) {
		this._initCallback = callback;
		this._log('Init');

		// This is used for data source selection
		this._viewControllerContainer = new Element('DIV');

		// Update element markup
		this._element = $(this._id);
		if (!this._element) {
			throw "Map element not found: "+id;
		}
		this._element
			.addClassName('drk_maps_map')
			.update(new Template(this._options.markup).evaluate(this));

		// Defer to allow DOM update
		(function(){

			// Get the search container element
			this._searchElement = this._element.down('.drk_maps_map_search');
			if (!this._searchElement) {
				this._log('Search container element not found');
			} else {
				var searchBox = new DRK.Maps.SearchBox(
					this._searchElement,
					this._googleMap,
					this._options.searchBox,
					this._search.bind(this)
				);
				if (this._options.showSearchBox) {
					searchBox.show();
				}
			}

			// Get the tabs container element
			this._tabsElement = $(this._tabsid);
			if (!this._tabsElement) {
				throw "Unable to find tabs element: "+this._tabsid;
			}

			// Hide view tabs?
			if (this._options.hideViews || (this._options.hideViewsIfSingle && (this._views.size() <= 1))) {
				this._tabsElement.hide();
			}

			// Iterate through views
			this._viewsToGo = this._views.size();
			if (this._viewsToGo) {
				this._views.each(function(pair){
					var view = pair.value;
					view.init(this._onViewInitialized.bind(this));
				},this);
			} else {
				this._onViewsInitialized();
			}

			// Observe clicks
			this._element.observe('click',this._onClick.bindAsEventListener(this));

			// Load Google Map
			// possibly delay initialization (used for maps in hidden elements at page load)
			if ( !this._options.delayedInit )
			{
				this._initGoogleMap();
			}
			
		}).bind(this).defer();	
	},
	_onViewInitialized: function() {
		// Keep count
		this._viewsToGo--;
		if (this._viewsToGo) { return; }
		// We're done
		this._onViewsInitialized();
	},
	_onViewsInitialized: function() {
		this._log('All views initialized');

		// Select a default view
		if (this._defaultView) {
			this._selectView(this._defaultView);
		}

		// Optional post-init() callback
		if (this._initCallback !== undefined) {
			this._initCallback();
		}
	},
	addViewTab: function(content) {
		return this._tabsElement.insert(content);
	},
	addViewController: function(content) {
		return this._viewControllerContainer.insert(content);
	},
	getPosition: function() {
		var p = {
			'center': this._googleMap.getCenter(),
			'zoom': this._googleMap.getZoom()
		};
		this._log($H(p).inspect());
		return p;
	},
	setPosition: function(p) {
		if (p !== undefined) {
			this._lastSetPosition = p;
			this._log('Setting map center: '+p.center+' zoom: '+p.zoom);
			try {
				this._googleMap.setCenter(p.center);
				this._googleMap.setZoom(p.zoom);
			} catch(ex) {
				this._log('Failed to move map');
			}
		} else if (this._lastSetPosition !== undefined) {
			this.setPosition(this._lastSetPosition);
		}
	},
	_initGoogleMap: function() {
		this._log('Initialize google map');
		var mapElement = this._element.down('.drk_maps_map_body');
		var options = {
			'mapTypeId': google.maps.MapTypeId.ROADMAP,
			'mapTypeControl': false,
			'panControl': false,
			'streetViewControl': false,
			'scrollwheel': 	false
		};
		this._googleMap = new google.maps.Map(mapElement, options);

		// Add custom dataSources (datasources) control
		this._googleMap.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(this._viewControllerContainer);

		google.maps.event.addListener(this._googleMap, 'click', this._onMapClick.bindAsEventListener(this));
		
		this._mapInitialized = true;
	},
	_onMapClick: function(e) {
		this._handleEvent('mapclick', e.latLng);
	},
	_onClick: function(e) {
		var element = Event.element(e);
		this._log('_onClick(): '+Object.inspect(element));

		// Look for action element
		if (!element.hasClassName(this._options.actionClassName)) {
			if (!(element = element.up('.'+this._options.actionClassName))) {
				return;
			}
		}
		Event.stop(e);

		// Look for parameter/data
		element.classNames().each(function(className){
			var match = className.match('^'+this._options.actionClassName+'_(.*?)(_(.*)|)$');
			if (match !== null) {
				this._handleEvent(match[1], match[3], element);
			}
		},this);
	},
	_handleEvent: function(name, parm, element) {
		if (!this.eventHandler(name, parm, element)) {
			this._log('Unhandled event: name='+name+' parm='+parm+' element='+(element ? element.inspect() : 'undefined'));
		}
	},
	eventHandler: function(name, parm, element) {
		switch (name) {
			case 'viewbutton':
				return this._onViewButtonClicked(parm);
			break;

			default:
				if (this._selectedView) {
					return this._selectedView.eventHandler(name, parm, element);
				}
			break;
		}
		return false;
	},
	_onViewButtonClicked: function(id) {
		var view = this._views.get(id);
		if (!view) {
			throw 'No such view: '+id;
		}
		this._selectView(view);
		return true;
	},
	_selectView: function(view) {
		if (this._selectedView !== undefined) {
			this._selectedView.hide();
		}
		this._selectedView = view;
		this._selectedView.show();
	},
	getGoogleMap: function() {
		return this._googleMap;
	},
	fireEvent: function(name, memo) {
		this._manager.fireEvent(name, Object.extend({
			'map': this._id
		},memo || {}));
	},
	resize: function() {
		if ( !this._mapInitialized )
		{
			this._log('Delayed initialization');
			this._initGoogleMap();
			this._selectedView._selectDataSource(this._selectedView._dataSources.values().first());
		}
		
		google.maps.event.trigger(this._googleMap, 'resize')
		this.setPosition();
	}
});

/****************************************************************************************************************/

DRK.Maps.SearchBox = Class.create(DRK.Maps, {
	initialize: function(element, map, options, searchCallback) {
		this._element = element;
		this._options = Object.extend({
			'searchTipClassName': 'input_tip'
		},options || {});
		this._searchCallback = searchCallback;
		this._render();
	},
	show: function() {
		this._element.show();
	},
	hide: function() {
		this._element.hide();
	},
	_render: function(element) {
		this._log('Rendering search form..');

		this._element
			.hide()
			.insert(new Element('div')
				.addClassName('drk_maps_map_search_label')
				.update(this._options.searchLabel)
			)
			.insert(new Element('div')
				.addClassName('drk_maps_map_search_form')
				.insert(this._form = new Element('form')
					.insert(this._input = new Element('input',{'type':'text','name':'queryText'})
						// Copied from event handlers below
						.addClassName('input_text')
						.addClassName(this._options.searchTipClassName)
						.setValue(this._options.searchTip)
					)	
					.insert(this._button = new Element('input',{'type':'submit','name':'submitButton'})
						.addClassName('input_submit')
						.setValue(this._options.searchButton)
					)
				)
			)
			.insert(new Element('div')
				.setStyle({'clear':'both'})
			);

		this._form.observe('submit', (function(event){
			Event.stop(event);
			
			var searchterm = this._input.getValue().toLowerCase().strip();
			if ( !searchterm.endsWith('danmark') || !searchterm.endsWith('denmark') )
			{
				searchterm += ', danmark';
			}
			
			this._searchCallback(searchterm);
		}).bindAsEventListener(this));

		this._input.observe('focus', (function(event){
			var element = Event.element(event);
			if (element.hasClassName(this._options.searchTipClassName)) {
				element
					.removeClassName(this._options.searchTipClassName)
					.setValue('');
			}
		}).bindAsEventListener(this));

		this._input.observe('blur', (function(event){
			var element = Event.element(event);
			if (element.getValue() == '') {
				element
					.addClassName(this._options.searchTipClassName)
					.setValue(this._options.searchTip);
			}
		}).bindAsEventListener(this));
	}
});	

/****************************************************************************************************************/

DRK.Maps.View = Class.create(DRK.Maps,{
	initialize: function(map, id, title, options) {
		this._map = map;
		this._id = id;
		this._title = title;
		this._options = Object.extend({
			'clientGeoLocation':		true,
			'hideDataSources':			false,
			'hideDataSourcesIfSingle':	true,
			'viewTabMarkup':					'<li id="#{_viewTabId}" class="drk_maps_view_tab drk_maps_action drk_maps_action_viewbutton_#{_id}"><a href="#">#{_title}</a></li>',
			'dataSourceContainerMarkup':		'<ul id="#{_dataSourceContainerId}"></ul>',
			'showElementsSelector':			''
		},options || {});

		this._dataSource = undefined;
		this._dataSources = new Hash();
		this._defaultDataSource = undefined;
		this._selectedDataSource = undefined;

		this._idprefix = this._map.getIDPrefix()+'view_'+id+'_';

		// View tab (Denmark, World, etc.)
		this._viewTabId = this._idprefix+'viewtab';
		this._viewTabElement = undefined;

		// DataSource container (Butik, Genbrugscontainer etc.)
		this._dataSourceContainerId = this._idprefix+'datasourcecontainer';
		this._dataSourceContainerElement = undefined;

		this._visible = false;
	},
	toString: function() {
		return 'DRK.Maps.View["'+this._id+'"]';
	},
	getIDPrefix: function() {
		return this._idprefix;
	},
	addDataSource: function(id, title, options) {
		this._dataSource = new DRK.Maps.DataSource(this, id, title, options);
		this._dataSources.set(id, this._dataSource);
		if (this._defaultDataSource === undefined) {
			this._defaultDataSource = this._dataSource;
		}
		this._log('Added '+this._dataSource.toString());
		return this._dataSource;
	},
	// For debugging purposes:
	getDataSource: function(id) {
		return this._dataSources.get(id);
	},
	init: function(callback) {
		this._initCallback = callback;
		this._log('Init');

		this._viewController = new Element('div')
			.addClassName('drk_maps_datasources_control')
			.update(new Template(this._options.dataSourceContainerMarkup).evaluate(this))
			.hide();

		var viewControllerContainer = this._map.addViewController(this._viewController);
		var tabContainer = this._map.addViewTab(new Template(this._options.viewTabMarkup).evaluate(this));

		// Defer to allow DOM update
		(function(){

			this._tabElement = $(this._viewTabId);
			if (!this._tabElement) {
				throw 'Tab element not found: '+this._viewTabId;
			}

			this._dataSourceContainerElement = viewControllerContainer.down('#'+this._dataSourceContainerId);
			if (!this._dataSourceContainerElement) {
				throw 'DataSource Container element not found: '+this._dataSourceContainerId;
			}

			this._dataSourcesToGo = this._dataSources.size();
			if (this._dataSourcesToGo) {
				this._dataSources.each(function(pair){
					var dataSource = pair.value;
					dataSource.init(this._onDataSourceInitialized.bind(this));
				},this);
			} else {
				this._onDataSourcesInitialized();
			}

		}).bind(this).defer();
	},
	_onDataSourceInitialized: function() {
		// Keep count
		this._dataSourcesToGo--;
		if (this._dataSourcesToGo) { return; }
		// We're done
		this._onDataSourcesInitialized();
	},
	_onDataSourcesInitialized: function() {
		this._log('All DataSources initialized');

		// Optional post-init() callback
		if (this._initCallback !== undefined) {
			this._initCallback();
		}
	},
	addDataSourceButton: function(content) {
		return this._dataSourceContainerElement.insert(content);
	},
	getGoogleMap: function() {
		return this._map.getGoogleMap();
	},
	setPosition: function(latitude, longitude, zoom) {
		this._position = {
			'center': new google.maps.LatLng(latitude, longitude),
			'zoom': zoom
		};
		if (this._visible) {
			this._map.setPosition(this._position);
		}
	},
	hide: function() {
		if (!this._visible) { return; }
		this._visible = false;

		// Unmark selected tab
		this._tabElement.removeClassName('selected');

		// Hide controller
		this._viewController.hide();

		// Save map position
		this._position = this._map.getPosition();

		// Hide selected datasource
		if (this._selectedDataSource !== undefined) {
			this._selectedDataSource.hide();
		}

		// Hide any external elements
//		$$(this._options.showElementsSelector).invoke('hide');

		this.fireEvent('view_hidden');
	},
	show: function() {
		if (this._visible) { return; }
		this._visible = true;

		// Mark selected tab
		this._tabElement.addClassName('selected');

		// Hide datasource buttons?

		// Show controller (conditional)
		if (!this._options.hideDataSources && (!this._options.hideDataSourcesIfSingle || (this._dataSources.size() > 1))) {
			this._viewController.show();
		}

		if (this._position !== undefined) {
			// Restore previous position
			this._log('Using predefined position');
			this._map.setPosition(this._position);
		} else {
			// Set default position
			this._log('Using default position');
			this._map.setPosition({
				'center': new google.maps.LatLng(-25.363882,131.044922),
				'zoom': 5
			});
		}

		// Client-provided location
		if (this._options.clientGeoLocation) {
			if (google.loader.ClientLocation) {
				var clientLat = google.loader.ClientLocation.latitude;
				var clientLng = google.loader.ClientLocation.longitude;
				this._map.setPosition({
					'center': new google.maps.LatLng(clientLat, clientLng),
					'zoom': 10
				});
				this._log('Google Maps API provided location: '+clientLat+','+clientLng);
			} else if (navigator.geolocation) {
				this._log('Requesting location via browser..');
				try {
					navigator.geolocation.getCurrentPosition(
						// Success callback
						(function(position){
							var clientLat = position.coords.latitude;
							var clientLng = position.coords.longitude;
							this._map.setPosition({
								'center': new google.maps.LatLng(clientLat, clientLng),
								'zoom': 10
							});
							this._log('Browser provided location: '+clientLat+','+clientLng);
						}).bind(this),
						// Error callback
						(function(error) {
							this._log('Browser failed to provide location: '+error.message+' ('+error.code+')');
						}).bind(this),
						// Parameters
						{
							'timeout': 1500 // ms
						}
					);
				} catch(ex) {
					this._log('navigator.geolocation.getCurrentPosition() threw exception: '+ex);
				}
			}
		}

		// Select a default datasource
		if (this._selectedDataSource !== undefined) {
			this._selectedDataSource.show();
		} else if (this._defaultDataSource !== undefined) {
			this._selectDataSource(this._defaultDataSource);
		}

		// Hide any external elements
	//	$$(this._options.showElementsSelector).invoke('show');

		this.fireEvent('view_shown');
	},
	eventHandler: function(name, parm, element) {
		switch (name) {
			case 'datasourcebutton':
				return this._onDataSourceButtonClicked(parm);
			break;

			default:
				if (this._selectedDataSource) {
					return this._selectedDataSource.eventHandler(name, parm, element);
				}
			break;
		}
		return false;
	},
	_onDataSourceButtonClicked: function(id) {
		var datasource = this._dataSources.get(id);
		if (!datasource) {
			throw 'No such datasource: '+id;
		}
		this._selectDataSource(datasource);
		return true;
	},
	_selectDataSource: function(dataSource) {
		this._log('Selecting data source: '+dataSource);
		if (this._selectedDataSource !== undefined) {
			this._selectedDataSource.hide();
		}
		this._selectedDataSource = dataSource;
		this._selectedDataSource.show();
	},
	fireEvent: function(name, memo) {
		this._map.fireEvent(name, Object.extend({
			'view': this._id
		},memo || {}));
	}
});

/****************************************************************************************************************/

DRK.Maps.DataSource = Class.create(DRK.Maps,{
	initialize: function(view, id, title, options) {
		this._view = view;
		this._id = id;
		this._title = title;
		this._options = Object.extend({
			'dataSourceButtonMarkup':	'<li id="#{_controllerid}" class="drk_maps_view_tab drk_maps_action drk_maps_action_datasourcebutton_#{_id}"><a href="#">#{_title}</a></li>',
			'infoWindowTabMarkup':		'<li class="#{Class}"><a href="#">#{Title}</a></li>',
			'infoWindowBodyMarkup': 	'<li class="#{Class}">#{Content}</li>',
			'infoWindowMarkup':
				'<div class="map_infowindow_tabs"><ul>#{tabMarkup}</ul></div>'+
				'<div class="map_infowindow_contents"><ul>#{bodyMarkup}</ul></div>',
			'infoWindowTabsSelector':	'.map_infowindow_tabs',
			'infoWindowContentSelector':	'.map_infowindow_body',
			'infoWindowWidth':			300,
			'useMarkerClusterer': 		false,

			/* Marker Image (Icon) */
			'markerImageUrl':		'/files/system/apps/DRK/Maps/css/images/box/rkmarker.png',
			'markerImageWidth':		21,
			'markerImageHeight':	21,
			'markerImageXOffset':	0,
			'markerImageYOffset':	21
		}, options || {});

		this._idprefix = this._view.getIDPrefix()+'datasources_'+id+'_';
		this._controllerid = this._idprefix+'controller';

		this._points = undefined; // Not attempted to load yet

		// Point with visible info window
		this._poppedPoint = undefined;
	},
	toString: function() {
		return 'DRK.Maps.DataSource["'+this._title+'"]';
	},
	init: function(callback) {
		this._log('Init');
		var container = this._view.addDataSourceButton(new Template(this._options.dataSourceButtonMarkup).evaluate(this));

		// Defer to allow DOM update
		(function(){
			this._controllerElement = container.down('#'+this._controllerid);
			if (!this._controllerElement) {
				throw 'Controller element not found: '+this._controllerid;
			}

			// Optional post-init() callback
			if (callback !== undefined) {
				callback();
			}

		}).bind(this).defer();	
	},
	hide: function() {
		if ( !this._visible || !this._view._map._mapInitialized ) { return; }
		this._visible = false;
		this._controllerElement.removeClassName('selected');

		// Clear cluster
		this._markerCluster.clearMarkers();

		// Iterate through points
		this._points.each(function(point){
			// Close any popped window
			if (point.infowindow !== undefined) {
				//point.infowindow.close();
				point.infowindow.hide();
			}
			// Remove marker from any map/cluster
			point.marker.setMap(null);
		},this);

		this.fireEvent('datasource_hidden');
	},
	show: function() {
		if ( this._visible || !this._view._map._mapInitialized ) { return; }
		this._visible = true;

		this._controllerElement.addClassName('selected');

		// Show points (load first if not available)
		if (this._points === undefined) {
			if (typeof(DRK.MapService) == 'undefined') {
				throw "This app needs DRK.MapService unless you load points manually.";
			}
			this._points = [];
			this._requestMapPoints();
		} else {
			this._showPoints();
		}
	},
	
	_requestMapPoints: function()
	{
		// DRK.MapService.GetMapPoints(this._id,this._onPointsAvailable.bind(this));
		DRK.MapService.GetChunkedMapPoints(this._id, this._points.size(),this._onPointsAvailable.bind(this));
	},
	
	_onPointsAvailable: function(data) {
		data.d.mapPoints.each(function(o){
			o.Tabs.each(function(t){
				t.Content = t.Content.replace(/\[\[/g, '<').replace(/\]\]/g, '>');
			},this);
			this.addMarker( o.Lat, o.Lng, o.Title, o.Tabs);
		},this);
		
		if ( data.d.moreData )
		{
			(function() {
				this._requestMapPoints();
			}).bind(this).defer();
		}
		else
		{
			try
			{
				this._showPoints();
			}
			catch ( ex )
			{
				this._log('Caught exception ' + ex);
			}
		}
	},
	
	_showPoints: function() {
		if (!this._points.size()) {
			this._log('Datasource is empty - not showing any points');
		} else {
			this._log('Showing with '+this._points.size()+' point(s)');

			// Initialize marker cluster?
			if (this._markerCluster === undefined) {
				// Sanity check
				if (typeof(MarkerClusterer) == 'undefined') {
					throw "This app needs MarkerClusterer (http://gmaps-utility-library.googlecode.com/svn/trunk/markerclusterer/) to work.";
				}
				this._markerCluster = new MarkerClusterer(this._view.getGoogleMap(), [], {});
			}

			// Iterate through points
			var markers = [];
			this._points.each(function(point){
				// Instantiate marker?
				if (point.marker === undefined) {
					point.marker = new google.maps.Marker({
						'position': point.position,
						'title': point.title,
						'icon': new google.maps.MarkerImage(
							this._options.markerImageUrl,
							new google.maps.Size(this._options.markerImageWidth, this._options.markerImageHeight),
							new google.maps.Point(0,0),
							new google.maps.Point(this._options.markerImageXOffset, this._options.markerImageYOffset)
						)	
					});
					try {
						google.maps.event.addListener(point.marker, 'click', this._onMarkerClick.bindAsEventListener(this, point));
					} catch(ex) {
						this._log('ERROR: Failed to listen on Google Maps clicks');
					}
				}
				markers.push(point.marker);
			},this);

			if ( this._options.useMarkerClusterer )
			{
				// Add to cluster
				this._markerCluster.addMarkers(markers);
			}
			else
			{
				markers.each(function(marker) {
					marker.setMap(this._view.getGoogleMap());
				}, this);
			}
		}

		this.fireEvent('datasource_shown');
	},
	_hidePoppedPoint: function() {
		if (this._poppedPoint === undefined) { return; }
		this._log('Closing open InfoWindow..');
		this._poppedPoint.infowindow.hide();
		this._log('Closed');
		this._poppedPoint = undefined;
	},
	_getContentFromTabs: function(tabs) {
		var i = 0;
		var tabMarkup = '';
		var bodyMarkup = '';
		var tabTemplate = new Template(this._options.infoWindowTabMarkup);
		var bodyTemplate = new Template(this._options.infoWindowBodyMarkup);
		tabs.each(function(tab){

			// Tweaks to tab.Contents
			tab.Content = tab.Content.replace(/(<a href=".*?")(>)/g, '$1 target="_blank"$2');


			if (i++==0) { tab.Class = 'selected'; }
			tabMarkup += tabTemplate.evaluate(tab);
			bodyMarkup += bodyTemplate.evaluate(tab);
		},this);

		return new Template(this._options.infoWindowMarkup)
			.evaluate({
				'tabMarkup': tabMarkup,
				'bodyMarkup': bodyMarkup
			});
	},
	_getNewGoogleInfoBox: function(options) {
		return new DRK.Maps.GoogleInfoBox(options);
	},
	_addInfoWindowToPoint: function(point) {
		// Convert tabs to content?
		if (typeof(point.content) == 'object') {
			point.content = this._getContentFromTabs(point.content);
			point.tabbed = true;
		}
		point.infowindow = this._getNewGoogleInfoBox({
			'width':		this._options.infoWindowWidth,
			'map':			this._view.getGoogleMap(),
			'position':		point.marker.getPosition(),
			'content':		point.content,
			/* For tabs */
			'infoWindowTabsSelector':		this._options.infoWindowTabsSelector,
			'infoWindowContentSelector':	this._options.infoWindowContentSelector,
			/* Callback for clicking close icon */
			'onClose':		this._hidePoppedPoint.bind(this)
		});
	},
	_onMarkerClick: function(e, point) {
		if (point === this._poppedPoint) { return; }

		this._hidePoppedPoint();
		if (point.infowindow === undefined) {
			this._addInfoWindowToPoint(point);
		}

		(function(){
			point.infowindow.show();
		}).bind(this).defer();
		this._poppedPoint = point;
	},
	_onInfoWindowClick: function(e, point) {
		this._log('InfoWindow clicked');
	},
	addMarker: function(latitude, longitude, title, content) {
		if (this._points === undefined) {
			this._log('Adding points manually - will not load asynchronously.');
			this._points = [];
		}

		//this._log('Added marker #'+this._points.size()+': "'+title+'"');
		this._points.push({
			'position': new google.maps.LatLng(latitude, longitude),
			'title': title,
			'content': content,
			'visible': false
		});	

		// Allow chaining
		return this;
	},
	eventHandler: function(name, parm, element) {
		switch (name) {
			case 'mapclick':
				this._log('Map click at: '+parm);
				this._hidePoppedPoint();
				return true;
			break;
		}
		return false;
	},
	fireEvent: function(name, memo) {
		this._view.fireEvent(name, Object.extend({
			'dataSource': this._id
		},memo || {}));
	}
});

/****************************************************************************************************************/

DRK.Maps.GoogleInfoBox = function(options) {
	this._options = Object.extend({
		'map':				undefined,
		'position':			undefined,
		'width':			300,
		'height':			191,
		'offsetX':			40,
		'offsetY':			-100,
		'paddingLeft': 		0,
		'paddingRight':		150,	// Leave room for selectors
		'paddingTop':		0,
		'paddingBottom':	30,		// Leave room for copyright notice
		'closeClassName':	'drk_maps_googleinfobox_close',
		'innerFrameClassName':	'drk_maps_googleinfobox_inner_frame',
		'innerContentClassName':	'drk_maps_googleinfobox_inner_content',
		'outerClassName':	'drk_maps_googleinfobox',
		'onClose':	undefined,
		'infoWindowHideTabs':			false,
		'infoWindowHideTabsIfSingle':	false,
		'classNamePrefix':	'drk_maps_googleinfobox2_',
		'clickableContainerSelector':   '.map_infowindow_contents'
	},options || {});
	this.div_ = null;
	this.setMap(this._options.map);

	google.maps.event.addListener(this, 'click', this._onClick.bind(this));
}

DRK.Maps.GoogleInfoBox.prototype = new google.maps.OverlayView();

DRK.Maps.GoogleInfoBox.prototype._log = function(str) {
	try {
		console.log('DRK.Maps.GoogleInfoBox: '+str);
	} catch(e) {
	}
}

DRK.Maps.GoogleInfoBox.prototype.onAdd = function() {
	this._log('onAdd()');

	var contentDiv = new Element('div', {'class': this._options.innerContentClassName})
		.update(this._options.content);

	var div;

	var prefix = this._options.classNamePrefix;
	var container = new Element('div')
		.addClassName(prefix+'container')
		.insert(new Element('div')
			.addClassName(prefix+'row_top')
			.insert(new Element('div')
				.addClassName(prefix+'col_middle')
				.wrap(new Element('div')
					.addClassName(prefix+'col_right')
				)
				.wrap(new Element('div')
					.addClassName(prefix+'col_left')
				)
			)
		)
		.insert(new Element('div')
			.addClassName(prefix+'row_middle')
			.insert(new Element('div')
				.insert(contentDiv)
				.addClassName(prefix+'col_middle')
				.wrap(new Element('div')
					.addClassName(prefix+'col_right')
				)
				.wrap(new Element('div')
					.addClassName(prefix+'col_left')
				)
			)
		)
		.insert(new Element('div')
			.addClassName(prefix+'row_bottom')
			.insert(new Element('div')
				.addClassName(prefix+'col_middle')
				.wrap(new Element('div')
					.addClassName(prefix+'col_right')
				)
				.wrap(new Element('div')
					.addClassName(prefix+'col_left')
				)
			)
		);

	div = new Element('div')
		.setStyle({
			'position': 'absolute'
		})
		.insert(new Element('div', {'class': prefix+'arrow'}))
		.insert(container
			.insert({'top':new Element('div', {'class': prefix+'close'})})
		)
		.hide();

	this.div_ = div;

	var panes = this.getPanes();
	this._targetPane = panes.floatPane;
	this._targetPane.appendChild(div);

	this.show();

	this._onClickBound = this._onClick.bind(this);
	google.maps.event.addDomListener(this.div_, 'mousedown', this._onClickBound);
	google.maps.event.addDomListener(this.div_, 'click', this._onClickBound);
	google.maps.event.addDomListener(this.div_, 'dblclick', this._onClickBound);
	google.maps.event.addDomListener(this.div_, 'contextmenu', this._onClickBound);

	// Show tabs?
	(function(){
		var tabsContainer = this.div_.down(this._options.infoWindowTabsSelector);
		if (!tabsContainer) {
			this._log('Unable to select tabs container by selector: '+this._options.infoWindowTabsSelector);
		} else {
			this.div_.addClassName( tabsContainer.select('li').size() > 1 ? 'multitab' : 'singletab');
		}
	}).bind(this).defer();
}

DRK.Maps.GoogleInfoBox.prototype._onClick = function(event) {
	var element = Event.element(event);

	// Ignore mousedown events
	if (event.type == 'mousedown') {
		// Prevent dragging
		Event.stop(event);
		return;
	}

	// Ignore mousedown events
	if (event.type == 'mousedown') {
		// Prevent dragging
		Event.stop(event);
		return;
	}

	// Real clickable content elements
	if ((element.tagName == 'A') && (element.up(this._options.clickableContainerSelector) !== undefined)) {
		// TODO: Fix event bubbling, so the browser actually handles link clicks
		// The problem seems to be that it's impossible to prevent Google Maps from intercepting before
		// the builtin events.
		window.open(element.href, element.target, '');
		Event.stop(event);
		return true;
		/*
		this._log('Ignoring content link click');
		return false;
		*/
	}

	Event.stop(event);

	// Close click?
	if ((element.hasClassName(this._options.closeClassName) || (element.hasClassName(this._options.classNamePrefix+'close'))) && (this._options.onClose !== undefined)) {
		this._options.onClose(this);
		return true;
	}

	// Tab click?
	if (element.up(this._options.infoWindowTabsSelector)) {
		if ((element.tagName != 'LI') && !(element = element.up('li'))) {
			// This could happen if the UL body is clicked outside of an LI
			// throw 'Tab click without LI parent';
			return true;
		}
		this._onTabClick(element);
		return true;
	}

	this._log('Unhandled event "'+event.type+'" on '+Object.inspect(element));
}

DRK.Maps.GoogleInfoBox.prototype._onTabClick = function(element) {
	// Clever trick to determine tab number
	var tabNum = element.previousSiblings('li').size();
	this._log('Tab #'+tabNum+' clicked');

	// Select tab
	element.siblings().invoke('removeClassName','selected');
	element.addClassName('selected');

	try {
		// Select body
		var bodyList = element
			.up(this._options.infoWindowTabsSelector)
			.siblings(this._options.infoWindowContentSelector).first()
			.down('ul');
		var bodyElement = bodyList.childElements('li')[tabNum];

		bodyElement.siblings().invoke('removeClassName','selected');
		bodyElement.addClassName('selected');
	} catch(e) {
		this._log('Unable to highlight tab body element: '+e);
	}
}

DRK.Maps.GoogleInfoBox.prototype.draw = function() {
	var overlayProjection = this.getProjection();
	var pixPosition = overlayProjection.fromLatLngToDivPixel(this._options.position);
	this.div_.setStyle({
		'left':		(pixPosition.x + this._options.offsetX)+'px',
		'top':		(pixPosition.y + this._options.offsetY)+'px',
		'width':	this._options.width+'px',
		'height':	this._options.height+'px'
	});

	if (this.div_.parentNode != this._targetPane) {
		this._log('element moved!!');
	}
}

DRK.Maps.GoogleInfoBox.prototype.onRemove = function() {
	this.div_.parentNode.removeChild(this.div_);
	this.div_ = null;
}

DRK.Maps.GoogleInfoBox.prototype.hide = function() {
	this.div_.hide();
}

DRK.Maps.GoogleInfoBox.prototype.show = function() {
	this.panMap();
	this.div_.show();
}

DRK.Maps.GoogleInfoBox.prototype.panMap = function() {
	// Get map bounds
	var bounds = this._options.map.getBounds();
	if (!bounds) {
		this._log('Unable to get map bounds');
		return false;
	}

	// Get degrees per pixel
	var mapElement = this._options.map.getDiv();
	var mapWidth = mapElement.offsetWidth;
	var mapHeight = mapElement.offsetHeight;
	var boundsSpan = bounds.toSpan();
	var longSpan = boundsSpan.lng();
	var latSpan = boundsSpan.lat();
	var degPixelX = longSpan / mapWidth;
	var degPixelY = latSpan / mapHeight;

	// The bounds of the map
	var mapBounds = [
		bounds.getSouthWest().lng(),
		bounds.getNorthEast().lng(),
		bounds.getNorthEast().lat(),
		bounds.getSouthWest().lat()
	];

	// The bounds of the infobox
	var infoboxBounds = [
		this._options.position.lng() + (this._options.offsetX - this._options.paddingLeft) * degPixelX,
		this._options.position.lng() + (this._options.offsetX + this._options.paddingRight + this._options.width) * degPixelX,
		this._options.position.lat() - (this._options.offsetY - this._options.paddingTop) * degPixelY,
		this._options.position.lat() - (this._options.offsetY + this._options.paddingBottom + this._options.height) * degPixelY
	];

	this._log('Infobox south lat: '+infoboxBounds[3]);
	this._log('Map south lat: '+mapBounds[3]);

	// Calculate center shift
	var shiftLng =
		(infoboxBounds[0] < mapBounds[0] ? mapBounds[0] - infoboxBounds[0] : 0) +
		(infoboxBounds[1] > mapBounds[1] ? mapBounds[1] - infoboxBounds[1] : 0);

	var shiftLat =
		(infoboxBounds[2] > mapBounds[2] ? mapBounds[2] - infoboxBounds[2] : 0) +
		(infoboxBounds[3] < mapBounds[3] ? mapBounds[3] - infoboxBounds[3] : 0);

	if (!shiftLng && !shiftLat) {
		this._log('No map shifting required');
		return true;
	}

	// Get current center and apply shift values
	var center = this._options.map.getCenter();
	center = new google.maps.LatLng(
		center.lat() - shiftLat,
		center.lng() - shiftLng
	);

	this._log('Shifting map '+shiftLng+','+shiftLat);
	this._options.map.setCenter(center);

}


