/**
 * #################################
 * Google Maps-related functionality
 * #################################
 */
 
 
/**
 * Loads Google Maps API if not loaded yet
 * When/as soon is loaded, it fires a user-defined callback-function
 *
 * Fires the callback function (assuming it tries to pull off some Google Maps constructions)
 * when it fails, the Google Maps API will be loaded
 * and tries to fire the callback once again on failure
 * 
 * When the loading takes longer than the given timeout, it will be cancelled
 *
 * Recursive function
 * @param	{String}	apiKey		Your Google Maps API key
 * @param	{Function}	callback	Optional, function called when/as soon as Google Maps API is loaded 
 * @return	void
 */
function onLoadGMapsApi(apiKey,callback)
{
	var checkInterval = 500;
	var timeout = 20000;
	var callback = callback || function(){ var testGMap=GMap2;testGMap=null; };
	
	try {
		callback.call();
		loadingGMapsApi.busy = false;
	}
	catch( err )
	{
		if( loadingGMapsApi.duration >= timeout )
		{
			console.error('Google Maps API timeout..');
			Dialog.alert( Lang.transl('mapApiNotLoaded') );
			return;
		}
		if( loadingGMapsApi.busy === false )// Only load if not busy loading it already
		{
			dojo.io.script.get({
				url: "http://maps.google.com/maps",
				content : {
					file: "api",
					async: "2",
					key: apiKey
				}
				/*timeout: 10000,
				callbackParamName: "callback",
				load: function(response, ioArgs) {
					// this function will be called when GMaps is loaded...
					callback.call();
				}// All this doesn't really work, therefore the setTimeout()*/
			});
			loadingGMapsApi.busy = true;
		}
		setTimeout(function(){onLoadGMapsApi(apiKey,callback);},checkInterval);
		loadingGMapsApi.duration += checkInterval;
	}
}
var loadingGMapsApi = {busy:false,duration:0};



/**
 * Has to be rewritten some day...making it using the CL.Maps.Comps.MapComponent object...
 *
 *
 * Constructor
 * Creates an empty Google Map, where the user can insert ONE marker and move the marker around
 * After inserting and / or dragging the marker, the location will be set as value of some given elements
 * @return void
 */
function mapGetLoc(mapElementId,centerLat,centerLon,centerZoom,latboxElementId,lonboxElementId)
{
	if (GBrowserIsCompatible())
	{
		var obj = this;
		
		obj.map = new GMap2(document.getElementById(mapElementId));
		obj.map.addControl(new GSmallMapControl());
		obj.map.addControl(new GMapTypeControl());
		obj.map.addControl(new GOverviewMapControl());
		obj.map.removeMapType(G_HYBRID_MAP);
		obj.map.addMapType(G_PHYSICAL_MAP);
		obj.map.setMapType(G_PHYSICAL_MAP);
		obj.map.enableScrollWheelZoom();
		obj.map.setCenter(new GLatLng(centerLat, centerLon), centerZoom);
		
		obj.counter = 0;
		var setMarker_EventListener = GEvent.bind(obj.map, "click", obj, function(marker,point) {
			if (obj.counter == 0) {
				if (point) {
					// Add new marker
					var myMarker = new GMarker(point,{draggable: true});
					obj.map.addOverlay(myMarker);

					// When a marker has been dragged do this:
					GEvent.addListener(myMarker, "dragend", function() {
						var npoint = myMarker.getPoint();
						// Move map to this point
						obj.map.panTo(npoint);
						// Register location
						document.getElementById(latboxElementId).value = npoint.y.toFixed(6);
						document.getElementById(lonboxElementId).value = npoint.x.toFixed(6); 
					});
					
					// Move map to this point
					obj.map.panTo(point);
					// Register location
					document.getElementById(latboxElementId).value=point.y;
					document.getElementById(lonboxElementId).value=point.x;
					
					// Count clicks
					obj.counter++;
				} else {
					obj.removeOverlay(marker);
				}
			} else {
			  // Only able to set a marker 1 time
			  // after that there won't be an eventListener for clicks on ther map
			  GEvent.removeListener(setMarker_EventListener);
			}
		});
	}
}































/* Namespace for Chopless : CL */
var CL = CL || {};

CL.Maps = CL.Maps || {};
CL.Maps.Comps = CL.Maps.Comps || {};// Operates as a facade / adapter for Google Maps API for common marker displaying
CL.Maps.Types = CL.Maps.Types || {};


