/**
 * The dgMap class represents the dieGesellschafter map application
 */
dgMap = Class.create({

	rel2rootweb: '',
	map: null,
	mmng: null, 	// MarkerManager
	lmc: null, 		// GLargeMapControl
	groups: {
		'p': [], 	// Geförderte Projekte
		'a': [], 	// Aktionen zum 5. Mai
		'u': [], 	// Unterstützer
		'fp': [],	// Partner des Filmfestivals
		'ff': [], 	// Orte des Filmfestivals
		'e': [], 	// Freiwilligenorganisationen
		'om': [], 	// Autoren des Projektmagazins
		'g': [] 	// Gesellschafter
	},
	result: null,
	resultcount: null,
	bounds: null,
	zoomlevel: 6,
	storage: null,
	
	/**
	 * Initializes the object
	 * @constructor
	 */
	initialize: function() {
		this.storage = new Hash();
		if (rel2rootweb) {
			this.rel2rootweb = rel2rootweb;
		}
		if (GBrowserIsCompatible()) {
			this.map = new GMap2($('dgmap-canvas'));
			this.map.setCenter(new GLatLng(52.05, 9.85), 6);
			// marker manager
			this.mmng = new MarkerManager(this.map, {
				maxZoom: 21
			});
			this.lmc = new GLargeMapControl();
			this.map.addControl(new GMapTypeControl());
			this.map.addControl(new GOverviewMapControl());
			// observe search action event
			evt = this.search.bind(this);
			Event.observe($('dgmap-searchform'), 'submit', evt);
			// observe form reset
			evt = this.resetForm.bind(this);
			Event.observe($('dgmap-reset'), 'click', evt);
			// observe zoom event
			evt = this.handleZoom.bind(this);
			GEvent.addListener(this.map, 'zoomend', evt);
			// observe (de)aktivation of marker groups
			me = this;
			Object.keys(this.groups).each(function(group) {
				evt = me.toggleMarkerGroup.bind(me, group);
				Event.observe($('toggle-' + group), 'click', evt);
			});
			// try to restore the previous serach from history if URL contains a hash
			if (location.hash.length > 1) {
				this.restoreFromHistory();
			}
		}
	},
	
	/**
	 * Handles a search request (by means of an ajax request)
	 */
	search: function() {
	
		// abort if map loading not completed
		if (!this.map.isLoaded()) {
			return;
		}
		
		// reset postcode value if not valid
		if (!$('dgmap-postcode').value.match(/\d{1,5}/)) {
			$('dgmap-postcode').value = '';
		}
		
		// abort if no serach criteria
		if ($('dgmap-postcode').value == '' && $('dgmap-city').value == '' && $('dgmap-searchterm').value == '') {
			this.handleException('nosearch');
			return;
		}
		
		// reset the map
		this.mmng.clearMarkers();
		this.map.closeExtInfoWindow();
		this.initResultcount();
		this.bounds = new GLatLngBounds();
		$('dgmap-overlay').show();
		$('dgmap-loader').show();
		
		me = this;
		new Ajax.Request(me.rel2rootweb + 'karte/ajax/search.php', {
			method: 'post',
			asynchronous: true,
			parameters: {
				'postcode': $('dgmap-postcode').value,
				'city': $('dgmap-city').value,
				'searchterm': $('dgmap-searchterm').value
			},
			onSuccess: me.processSearchresult.bind(me),
			onFailure: me.handleException.bind(me, 'error')
		});
		
		/*
		 // polygone of the postcod area
		 this.map.clearOverlays();
		 if($('dgmap-postcode').value.match(/\d{5}/)) {
		 	new Ajax.Request(me.rel2rootweb+'karte/ajax/postcodearea.php', {
		 		method: 'post',
		 		asynchronous: true,
		 		parameters: $('dgmap-postcode').serialize(true),
		 		onSuccess: function(xhr) {
		 			var polygon = eval(xhr.responseText);
		 			if(polygon) {
		 				me.map.addOverlay(polygon);
		 			}
		 		},
		 		onFailure: function() {
		 		}
		 	});
		 }
		 */
	},
	
	/**
	 * Processes a search result returned as a JSON string
	 * @param xhr Reference of the XMLHttpRequest
	 */
	processSearchresult: function(xhr) {
		this.result = xhr.responseText.evalJSON();
		
		// update browser history
		this.saveToHistory('postcode', $('dgmap-postcode').value);
		this.saveToHistory('city', $('dgmap-city').value);
		this.saveToHistory('searchterm', $('dgmap-searchterm').value);
		
		// create markers and display state for every group
		me = this;
		Object.keys(this.groups).each(function(group) {
			if (me.result[group] == null || me.result[group].length == 0) {
				me.setGroupState(group, false);
				return;
			}
			me.crateZoomLevels(group);
			me.setGroupState(group, true);
		});
		
		this.createAMMarker();
		
		// no match handling
		if (this.resultcount.total == 0) {
			// searchterm contains the term "aktion mensch" (no natural match)
			if ($('dgmap-searchterm').value.search(/aktion\s*mensch/i) != -1) {
				this.bounds.extend(new GLatLng(50.73, 7.09));
				this.bounds.extend(new GLatLng(50.68, 7.20));
			} else {
				this.handleException('nomatch');
				return;
			}
		}
		
		// show ths zoom controller
		this.map.addControl(this.lmc);
		
		// redraw markers
		this.mmng.refresh();
		
		// center and zoom to bounds
		this.map.setCenter(this.bounds.getCenter(), this.map.getBoundsZoomLevel(this.bounds));
		
		// show matches info and marker groups
		$('dgmap-resultcount').innerHTML = this.resultcount.total + ' ' + (this.resultcount.total == 1 ? 'Ergebnis' : 'Ergebnisse');
		$('dgmap-resultcount').show();
		$('dgmap-displayoptions').show();
		
		// hide overlays
		$('dgmap-nosearch').hide();
		$('dgmap-nomatch').hide();
		$('dgmap-error').hide();
		$('dgmap-loader').hide();
		$('dgmap-overlay').hide();
	},
	
	/**
	 * Handles a serach request exception
	 * @param type Exception type (nosearch|nomatch|error)
	 */
	handleException: function(type) {
		// reset some map elements
		this.mmng.clearMarkers();
		this.map.closeExtInfoWindow();
		// hide the zoom controller
		this.map.removeControl(this.lmc);
		// center and zoom to default
		this.map.setCenter(new GLatLng(52.05, 9.85), 6);
		// reset browser history
		location.hash = '';
		// show overlay after hiding some other elements
		$('dgmap-resultcount').innerHTML = '';
		$('dgmap-resultcount').hide();
		$('dgmap-loader').hide();
		$('dgmap-nosearch').hide();
		$('dgmap-nomatch').hide();
		$('dgmap-error').hide();
		$('dgmap-overlay').show();
		$('dgmap-' + type).show();
		// inactivate all marker groups
		me = this;
		Object.keys(this.groups).each(function(group) {
			me.setGroupState(group, false);
		});
	},
	
	/**
	 * Creates the zoom levels for a specific marker group (i.e. volunteer orgs)
	 * @param group The marker group to be processed
	 */
	crateZoomLevels: function(group) {
		levels = this.result[group];
		
		// basic icon definition
		var _icon = new GIcon();
		_icon.iconSize = new GSize(32, 32);
		_icon.iconAnchor = new GPoint(16, 32);
		_icon.infoWindowAnchor = new GPoint(50, 0);
		
		// zoom level 1
		var level = levels[0];
		var data = level['data'];
		var icon = new GIcon(_icon);
		icon.image = 'img/' + group + '_lmarker.png';
		icon.iconSize = new GSize(62, 29);
		for (i = 0; i < data.length; i++) {
			this.resultcount.total += data[i]['count'];
			this.resultcount[group] += data[i]['count'];
			var marker = this.createLabeledMarker(new GLatLng(data[i]['lat'], data[i]['lng']), data[i]['count'], data[i]['infohead'] + data[i]['info'], icon, group);
			this.mmng.addMarker(marker, level['zoomlvl'][0], level['zoomlvl'][1]);
		}
		
		// zoom level 2
		var level = levels[1];
		var data = level['data'];
		var icon = new GIcon(_icon);
		icon.image = 'img/' + group + '_marker.png';
		icon.iconSize = new GSize(29, 33);
		if (group == 'g' || group == 'p') {
			icon.image = 'img/' + group + '_lmarker.png';
			icon.iconSize = new GSize(62, 29);
			for (i = 0; i < data.length; i++) {
				var pos = new GLatLng(data[i]['lat'], data[i]['lng']);
				var marker = this.createLabeledMarker(pos, data[i]['count'], data[i]['infohead'] + data[i]['info'], icon, group);
				this.bounds.extend(pos);
				this.mmng.addMarker(marker, level['zoomlvl'][0], level['zoomlvl'][1]);
			}
		} else {
			for (i = 0; i < data.length; i++) {
				var pos = new GLatLng(data[i]['lat'], data[i]['lng']);
				var marker = this.createMarker(pos, data[i]['info'], icon, group);
				this.bounds.extend(pos);
				this.mmng.addMarker(marker, level['zoomlvl'][0], level['zoomlvl'][1]);
			}
		}
	},
	
	/**
	 * Checks for zoom level transitions and closes the open info window if necessary
	 */
	handleZoom: function() {
		var zoom = this.map.getZoom();
		if (this.zoomlevel >= 12 && zoom < 12) {
			this.map.closeExtInfoWindow();
		} else {
			if (this.zoomlevel < 12 && zoom >= 12) {
				this.map.closeExtInfoWindow();
			}
		}
		this.zoomlevel = zoom;
	},
	
	/**
	 * Creates a marker
	 * @param pos Geo position
	 * @param info Info window content
	 * @param icon Marker icon
	 * @param group Marker group
	 */
	createMarker: function(pos, info, icon, group) {
		me = this;
		var marker = new GMarker(pos, {
			title: '',
			icon: icon
		});
		GEvent.addListener(marker, 'click', function() {
			marker.openExtInfoWindow(me.map, "infowin", '<div id="infowin_inner" class="inner">' + info + '</div>', {
				beakOffset: 1
			});
		});
		if (group) {
			this.groups[group].push(marker);
		}
		return marker;
	},
	
	/**
	 * Creates a labeled marker
	 * @param pos Geo position
	 * @param label Label text for the marker
	 * @param info Info window content
	 * @param icon Marker icon
	 * @param group Marker group
	 */
	createLabeledMarker: function(pos, label, info, icon, group) {
		me = this;
		var opts = {
			'icon': icon,
			'labelText': label,
			'labelClass': 'marker-label',
			'labelOffset': new GSize(10, -24)
		};
		var marker = new LabeledMarker(pos, opts);
		GEvent.addListener(marker, 'click', function() {
			marker.openExtInfoWindow(me.map, "infowin", '<div id="infowin_inner" class="inner">' + info + '</div>', {
				beakOffset: 1
			});
		});
		if (group) {
			this.groups[group].push(marker);
		}
		return marker;
	},
	
	/**
	 * Creates the permanent "Aktion Mensch" marker, excluded in the bounds calculation
	 * Coords obtained using the method getLatLng() of class GClientGeocoder
	 */
	 createAMMarker: function() {
		// overlay
		var info = 	'<h4>Aktion Mensch</h4>' +
					'<p>Heinemannstr. 36<br />53175 Bonn</p>' +
					'<p><a href="http://www.aktion-mensch.de" class="arr">www.aktion-mensch.de</a></p>';
		
		// icon definition
		var _icon = new GIcon();
		_icon.iconSize = new GSize(32, 32);
		_icon.iconAnchor = new GPoint(16, 32);
		_icon.infoWindowAnchor = new GPoint(50, 0);
		var icon = new GIcon(_icon);
		icon.image = 'img/akme_marker.png';
		icon.iconSize = new GSize(83, 33);
		
		me = this;
		var marker = new GMarker(new GLatLng(50.7057265, 7.1432907), {
			title: '',
			icon: icon,
			zIndexProcess: function(){return -180000000;}
		});
		GEvent.addListener(marker, 'click', function() {
			marker.openExtInfoWindow(me.map, "infowin", '<div id="infowin_inner" class="inner">' + info + '</div>', {
				beakOffset: 1
			});
		});

		marker.show();
		this.mmng.addMarker(marker, 1, 21);
		
	},
	
	/**
	 * Sets the state of a marker group (enabled or disabled)
	 * @param group Marker group to set
	 * @param isacive Active state
	 */
	setGroupState: function(group, isactive) {
		if (isactive) {
			$('icon-' + group).src = 'img/' + group + '_active.gif';
			$('icon-' + group).title = this.resultcount[group] + ' Treffer gefunden';
			if ($('toggle-' + group).checked) 
				this.toggleMarkerGroup(group);
		} else {
			$('icon-' + group).src = 'img/' + group + '_inactive.gif';
			$('icon-' + group).title = 'Keine Treffer gefunden';
			if ($('toggle-' + group).checked) 
				this.saveToHistory(group, 1);
		}
	},
	
	/**
	 * Toggles the visibility of a marker group
	 * @param group Marker group to be toggled
	 */
	toggleMarkerGroup: function(group) {
		var checked = $('toggle-' + group).checked;
		// loop through the markers an (de)activate them
		for (var i = 0; i < this.groups[group].length; i++) {
			var marker = this.groups[group][i];
			if (checked) {
				marker.show();
			} else {
				marker.hide();
				this.map.closeExtInfoWindow();
			}
		}
		// update browser history
		this.saveToHistory(group, checked ? 1 : null);
	},
	
	/**
	 * Fetches detail info via AJAX and attaches it to the
	 * appropriate element in the list (a currently opened
	 * element will be closed automatically)
	 */
	showInfo: function(group, id) {
		me = this;
		
		new Ajax.Request(me.rel2rootweb + 'karte/ajax/info.php', {
			method: 'post',
			asynchronous: true,
			parameters: {
				'group': group,
				'id': id
			},
			onSuccess: function(xhr) {
				// restore old state
				if (me.storage.get('i-id') && $(me.storage.get('i-id'))) {
					$(me.storage.get('i-id')).innerHTML = me.storage.get('i-html');
					$(me.storage.get('i-id')).removeClassName('active');
				}
				// save actual state for later restore
				me.storage.set('i-id', 'i-' + group + id);
				me.storage.set('i-html', $('i-' + group + id).innerHTML);
				// replace content and resize info window
				$('i-' + group + id).innerHTML = xhr.responseText;
				$(me.storage.get('i-id')).addClassName('active');
				me.map.getExtInfoWindow().resize();
			},
			onFailure: function() {
				// currently we just do no error handling
			}
		});
	},
	
	/**
	 * Initializes the result count for the differen marker groups
	 */
	initResultcount: function() {
		this.resultcount = {
			'total': 0
		};
		me = this;
		Object.keys(this.groups).each(function(group) {
			me.resultcount[group] = 0;
		});
	},
	
	/**
	 * Resets the search form values and history
	 */
	resetForm: function() {
		$('dgmap-postcode').value = '';
		$('dgmap-city').value = '';
		$('dgmap-searchterm').value = '';
		location.hash = '';
	},
	
	/**
	 * Saves a key value pair into the history
	 * excluding Gecko browsers because:
	 * 1. FF handles the history on it's own (and better)
	 * 2. FF2 bahaves wrong with this history concept
	 * @param key The key that shoud be used for storage
	 * @param value The value that shoud be stored
	 */
	saveToHistory: function(key, value) {
		if (!Prototype.Browser.Gecko) {
			if (value) {
				History.set(key, value);
			} else {
				History.unset(key);
			}
		}
	},
	
	/**
	 * Restores search form values from history and triggers the search if necessary
	 */
	restoreFromHistory: function() {
		var city = History.get('city');
		var postcode = History.get('postcode');
		var searchterm = History.get('searchterm');
		$('dgmap-city').value = city != null ? city : '';
		$('dgmap-postcode').value = postcode != null ? postcode : '';
		$('dgmap-searchterm').value = searchterm != null ? searchterm : '';
		Object.keys(this.groups).each(function(group) {
			if (History.get(group) != null) {
				$('toggle-' + group).checked = true;
			} else {
				$('toggle-' + group).checked = false;
			}
		});
		if (city != null || postcode != null || searchterm != null) {
			this.search();
		}
	}
	
});


// create the init queue, if not existing
if (!initFunctions) {
	initFunctions = new Array();
}
// add the map to the init queue
initFunctions.push(function() {
	dgmap = new dgMap();
});

