/**
 * Librairie de gestion de l'api GOOGLE MAP
 * 
 * @author Luc ALBERT <luc@nematis.com>
 * @version V1.0 - 02 Juillet 2008
 * 
 * @see Prototype 1.6
 */

/** Vérifie si Prototype est disponible **/
if( typeof Prototype=='undefined' ){
	throw("GoMap requires the Prototype JavaScript");
}

var GoMap = Class.create({
	initialize: function( container, options ) {
		this.container = $(container);
		this.options = Object.extend({
			lat: 42.701574,
			lng: 2.89415,
			zoom: 10,
			point : {lat: null, lng: null, panTo: false},
			controls : ['GLargeMapControl', 'GMapTypeControl'],
			enableDragging: true,
			enableInfoWindow: true,
			enableDoubleClickZoom: false,
			enableContinuousZoom: false,
			enableGoogleBar: false,
			enableScrollWheelZoom: false,
			debug: false
		}, options || {});
		
		
		/**
		 * @var {Bool} Flag définissant si l'application est en train de travailler ou a fini.
		 */
		this.isWorking = true;
		/**
		 * @var {Array} Liste des contrôles de la carte
		 */
		this.controls = new Array();
		/**
		 * @var {Array} Liste des markers de la carte
		 */
		this.markers = new Array();
		/**
		 * @var {Array} Tableau des icones disponibles pour l'affichage des markers
		 */
		this.icons = new Array();
		/**
		 * @var {Array} Tableau des overlays enregitrés. Ils seront affiché lors de chaque maj de la carte
		 */
		this.registeredOverlays = new Array();
		
		
		/**
		 * @var {Object} Objet de gestion de l'itinéraire
		 */
		this.routes = {};
		
		this.routes.container = null;
		this.routes.from = null;
		this.routes.destinations = new Array();
		
		
		if (GBrowserIsCompatible()) {
			// Préviens les fuites de mémoire
			Event.observe( window, 'unload', GUnload );
			// Initialisation de la carte
			this.map = new GMap2(this.container);
			// Initialisation des contrôles
			this.addControls( this.options.controls );
			// Centrage de la carte
			var point = new GLatLng(parseFloat(this.options.lat), parseFloat(this.options.lng));
			this.map.setCenter(point, this.options.zoom);
			
			// Initialisation des options de la carte
			if( this.options.enableDragging) {this.map.enableDragging();}
			if( this.options.enableInfoWindow) {this.map.enableInfoWindow();}
			if( this.options.enableDoubleClickZoom) {this.map.enableDoubleClickZoom();}
			if( this.options.enableContinuousZoom) {this.map.enableContinuousZoom();}
			if( this.options.enableGoogleBar) {this.map.enableGoogleBar();}
			if( this.options.enableScrollWheelZoom) {this.map.enableScrollWheelZoom();}
			
			// panTo si activé
			if (this.options.point.lat != null && this.options.point.lng != null) {
				this.addMarker(this.options.point.lat, this.options.point.lng, this.options.point.panTo);
			}
			
			// Debug
			/**
			 * TODO: faire automatiquement
			 */
			if( this.options.debug )
			{
				obj = this;
				GEvent.addListener( obj.map, 'moveend', function( e ){
					$('debug_map').innerHTML = obj.map.getCenter() + ' - ' + obj.map.getZoom();
				});
			}
		} else {
			alert( BROWSER_NO_COMPATIBLE );
			return;
		}
	},
	
	/**
	 * Ajoute des controles à la cartes
	 * @param {Object} controls
	 */
	addControls: function( control )
	{
		var controls = new Array();
		
		if( Object.isArray( control ) )	{
			control.each( function( c ){ controls.push( c );} );
		} else {
			controls.push( control );
		}
		
		var obj = this;
		controls.each( function( c ){
			obj.addControl( c );
		});
	},
	
	/**
	 * Ajoute un contrôle à la carte
	 * 
	 * @param {String} control Nom du contrôle à ajouter tel qu'il est définie dans la doc de l'Api de Google
	 */
	addControl: function( control )
	{
		if (this.controls.indexOf(control) == -1) {
			this.map.addControl(eval('new ' + control + '()'));
			this.controls.push( control );
		}
	},
	
	/**
	 * Supprime un contrôle de la carte
	 * 
	 * @param {String} control Nom du contrôle à ajouter tel qu'il est définie dans la doc de l'Api de Google
	 */
	removeControl: function( control )
	{
		if (this.controls.indexOf(control) != -1) {
			this.map.removeControl(control);
		}
	},
	
	/**
	 * Centre la carte aux coordonnées spécifiées
	 * 
	 * @param {Object} lat
	 * @param {Object} lng
	 * @param {Object} zoom
	 */
	center: function( lat, lng, zoom )
	{
		this.map.setCenter(new GLatLng( lat, lng), zoom);
	},
	
	/**
	 * Ajoute un observateur sur un évènement d'un élément pour mettre à jour les coordonnées GPS selon une adresse
	 * 
	 * @param {Object|String} element Elément à observer
	 * @param {String} event Evènement à observer (click, load, focus, ...)
	 * @param {Object|String|Array} addressField	Champ(s) contenant l'adresse. Si c'est un tableau, les données seront concatainées
	 * dans l'ordre fournis par le tableau
	 * @param {Object|String} latFied Champ à mettre à jour avec la latitude trouvée
	 * @param {Object|String} lngField Champ à mettre à jour avec la longitude trouvée
	 */
	observeRefreshGeocod: function( element, event, addressField, latFied, lngField ) {
		this.addressField	= addressField;
		this.latFied		= $( latFied );
		this.lngField		= $( lngField );
		
		// Curseur style cliquant
		$( element ).style.cursor = 'pointer';
		
		Event.observe( element, event, this.geocode.bindAsEventListener( this ) );
	},
	geocode: function() {
		var adresse = '';
		if( Object.isArray( this.addressField ) ) {
			this.addressField.each( function( a ){ adresse+= $( a ).value + ' '; });
		} else {
			adresse = $( this.addressField ).value;
		}
		adresse.strip();
		
		var geocoder = new GClientGeocoder();
		
		var obj = this;
		geocoder.getLatLng( adresse, function( point ){
			obj.placeMarkerGeocode( point )
		} );
	},
	placeMarkerGeocode: function( point ) {
		if( point == null ) {
			alert( NO_GEOCODE_WITH_ADRRESS );
		} else {
			this.map.clearOverlays();
			var marker = this.addMarker( point.lat(), point.lng(), true );
			this.latFied.value = point.lat();
			this.lngField.value = point.lng();
			
			marker = this.makeSelectCoord( marker, this.latFied, this.lngField );
		}
	},
	
	/**
	 * La carte permet de sélectionner un point de coordonnée, lors de l'ajout/modification de position du marker, met à jour
	 * les champs latFied et lngField avec les valeurs respectives
	 * 
	 * @param {Object|String} latFied Champ à mettre à jour avec la latitude trouvée
	 * @param {Object|String} lngField Champ à mettre à jour avec la longitude trouvée
	 * @param {Object} options Options supplémentaires
	 */
	setSelectCoord: function( fieldLat, fieldLng, options ) {
		var options = Object.extend({
			event: 'dblclick'
		}, options || {});
		
		if( options.event == 'dblclick' && this.map.doubleClickZoomEnabled() ) {
			this.map.disableDoubleClickZoom();
		}
		
		var obj = this;
		this.handleSelectCoord = GEvent.addListener( this.map, options.event, function( overlay, point ) {
			obj.map.clearOverlays();
			var marker = obj.addMarker( point.lat(), point.lng() );
			marker = obj.makeSelectCoord( marker, fieldLat, fieldLng );
			
			$( fieldLat ).value = marker.getPoint().lat();
			$( fieldLng ).value = marker.getPoint().lng();
		});
	},
	
	
	/**
	 * Ajoute un nouveau marker sur la map
	 * 
	 * @param	{Float}	lat	Latitude du point
	 * @param	{Float}	lng	Longitude du point
	 * @param	{Bool}	panTo	Définie si on centre la carte sur le point ou non. Par défaut vaut FALSE
	 * @param	{Object} options Option à passer au constructeur du marker (voir doc Google Maps Api)
	 * 
	 * @return	{Object} Retourne le Marker
	 */
	addMarker: function( lat, lng , panTo, options ) {
		var point = new GLatLng( parseFloat( lat ), parseFloat( lng ) );
		var marker = new GMarker( point, options );
				
		this.map.addOverlay( marker );
		if( panTo == true ) {
			this.map.panTo( point );
		}
		
		this.markers.push( marker );
		
		return marker;
	},
	
	/**
	 * Renvoi le tableau des marqueurs enregistrés
	 * 
	 * @return {Array} Tableau des markers enregistrés
	 */
	getMarkers: function() {
		return this.markers;
	},
	
	/**
	 * Rends le marker passé en paramètre draggable, met à jour les coordonnées dans les champs fournis
	 * 
	 * @param {Object} marker
	 * @param {Object} fieldLat
	 * @param {Object} fieldLng
	 * 
	 * @return {Object}
	 */
	makeSelectCoord: function( marker, fieldLat, fieldLng ){
		var lastPoint 	= marker.getLatLng();
		var lastLat		= lastPoint.lat();
		var lastLng		= lastPoint.lng();
		var lastIcon	= marker.getIcon();
		
		// Supprime l'écoute de l'évènement
		GEvent.removeListener( this.handleSelectCoord );
		// Remet l'option d'origine
		if( this.options.enableDoubleClickZoom) {this.map.enableDoubleClickZoom();}
		
		// Supprime le marker de la map
		this.map.removeOverlay( marker );
		
		// Options du nouveau marker
		var options = {
			icon: lastIcon,
			title: 'Déplacer moi pour sélectionner votre position précisément',
			draggable: true
		};
		
		// Ajoute le marker draggable sur la carte
		var marker = this.addMarker( lastLat, lastLng, true, options);
		
		// Définie les évènements
		GEvent.addListener( marker, 'dragend', function() {
			$( fieldLat ).value = marker.getPoint().lat();
			$( fieldLng ).value = marker.getPoint().lng();
		});
		
		return marker;
	},
	
	/**
	 * Ajoute un overlay à la pile des overlays enregistrés
	 * 
	 * @param {Object} overlay
	 */
	registerOverlay: function( overlay )
	{
		this.registeredOverlays.push( overlay );
	},
	
	/**
	 * Supprime tous les overlays enregistrés
	 */
	clearRegisteredOverlays: function()
	{
		var obj = this;
		this.registeredOverlays.each( function( overlay ){
			obj.map.removeOverlay( overlay );
		});
		
		this.registeredOverlays.clear();
	},
	
	/**
	 * Affiche tous les overlays enregistrés
	 */
	displayRegisteredOverlays: function()
	{
		var obj = this;
		this.registeredOverlays.each( function( overlay ){
			try {
				obj.map.addOverlay( overlay );
			}catch( exception ){}
		});
	},

/****************
 * Gestion des icones
 */
	
	setDefaultIcon: function( icon ) {
		this.registerIcon( 'default', icon );
	},
	
	getDefaultIcon : function() {
		if( !this.hasIcon( 'default' ) ) {
			this.setDefaultIcon( new GIcon( G_DEFAULT_ICON ) );
		}
		
		return this.icons['default'];
	},
	
	registerIcon: function( idName, icon, replace )
	{
		var replace = new Boolean( replace );
		
		if( this.hasIcon( idName ) ) {
			if( replace ) {
				this.icons[idName] = icon;
			}
		} else {
			this.icons[idName] = icon;
		}
	},
	
	/**
	 * 
	 * @param {Object} idName
	 * @return {Object} Retourne l'icone enregistré avec idName
	 */
	getIcon: function( idName, returnDefaultIcon )
	{
		if( undefined  == returnDefaultIcon ) {
			returnDefaultIcon = true;
		} else {
			returnDefaultIcon = new Boolean( returnDefaultIcon );
		}
		
		if( !this.hasIcon( idName ) ) {
			if( true == returnDefaultIcon ) {
				return this.getDefaultIcon();
			} else {
				return null;
			}
		} else {
			return this.icons[idName];
		}
	},
	
	/**
	 * 
	 * @param {Object} idName
	 * @return {Boolean} Retourne TRUE si l'icone idName existe dans le tableau this.icons, sinon retourne FALSE
	 */
	hasIcon: function( idName ) {
		if( undefined == this.icons[idName] )return false;
		return true;
	},
	
	
/****************
 * Méthodes pour la gestion de fichier xml
 */

	initXml: function( options )
	{
		
	},
	
	
	loadXml: function( url )
	{
		var obj = this;
		GDownloadUrl( url, function( datas ){
			var xml = GXml.parse( datas );
			var markers = xml.documentElement.getElementsByTagName( 'marker' );
			obj.map.clearOverlays();
			
			obj.displayRegisteredOverlays();
			
			for (var i = 0; i < markers.length; i++) {
				var lat = parseFloat(markers[i].getAttribute("lat"));
				var lng = parseFloat(markers[i].getAttribute("lng"));
				var text = markers[i].getAttribute("text");
				var icon = markers[i].getAttribute("icon");
				
				var marker = obj.addMarker( lat, lng, false, obj.getIcon( icon ) );
				marker.bindInfoWindowHtml( text );
				
				GEvent.addListener( marker, 'infowindowopen', function(){
					obj.map.savePosition();
				});
				
				GEvent.addListener( marker, 'infowindowclose', function(){
					obj.map.returnToSavedPosition();
				});
			}
		} );
	},

/****************
 * Méthodes pour la gestion de fichier kml
 */

	initKml: function ( options )
	{
		
	},
	
	/**
	 * Charge le fichier kml et retourne un objet GOverlay représentation du fichier
	 * 
	 * @param {String} url
	 */
	loadKml: function( url )
	{
		var kml = new GGeoXml( url );
		return kml;
	},

/****************
 * Méthodes pour la gestion de l'itinéraire
 */

	initRoute : function( container )
	{
		this.routes.container = $( container );
		this.routes.gdir = new GDirections( this.map, this.routes.container );
		
		var obj = this;
		GEvent.addListener(obj.routes.gdir, "load", function(){
			//$("getStatus").innerHTML = obj.routes.gdir.getStatus().code;
		});
		GEvent.addListener(obj.routes.gdir, "error", function(){
			alert( 'erreur : ' + obj.routes.gdir.getStatus().code);
		});
	},

	/**
	 * Définie l'adresse de départ pour le calcul d'itinéraire
	 * Le format peut être :
	 *  - une adresse ('8, rue de la soif, 66000 perpignan')
	 *  - des coordonnées ('42.152, 3.21')
	 *  - ou les 2 (8, rue de la soif, 66000 perpignan@42.152, 3.21) 
	 * @param {Object} from
	 */
	setFrom: function( from )
	{
		this.routes.from = from.strip();
	},
	
	/**
	 * Ajoute une destination pour le calcul d'un itinéraire
	 * 
	 * @param {String} destination Une destination à ajouter.
	 * Voir les format possible this.setForm
	 */
	addDestination: function( destination )
	{
		this.routes.destinations.push( destination.strip() );
	},
	
	observeRefreshRoute: function( elementFrom )
	{
		this.routes.elFrom = $( elementFrom );
		
		/**
		 * TODO: Empêcher dynamiquement l'envoie du formulaire
		 */
		
		this.routes.form = this.routes.elFrom.form;
		Event.observe( this.routes.form, 'submit', this.refreshRoutes.bindAsEventListener( this ) );
	},
	
	refreshRoutes: function()
	{
		this.setFrom( this.routes.elFrom.value );
		this.routes.destinations.splice (0, 1);
		this.calculRoutes();
	},
	
	calculRoutes: function()
	{
		this.map.clearOverlays();
		this.routes.gdir.clear();
		
		if( this.routes.destinations.length == 0 )
		{
			alert( NO_DESTINATION );
		}
		else if( this.routes.destinations.length == 1 )
		{
			var query = 'from: ' + this.routes.from + ' to: ' + this.routes.destinations[0];
			this.routes.gdir.load( query );
		}
		else
		{
			this.routes.destinations.unshift( this.routes.from );
			this.routes.gdir.loadFromWaypoints( this.routes.destinations );
		}
	}
});