/* Settings */
CL.Maps.settings = {
	startCenterLat : 56.438203,
	startCenterLng : 9.75585,
	startZoom : 5,
	dataMinZoom : 3, // We don't show data on zoomlevels lower than this value
	clusterMaxZoom_lev1 : 15 // Clusters are shown between dataMinZoom and clusterMaxZoom_lev1, when null they're shown when clustering appears
};


/* Marker icons display 
*/
CL.Maps.icons = function(req) {
	
	var icons = {
	
		defMarker : (function() {
			
			var icon = new GIcon(G_DEFAULT_ICON);
			
			return icon;
		})(),
		
		defMarkerCluster : (function() {
			
			var icon = new GIcon(G_DEFAULT_ICON);
			icon.image = "/images/markers/clustered-def.png";
			icon.shadow = "";
			icon.iconSize = new GSize(30, 30);
			icon.iconAnchor = new GPoint(15, 15);
			
			return icon;
		})(),
		
		tinyRed : (function() {
			
			var icon = new GIcon();
			icon.image = "http://labs.google.com/ridefinder/images/mm_20_red.png";
			icon.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
			icon.iconSize = new GSize(12, 20);
			icon.shadowSize = new GSize(22, 20);
			icon.iconAnchor = new GPoint(6, 20);
			icon.infoWindowAnchor = new GPoint(5, 1);
			
			return icon;
		})(),
		
		redCluster : (function() {
			
			var icon = new GIcon(G_DEFAULT_ICON);
			icon.image = "/images/markers/clustered-red.png";
			icon.shadow = "";
			icon.iconSize = new GSize(20, 20);
			icon.iconAnchor = new GPoint(10, 10);
			
			return icon;
		})(),
		
		tinyGreen : (function() {
			
			var icon = new GIcon();
			icon.image = "http://labs.google.com/ridefinder/images/mm_20_green.png";
			icon.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
			icon.iconSize = new GSize(12, 20);
			icon.shadowSize = new GSize(22, 20);
			icon.iconAnchor = new GPoint(6, 20);
			icon.infoWindowAnchor = new GPoint(5, 1);
			
			return icon;
		})(),
		
		greenCluster : (function() {
			
			var icon = new GIcon(G_DEFAULT_ICON);
			icon.image = "/images/markers/clustered-green.png";
			icon.shadow = "";
			icon.iconSize = new GSize(20, 20);
			icon.iconAnchor = new GPoint(10, 10);
			
			return icon;
		})(),

		
		tinyBlue : (function() {
			
			var icon = new GIcon();
			icon.image = "http://labs.google.com/ridefinder/images/mm_20_blue.png";
			icon.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
			icon.iconSize = new GSize(12, 20);
			icon.shadowSize = new GSize(22, 20);
			icon.iconAnchor = new GPoint(6, 20);
			icon.infoWindowAnchor = new GPoint(5, 1);
			
			return icon;
		})(),
		
		blueCluster : (function() {
			
			var icon = new GIcon(G_DEFAULT_ICON);
			icon.image = "/images/markers/clustered-blue.png";
			icon.shadow = "";
			icon.iconSize = new GSize(20, 20);
			icon.iconAnchor = new GPoint(10, 10);
			
			return icon;
		})(),

		
		tinyYellow : (function() {
			
			var icon = new GIcon();
			icon.image = "http://labs.google.com/ridefinder/images/mm_20_yellow.png";
			icon.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
			icon.iconSize = new GSize(12, 20);
			icon.shadowSize = new GSize(22, 20);
			icon.iconAnchor = new GPoint(6, 20);
			icon.infoWindowAnchor = new GPoint(5, 1);
			
			return icon;
		})(),
		
		yellowCluster : (function() {
			
			var icon = new GIcon(G_DEFAULT_ICON);
			icon.image = "/images/markers/clustered-yellow.png";
			icon.shadow = "";
			icon.iconSize = new GSize(20, 20);
			icon.iconAnchor = new GPoint(10, 10);
			
			return icon;
		})()

	
	}
	
	return icons[req];
	
};






/**
 * @constructor
 * @param {String}	mapElementId
 * @param {Object}	_options
 *  {Float}		centerLat
 *  {Float}		centerLng
 *  {Integer}	startZoom
 *  {Boolean}	largeMapControl
 *  {Boolean}	scrollWheelZoom
 * @return void
 */
CL.Maps.Comps.MapComponent = function(mapElementId,_options)
{
	if (!GBrowserIsCompatible()) {
		Dialog.alert(Lang.transl('browserUnsupported'));
		return;
	} 
	
	// PRIVATE ATTRIBUTES
	// Create Google Map
	var map = new GMap2(document.getElementById(mapElementId));
	// Storage to keep track of already loaded markers
	var storage = {};
	
	// Define options
	var _options = _options || {};
	
	// Map settings
	var centerLat = _options.centerLat || CL.Maps.settings.startCenterLat;
	var centerLng = _options.centerLng || CL.Maps.settings.startCenterLng;
	var startZoom = _options.startZoom || CL.Maps.settings.startZoom;
	
	
	// PRIVATE METHODS
	// Adds an id of an instance of CL.Maps.Comps.MarkerComponent to this storage
	// which will mark that marker as loaded
	addToStorage = function(markerComp) {
		storage[markerComp.getId()] = markerComp;
	}
	// Removes an id of an instance of CL.Maps.Comps.MarkerComponent from this storage
	removeFromStorage = function(markerId) {
		delete storage[markerId];
	}
	// Removes all id's of instances of CL.Maps.Comps.MarkerComponent from this storage
	clearStorage = function(markerId) {
		storage = {};
	}
	
	
	
	
	// PRIVILIGED METHODS
	this.getMap = function() {
		return map;
	};
	this.getStorage = function() {
		return storage;
	};
	
	// Tells whether the given markerId is already present and therefore loaded in the marker-manager
	this.isMarkerLoaded = function(markerId) {
		if( this.getStorage()[markerId] ) return true;
		else return false;
	}
	// Adds a collection of markers to this maps marker-manager
	// "coll" is an array of markers instantiated of "LatLng" or "GMarker" or "CL.Maps.Comps.MarkerComponent"
	// When "_options.panTo" is set, the map will pan to the last item of the collection
	this.addMarkers = function(coll,_options) {
		var _options = _options || {};
		var panTo = !!_options.panTo;
		var marker;
		
		for(var i=0; i<coll.length; i++)
		{
			// Is it a collection of CL.Maps.Comps.MarkerComponent 's ?
			if(coll[i] instanceof CL.Maps.Comps.MarkerComponent)
			{
				marker = coll[i].getMarker();
				addToStorage(coll[i]);
			}
			// ...or a collection of GMarker 's?
			else if(coll[i] instanceof GMarker){
				marker = coll[i];
			}
			else if(coll[i] instanceof GLatLng){
				marker = new GMarker(coll[i]);
			}else
				return;
			
			// Display marker on map
			this.getMap().addOverlay( marker );
			if( panTo && i == (coll.length-1) ) this.getMap().panTo( marker.getLatLng() );
		}
	};
	// Removes a collection of markers
	// "coll" is an array of markers instantiated of "LatLng" or "GMarker" or "CL.Maps.Comps.MarkerComponent"
	this.removeMarkers = function(coll) {
		var id;
		var marker;
		
		for(var i=0; i<coll.length; i++)
		{
			// Is it a collection of CL.Maps.Comps.MarkerComponent 's ?
			if(coll[i] instanceof CL.Maps.Comps.MarkerComponent)
			{
				id = coll[i].getId();
				// Remove marker from our map
				marker = coll[i].getMarker();
				// Remove markerComponent from our marker-list
				removeFromStorage(id);
			// ...or a collection of GMarker 's?
			}
			else if(coll[i] instanceof GMarker){
				marker = coll[i];
			}
			else if(coll[i] instanceof GLatLng){
				marker = new GMarker(coll[i]);
			}else
				return;
			
			// Hide marker from the map
			this.getMap().removeOverlay(marker);
		}
	};
	// Removes all markers
	this.clear = function() {
		this.getMap().clearOverlays();
		clearStorage();
	};
	
	
	
	
	// CONSTRUCTOR CODE
	var obj = this;// Scope

	map.removeMapType(G_HYBRID_MAP);
	map.addMapType(G_PHYSICAL_MAP);
	map.setMapType(G_PHYSICAL_MAP);
	
    if(!_options.largeMapControl)
    	map.addControl(new GSmallMapControl());
    else
    	map.addControl(new GLargeMapControl());
    if(_options.scrollWheelZoom)
    	map.enableScrollWheelZoom();
   	map.addControl(new GMapTypeControl(), new GControlPosition(G_ANCHOR_TOP_RIGHT,  new GSize(30,8)) );
	map.addControl(new GOverviewMapControl(new GSize(180,100)));
    map.setCenter(new GLatLng(centerLat, centerLng), startZoom);
	
	// Binding map-events to handlers
	if(_options.handlers) {
		for(var i=0; i<_options.handlers.length;i++)
			GEvent.addListener(map, _options.handlers[i].event,  _options.handlers[i].handler);
	}


	
}// end constructor


// PUBLIC, NON-PRIVILIGED METHODS

// Return corner-points (GLatLng) of this maps viewport
CL.Maps.Comps.MapComponent.prototype.getCorners = function()
{
	//create the boundary for the data
	var bounds = this.getMap().getBounds();
	
	var northEast = bounds.getNorthEast();
	var southWest = bounds.getSouthWest();
		
	return { 'ne':northEast , 'sw':southWest };
}

/**
 * Updates markers on the map,
 * Loads only the points within the viewport of the map,
 * and the ones requested by the user by filtering
 * @param {string}	triggerEvent	optional ('zoom' / 'move')
 * @return void
 */
CL.Maps.Comps.MapComponent.prototype.updateMarkers = function (triggerEvent)
{
	// We don't show data on too low zoomlevels
	if( this.getMap().getZoom() < CL.Maps.settings.dataMinZoom )
	{
		this.clear();
		return;
	}else
	// For these high zoomlevels we don't show any clusters anymore
	if( this.getMap().getZoom() > CL.Maps.settings.clusterMaxZoom_lev1 )
		var action = 'list_within_bounds';
	// As default we cluster markers when nescacary
	else
		var action = 'list_clustered';
	
	//find active filters
	var filters = CL.Maps.Comps.Controls.getAllFilters('on');
	//create the boundary for the data
	var corners = this.getCorners();
	var url = '/' + action + '?ne=' + corners.ne.toUrlValue() + '&sw=' + corners.sw.toUrlValue() + '&filters=' + filters.join(',');
	
	
	//retrieve points from the server
	var obj = this;
	var request = GXmlHttp.create();
	request.open('GET', url, true);
	request.onreadystatechange = function() {
		if (request.readyState == 4)
		{
			//remove the existing points when zoomed
			if( triggerEvent == 'zoom' ) obj.clear();
			
			var newMarkers = [];
			var newClusters = [];
			
			//parse the result to JSON,by eval-ing it.
			//The response is an array of markers
			var markers=eval( "(" + request.responseText + ")" );
			for (var i = 0 ; i < markers.length ; i++) {
				var marker = markers[i];
				var lat = marker.lat;
				var lng = marker.lng;
				
				var markerCompProps = {};
				markerCompProps.isCluster = ( marker.type == 'c' ) ? true : false;
				markerCompProps.groupType = ( marker.groupType != '' || marker.groupType != null ) ? marker.groupType : null;
				markerCompProps.recId = marker.id || null;
				markerCompProps.content = marker.content || null;
							
				//check for lat and lng so MSIE does not error
				//on parseFloat of a null value
				if (lat && lng) {
					var latlng = new GLatLng(parseFloat(lat),parseFloat(lng));
					var markerComp = new CL.Maps.Comps.MarkerComponent(latlng, obj, markerCompProps);
					
					// See if marker already is loaded in markermanager
					if( obj.isMarkerLoaded(markerComp.getId()) )						
						continue;
					
					if( markerComp.isCluster )
						newClusters.push(markerComp);
					else
						newMarkers.push(markerComp);
				} // end of if lat and lng
			} // end of for loop
			// Add the new markers and clusters
			obj.addMarkers( newMarkers );
			obj.addMarkers( newClusters,{minZoom:CL.Maps.settings.dataMinZoom,maxZoom:CL.Maps.settings.clusterMaxZoom_lev1} );
		} //if
	} //function
	request.send(null);
}



/**
 * Toggles className 'fullMap', which will cause different CSS behavior
 * First the whole site (document body) will fadeout, and fade in again after the toggle
 * @return void
CL.Maps.Comps.MapComponent.prototype.toggleFullMap = function()
{
	var obj = this;
	var currCenter = this.getMap().getCenter();
	
	new Effect.Opacity(document.body, { // Fade out
		duration: 0.75,
		transition: Effect.Transitions.sinoidal,
		from: 1.0, 
		to: 0.5,
		afterFinish:function() // Callback when fade is finished
		{
			document.body.toggleClassName('fullMap'); // Toogle body-className
			obj.getMap().checkResize();
			obj.getMap().panTo(currCenter);
			obj.updateMarkers();
			new Effect.Opacity(document.body, { // Fade in again
				duration: 0.75,
				transition: Effect.Transitions.sinoidal,
				from: 0.5, 
				to: 1.0,
				afterFinish:function() // Callback when fade is finished
				{
					CL.Maps.Comps.Controls.rePosition();// Set controls at default position
				}
			});
		}
	});
}
 */



/**
 * @constructor
 * @param {GLatLng}	latlng
 * @param {MapComponent}	mapComponent
 * @param {Object}	_options	 					A contianer for optional arguments
 * 			{String}	groupType					Group-type
 * 			{Boolean}	isCluster					Does this marker represent several near-markers?
 * 			{String}	recId						Record-id
 * 			{String}	content						Content obj. literal, containing the content-properties for this grouptype
 * 			{GIcon}		iconType					Define looks for marker-icon
 * 			{String}	iwHtml						HTML for content infowindow
 * @return void
 */
CL.Maps.Comps.MarkerComponent = function(latlng, mapComponent, _options)
{
		
	// PRIVATE ATTRIBUTES
	var id = null;// ID of this marker in the mapComponent instance
	var marker = null;
	var mapComp = mapComponent;// Set up our map
	
	// Define displayOptions
	var _options = _options || {};
	
	var recId = _options.recId || unique();
	var isCluster = _options.isCluster || false;
	var content = _options.content || null;
	
	
	// PUBLIC ATTRIBUTES
	// Define marker type, set to default when undefined
	this.type = null;
	this.groupType = _options.groupType || 'def';
	
	
	
	// PRIVILIGED METHODS
	//getters
	this.getId = function() {
		return id;
	};
	this.getMarker = function() {// Retrieve GMarker object
		return marker;
	};
	this.getMapComp = function() {
		return mapComp;
	};
	//setters
	this.setId = function() {
		id = recId;
	};
	this.setType = function() {// Define type
		if( CL.Maps.Types[this.groupType] )
			this.type = new CL.Maps.Types[this.groupType](isCluster,content);
		else
			this.type = new CL.Maps.Types.Def(isCluster,content);
	};
	
	
	
	// CONSTRUCTOR CODE
	this.setId();
	this.setType();
	
	var iwHtml = _options.iwHtml || this.type.getIWHtml();// Either type-standard, or userdefined
	var icon = _options.iconType ? CL.Maps.icons(_options.iconType) : this.type.getIcon();// Either type-standard, or userdefined
		
	var obj = this;// Scope
	// Set up our GMarkerOptions object
	var markerOptions = { 'icon':icon };
	marker = new GMarker(latlng, markerOptions);
	marker.removable = false;	
	
	
	// InfoWindow setup
    if( iwHtml )
	{
		GEvent.addListener(marker, 'click', function()
		{	
	      	marker.openInfoWindowHtml(iwHtml);
		});// end GEvent.addListener()
	}// end if
	
}// end constructor

// PUBLIC, NON-PRIVILIGED METHODS
// Add marker to map - shortcut
CL.Maps.Comps.MarkerComponent.prototype.add = function(_options)
{
	var _options = _options || {};
	this.getMapComp().addMarkers([this],_options);
};

// Remove marker from map - shortcut
CL.Maps.Comps.MarkerComponent.prototype.remove = function()
{
	this.getMapComp().removeMarkers([this]);
};

// Checks whether this marker is placed within the maps viewports bounds
CL.Maps.Comps.MarkerComponent.prototype.isWithinBounds = function()
{
	return this.getMapComp().getMap().getBounds().containsLatLng(
		this.getMarker().getLatLng()
	);
};












/*
Interface for CL.Maps.Types.*
	CL.Maps.Types.*::getIcon()
	CL.Maps.Types.*::getIWHtml()
*/

/**
 * Implements Interface for CL.Maps.Types.*
 */
CL.Maps.Types.Def = function(isCluster,content)
{
	// PRIVATE ATTRIBUTES
	var icon = (isCluster) ? CL.Maps.icons('defMarkerCluster') : CL.Maps.icons('defMarker');
	
	// PRIVILIGED METHODS
	this.getIcon = function() {
		return icon;
	};
	// Returns the HTML for infowindow
	this.getIWHtml = function() {
		// No information is given
		if( !content || (content.header == '' && content.teaser == '') )
			return null;
		// Build and return the HTML
		var html = 	'<div class="iwContent"><h3>' 
					 + content.header//.escapeHTML()
					 + '</h3><p class="teaser"><a href="'
					 + content.detailURI
					 + '">'
					 + content.teaser//.escapeHTML() 
					 + '</a></p><p><a class="readmore" href="'
					 + content.articleURI
					 + '">'
					 + Lang.transl('viewArticle')
					 + '</a>'
					 + '</p></div>';
		return html;
	};
}
CL.Maps.Types.Def.prototype = {};
