/**
 * Verwaltung von mehreren Redx_Map_Controler-Objekten
 */
Redx_Map_Controler.instances = new Hash();

Redx_Map_Controler.addInstance = function(map) {
	Redx_Map_Controler.instances[map.getMapContainerId()] = map;
}

Redx_Map_Controler.getInstance = function(id) {
	return Redx_Map_Controler.instances[id];
}

Redx_Map_Controler.CONTROL_TYPE_SMALL = 1;
Redx_Map_Controler.CONTROL_TYPE_LARGE = 2;
Redx_Map_Controler.CONTROL_MAP = 3;
Redx_Map_Controler.CONTROL_MAP_TYPE = 4;
Redx_Map_Controler.CONTROL_OVERVIEW_MAP = 5;
Redx_Map_Controler.CONTROL_SCALE = 6;
Redx_Map_Controler.CONTROL_DRAG_ZOOM = 7;
Redx_Map_Controler.CONTROL_SEARCH = 8;
Redx_Map_Controler.CONTROL_SPECIAL_LAYER = 9;

Redx_Map_Controler.MAP_TYPE_MAPNIK = 0;
Redx_Map_Controler.MAP_TYPE_TAH = 1;
Redx_Map_Controler.MAP_TYPE_CYCLE = 2;
Redx_Map_Controler.MAP_TYPE_COURSE = 3;

/**
 * Factory für einen Mapcontroler
 * @param map_container_id String
 * @return Redx_Map_Controler
 */
Redx_Map_Controler.create = function(map_container_id, control_type) {
	if (typeof control_type == "undefined") {
		control_type = Redx_Map_Controler.CONTROL_TYPE_SMALL;
	}
	var mc = new Redx_Map_Controler(map_container_id);
	mc.setMapContainerId(map_container_id);
	mc.setControlType(control_type);
	mc.init();
	return mc;
}

/**
 * Redx_Map_Controler - Klasse
 * Stellt grundsätzliche Maps-Funktionen zur Verfügung
 */
function Redx_Map_Controler() {
	/**
	* Eigenschaften
	*/
	/**
	* @var GMap2
	*/
	this._map = null;
	this._map_container_id = null;

	/**
	* Dom-Objekt, in das die Karte gezeichnet wird
	**/
	this._map_container = null;
	this._special_layers = new Object();

	/**
	* Verwaltung der Layers, die der Karte hinzugefügt werden
	* Private Eigenschaft
	*/
	var _layers = new Object();

	var _control_type = Redx_Map_Controler.CONTROL_TYPE_SMALL;

	var _controls = new Hash();

	/**
	* privilegierte Methoden mit Zugriff auf private Eigenschaften
	*/

	/* Enabled services
	*  geocode
	*  directions
	*/
	this._services = new Hash();
	this._services['directions'] = false;
	this._services['geocoder'] = false;

	this._geocoder_address_fields = [];


	/**
	* @var GDirections
	* Wird verwendet für die ermittlung einer Route von: nach:
	*/
	this._directions = null;
	/**
	* @var GClientGeocoder
	* Wird verwendet für die ermittlung der Koordinaten einer Adresse
	*/
	this._geocoder = null;
	/**
	* ID des Latitude-Feldes, dass ausgefüllt wird, nach dem eine Adresse gefunden wurde
	*/
	this._lat_field_id = null;
	/**
	* ID des Longitude-Feldes, dass ausgefüllt wird, nach dem eine Adresse gefunden wurde
	*/
	this._lng_field_id = null;

	this._position_marker = null;

	/**
	* Dom-Objekt, in das die Routeninfromationen geschrieben werden
	*/
	this._drivings_container_id = null;

	this._init_zoom_level = 10;
	this._address_found_zoom_level = 7;
	
	/**
	 * setzt ID des MapContainer Elementes
	 * @param id string
	 */
	this.setMapContainerId = function(id) {
		this._map_container_id = id;
	}

	this.getMapContainerId = function() {
		return this._map_container_id;
	}
	
	/**
	 * Hinzufügen von custom Maps z.B. OpenStreetMap
	 * 
	 * @param copyright CopyrightCollection
	 * @param name string
	 * @param tile_url_func function
	 */
	this.addCustomMapType = function(copyright, name, tile_url_func) {
		var layer = new Array();
		layer[0] = new GTileLayer(copyright, 0, 18);
		layer[0].getTileUrl = tile_url_func;
		layer[0].isPng = function () { return true; };
		layer[0].getOpacity = function () { return 1.0; };
		var map = new GMapType(layer,
		new GMercatorProjection(19), name,
		{ urlArg: name, linkColor: '#000000' });
		this._map.addMapType(map);
		return map;
	}
	
	/**
	 * fügt neuen MapType hinzu
	 * @param layer Redx_Map_Controler.MAP_TYPE_*
	 * @param display_name string
	 */
	this.addMapType = function(layer, display_name) {
		switch (layer) {
			case Redx_Map_Controler.MAP_TYPE_MAPNIK:
			this.addCustomMapType(this.getOSMCopyright(), display_name, function(a, z) { return "http://tile.openstreetmap.org/" + z + "/" + a.x + "/" + a.y + ".png"; });
			break;
			case Redx_Map_Controler.MAP_TYPE_TAH:
			this.addCustomMapType(this.getOSMCopyright(), display_name, function(a, z) { return "http://tah.openstreetmap.org/Tiles/tile/" + z + "/" + a.x + "/" + a.y + ".png"; });
			break;
			case Redx_Map_Controler.MAP_TYPE_CYCLE:
			this.addCustomMapType(this.getOSMCopyright(), display_name, function(a, z) { return "http://b.andy.sandbox.cloudmade.com/tiles/cycle/"+ z +"/"+a.x+"/"+a.y+".png"; });
			break;
			case Redx_Map_Controler.MAP_TYPE_COURSE:
			this.addCustomMapType(this.getOSMCopyright(), display_name, function(a, z) { return "http://openpistemap.org/tiles/contours/"+ z +"/"+a.x+"/"+a.y+".png"; });
			break;
			default:
			this._map.addMapType(layer);
		}
	}

	/**
	 * erstellt generelle OpenStreetMap Copyright Collection (verwendet für addMapType)
	 */
	this.getOSMCopyright = function() {
		var copyright = new GCopyright(1, new GLatLngBounds(new GLatLng(-90,-180), new GLatLng(90,180)), 0,
		'(<a rel="license" href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>)');
		var copyrightCollection = new GCopyrightCollection('Kartendaten &copy; 2009 <a href="http://www.openstreetmap.org/">OpenStreetMap</a> Contributors');
		copyrightCollection.addCopyright(copyright);
		return copyrightCollection;
	}

	/**
	* Setzt den Map-Container
	* @param container DOM-Object
	*/
	this.setMapContainer = function(container) {
		this._map_container = container;
	}

	/**
	 * setzt das default Zoom Level
	 * @param zoom_level int
	 */
	this.setZoomLevel = function(zoom_level) {
		this.getGMap().setZoom(zoom_level);
	}

	this.getZoomLevel = function() {
		return this.getGMap().getZoom();
	}

	/**
	 * setzt Mittelpunkt auf der Map
	 * @param point GLatLng
	 * @param zoom integer
	 */
	this.setCenter = function(point, zoom) {
		this.getGMap().setCenter(point, zoom);
	}

	this.getCenter = function() {
		return this.getGMap().getCenter();
	}

	/**
	 * setzt den default Map Type
	 * @param type GMapType
	 */
	this.setMapType = function(type) {
		this.getGMap().setMapType(type);
	}

	/**
	 * returns the GMap Object
	 * @return GMap
	 */
	this.getGMap = function() {
		return this._map;
	}

	this.hideMap = function() {
		$(this.getMapContainerId()).hide();
	}

	this.showMap = function(recenter_to_position_marker) {
		$(this.getMapContainerId()).show();
		this.getGMap().checkResize();
		if (recenter_to_position_marker) {
			this.centerMapToPositionMarker();
		}
	}

	this.toggleMap = function(recenter_to_position_marker) {
		$(this.getMapContainerId()).toggle();
		if (this.isMapVisible()) {
			this.getGMap().checkResize();
			if (recenter_to_position_marker) {
				this.centerMapToPositionMarker();
			}
		}
	}

	this.isMapVisible = function() {
		return $(this.getMapContainerId()).visible();
	}

	/**
	* Hinzufügen eines zusätzlichen Layers
	* @param layer String
	*/
	this.addLayer = function(layer) {
		var glayer = null;
		switch (layer) {
			case 'polyline':
			glayer = new GPolyline([
			new GLatLng(48.47973501021, 13.9935350418090),
			new GLatLng(48.479, 13.993),
			new GLatLng(48.479, 13.995)
			], "#ff0000", 10);
			//glayer.enableEditing();
			break;
			case 'polygon':
			glayer = new GPolygon([
			new GLatLng(48.48073501021, 13.9940350418090),
			new GLatLng(48.485, 13.9938),
			new GLatLng(48.485, 13.990),
			new GLatLng(48.48073501021, 13.9940350418090)
			], "#f33f00", 5, 1, "#ff0000", 0.2);
			glayer.enableEditing();
			break;
			case 'groundoverlay':
			var boundaries = new GLatLngBounds(new GLatLng(48.483, 13.98),
			new GLatLng(48.485, 13.99));
			glayer = new GGroundOverlay("http://www.gps-tour.info/res/pics/gps_tour_info.gif", boundaries);
			break;
			default:
			glayer = new GLayer(layer);
		}

		this.getGMap().addOverlay(glayer);
		_layers[layer] = glayer;
	}

	/**
	* Entfernt ein bestimmten Layer
	* @param layer String
	*/
	this.removeLayer = function(layer) {
		this.getGMap().removeOverlay(_layers[layer]);
		if (layer == 'polyline' || layer == 'polygon') {
			_layers[layer].disableEditing();
		}
		_layers[layer] = null;
	}

	/**
	* Je nach status wird das Layer ein oder ausgeblendet
	* @param layer String
	*/
	this.toggleLayer = function(layer) {
		if (_layers[layer]) {
			this.removeLayer(layer);
		} else {
			this.addLayer(layer);
		}
	}

	/**
	* Liefert die Fläche des Polygons in m2
	* @return Integer
	*/
	this.getPolygonArea = function() {
		if (_layers['polygon']) {
			return _layers['polygon'].getArea();
		}
		return 0;
	}

	this.enableScrollWheelZoom = function() {
		this.getGMap().enableScrollWheelZoom();
	}

	this.disableScrollWheelZoom = function() {
		this.getGMap().disableScrollWheelZoom();
	}

	this.enableContinuousZoom = function() {
		this.getGMap().enableDoubleClickZoom();
	}

	this.disableContinuousZoom = function() {
		this.getGMap().disableContinuousZoom();
	}

	this.enableDoubleClickZoom = function() {
		this.getGMap().enableDoubleClickZoom();
	}

	this.disableDoubleClickZoom = function() {
		this.getGMap().disableDoubleClickZoom();
	}

	/**
	 * Hinzufügen eines SpecialLayers für z.B. Wikipedia und Panoramio
	 * @param name string
	 * @param display_name string
	 */
	this.addSpecialLayer = function(name, display_name) {
		this._special_layers[name] = display_name;
	}
	
	/**
	 * Einen Bestimmten Layer entfernen
	 * @param name string
	 */
	this.removeSpecialLayer = function(name) {
		delete(this._special_layers[name]);
	}

	/**
	 * Fügt ein Menu in die Karte ein, als Optionen werden alle mit addSpecialLayer
	 * hinzugefügten Layer angezeigt.
	 * Darstellung in einem DropDown Menu.
	 * @param display_name
	 */
	this.addSpecialLayerMenuControl = function(display_name) {
		this.addControl(new Redx_Map_Special_Layer_Control(this, 'menu', display_name), Redx_Map_Controler.CONTROL_SPECIAL_LAYER);
	}

	/**
	 * Fügt ein Menu in die Karte ein, als Optionen werden alle mit addSpecialLayer
	 * hinzugefügten Layer angezeigt.
	 * Darstellung nebeneinander.
	 * @param display_name
	 */
	this.addSpecialLayerControl = function(display_name) {
		this.addControl(new Redx_Map_Special_Layer_Control(this, '', display_name), Redx_Map_Controler.CONTROL_SPECIAL_LAYER);
	}
	
	this.addSpecialLayerPanoramio = function() {
		this.addSpecialLayer('com.panoramio.all', 'Panoramio');
	}
	
	this.addSpecialLayerWikipedia = function(language) {
		this.addSpecialLayer('org.wikipedia.'+language, 'Wikipedia');
	}
	
	this.addSpecialLayerYoutube = function() {
		this.addSpecialLayer('com.youtube.all', 'YouTube');
	}
	

	/**
	 * entfernt das Special Control von der Map
	 */
	this.removeSpecialLayerControl = function() {
		this.removeControl(Redx_Map_Controler.CONTROL_SPECIAL_LAYER);
	}

	this.getSpecialLayerControl = function() {
		return this.getControl(Redx_Map_Controler.CONTROL_SPECIAL_LAYER);
	}

	/**
	* Hinzufügen einer Adresssuche
	* @param error_msg string - wenn Adresse nicht gefunden
	*/
	this.addSearchControl = function(error_msg) {
		this.addControl(new Redx_Map_Search_Control(this, error_msg), Redx_Map_Controler.CONTROL_SEARCH);
	}

	this.removeSearchControl = function() {
		this.removeControl(Redx_Map_Controler.CONTROL_SEARCH);
	}

	this.getSearchControl = function() {
		return this.getControl(Redx_Map_Controler.CONTROL_SEARCH);
	}

	/**
	* Hinzufügen eines DragZoomControls
	*
	*/
	this.addDragZoomControl = function(position) {
		var me = this;
		this.addControl(new DragZoomControl(), Redx_Map_Controler.CONTROL_DRAG_ZOOM, position);
		GEvent.addListener(this.getGMap(), "maptypechanged", function() { me.adjustDragZoomControlPosition(); });
	}

	this.removeDragZoomControl = function() {
		this.removeControl(Redx_Map_Controler.CONTROL_DRAG_ZOOM);
	}

	this.getDragZoomControl = function() {
		return this.getControl(Redx_Map_Controler.CONTROL_DRAG_ZOOM);
	}

	/**
	 * verändert die Position des DragZoom Controls je nach MapType
	 */
	this.adjustDragZoomControlPosition = function() {
		var pos_x = 10;
		var pos_y;
		switch (this.getGMap().getCurrentMapType().getUrlArg()) {
			case 'm': // map
			pos_y = 270;
			break;
			case 'k': // satelite
			pos_y = 290;
			break;
			case 'h': // hybrid
			pos_y = 290;
			break;
			case 'p': // gelaende
			pos_y = 260;
			break;
			default:
			pos_y = 280;
		}
		this.addDragZoomControl(new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(pos_x, pos_y)));
	}

	/**
	* Hinzufügen eines MapControl-Objekts
	*
	* @param GSmallMapControl, GLargeMapControl control
	*/
	this.addMapControl = function(control) {
		this.addControl(control, Redx_Map_Controler.CONTROL_MAP);
	}

	this.getMapControl = function() {
		return this.getControl(Redx_Map_Controler.CONTROL_MAP);
	}

	this.removeMapControl = function() {
		this.removeControl(Redx_Map_Controler.CONTROL_MAP);
	}

	/**
	* Hinzufügen eines MapTypeControl-Objekts
	*
	* @param GMapTypeControl, GHierarchicalMapTypeControl, GMenuMapTypeControl control
	*/
	this.addMapTypeControl = function(control) {
		this.addControl(control, Redx_Map_Controler.CONTROL_MAP_TYPE)
	}

	this.getMapTypeControl = function() {
		return this.getControl(Redx_Map_Controler.CONTROL_MAP_TYPE);
	}

	this.removeMapTypeControl = function() {
		this.removeControl(Redx_Map_Controler.CONTROL_MAP_TYPE);
	}

	/**
	* Hinzufügen eines ScaleControl-Objekts
	*
	* @param GScaleControl control
	*/
	this.addScaleControl = function(control) {
		this.addControl(control, Redx_Map_Controler.CONTROL_SCALE);
	}

	this.getScaleControl = function() {
		this.getControl(Redx_Map_Controler.CONTROL_SCALE);
	}

	this.removeScaleControl = function() {
		this.removeControl(Redx_Map_Controler.CONTROL_SCALE);
	}

	/**
	* Hinzufügen eines OverviewMap-Objekts
	*
	* @param GOverviewMapControl control
	*/
	this.addOverviewMapControl = function(control) {
		this.addControl(control, Redx_Map_Controler.CONTROL_OVERVIEW_MAP);
	}

	this.getOverviewMapControl = function(control) {
		this.getControl(Redx_Map_Controler.CONTROL_OVERVIEW_MAP);
	}

	this.removeOverviewMapControl = function() {
		this.removeControl(Redx_Map_Controler.CONTROL_OVERVIEW_MAP)
	}

	/**
	 * Fügt ein Control der Map hinzu
	 * @param control Object
	 * @param type Redx_Map_Controler.CONTROL_*
	 * @param position GPosition
	 * @return
	 */
	this.addControl = function(control, type, position) {
		this.removeControl(type);
		_controls.set(type, control);
		this.getGMap().addControl(control, position);
	}

	/**
	 * liefert ein bestimmtes Control 
	 * @param type Redx_Map_Controler.CONTROL_*
	 */
	this.getControl = function(type) {
		return _controls.get(type);
	}

	/**
	 * entfernt ein bestimmtes Control
	 * @param type Redx_Map_Controler.CONTROL_*
	 */
	
	this.removeControl = function(type) {
		if (typeof _controls.get(type) != 'undefined') {
			this.getGMap().removeControl(_controls.get(type));
			_controls.unset(type);
		}
	}
	
	/**
	 * erstellt default controls
	 * @param control_type Redx_Map_Controler.CONTROL_TYPE_SMALL, Redx_Map_Controler.CONTROL_TYPE_LARGE
	 */
	this.createControls = function(control_type) {
		//this.removeAllControls();
		if (control_type == Redx_Map_Controler.CONTROL_TYPE_LARGE) {
			this.createLargeControls();
		} else if (control_type == Redx_Map_Controler.CONTROL_TYPE_SMALL) {
			this.createSmallControls();
		}
		this.enableScrollWheelZoom();
	}
	
	/**
	 * erstellt Redx_Map_Controler.CONTROL_TYPE_SMALL Controls
	 */
	this.createSmallControls = function() {
		this.addMapControl(new GSmallMapControl());
		this.addMapTypeControl(new GMenuMapTypeControl());
	}

	/**
	 * erstellt Redx_Map_Controler.CONTROL_TYPE_LARGE Controls
	 * @return
	 */
	this.createLargeControls = function() {
		this.addMapControl(new GLargeMapControl());
		this.addMapTypeControl(new GMapTypeControl());
	}

	/**
	 * entfernt alle Controls
	 */
	this.removeAllControls = function() {
		this.removeMapControl();
		this.removeMapTypeControl();
		this.removeScaleControl();
		this.removeOverviewMapControl();
	}


	/**
	 * Errorhandling für DrivingDirections
	 */
	this.directionsErrors = function(){
		var me = this;
		if (me.getDrivingStatus().code == G_GEO_UNKNOWN_ADDRESS)
			alert("Die Start-Adresse konnte nicht zugeordnet werden. Bitte ergänzen Sie die Adresse um Informationen wie Strasse, PLZ oder Ort");
		else if (me.getDrivingStatus().code == G_GEO_SERVER_ERROR)
			alert("A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.\n Error code: " + me.getDrivingStatus().code);
		else if (me.getDrivingStatus().code == G_GEO_MISSING_QUERY)
			alert("The HTTP q parameter was either missing or had no value. For geocoder requests, this means that an empty address was specified as input. For directions requests, this means that no query was specified in the input.\n Error code: " + me.getDrivingStatus().code);
		else if (me.getDrivingStatus().code == G_GEO_BAD_KEY)
			alert("The given key is either invalid or does not match the domain for which it was given. \n Error code: " + me.getDrivingStatus().code);
		else if (me.getDrivingStatus().code == G_GEO_BAD_REQUEST)
			alert("A directions request could not be successfully parsed.\n Error code: " + me.getDrivingStatus().code);
		else 
			alert("Die Route konnte nicht ermittelt werden");
	}

	this.enableService = function(service) {
		this._services[service] = true;
	}

	this.serviceEnabled = function(service) {
		return this._services[service];
	}

	/**
	 * aktiviert und Initialisiert den GClientGeocoder zur Positionsbestimmung
	 */
	this.enableGeocoder = function() {
		this.enableService('geocoder');
		this._geocoder = new GClientGeocoder();
	}

	/**
	 * aktiviert und Initialisiert GDirections zur Routenberechnung
	 * @param drivings_container_id string
	 */
	this.enableDrivingDirections = function(drivings_container_id) {
		var me = this;
		this.enableService('directions');
		this.setDrivingsContainerId($(drivings_container_id));
		this._directions = new GDirections(this.getGMap(), this._drivings_container_id);
		GEvent.addListener(this._directions, "error", function() { me.directionsErrors(); });
	}

	/**
	 * Anfangs zoomlevel
	 * @param value integer
	 */
	this.setInitZoomLevel = function(value) {
		this._init_zoom_level = value;
	}

	this.getInitZoomLevel = function() {
		return this._init_zoom_level;
	}

	/**
	 * Zoomlevel wenn vom GClientGeocoder eine Adresse gefunden wurde
	 * @param value integer
	 */
	this.setAddressFoundZoomLevel = function(value) {
		this._address_found_zoom_level = value;
	}

	this.getAddressFoundZoomLevel = function() {
		return this._address_found_zoom_level;
	}

	/**
	 * setzt Container-ID für Routenberechnungs Ergebnis
	 * @param container string
	 */
	this.setDrivingsContainerId = function(container) {
		this._drivings_container_id = container;
	}

	this.getAddressStringFromFields = function() {
		var address_str = '';
		for (var i=0; i < this._geocoder_address_fields.length; i++) {
			address_str += $(this._geocoder_address_fields[i]).value + ' ';
		}
		return address_str;
	}

	this.setLatLngFormFieldIDs = function(lat_id, lng_id) {
		this._lat_field_id = lat_id;
		this._lng_field_id = lng_id;
	}

	/**
	* Sucht eine bestimmte Adresse und setzt an diese einen Marker
	* @param address String
	*/
	this.findAddress = function(address) {
		var me = this;
		this._geocoder.getLatLng(address, function(point) {
			if (point) {
				me.setInitZoomLevel(me.getAddressFoundZoomLevel());
				me.setAddressMarker(point, address);
				me.centerMapToPositionMarker();
				me.updateLatLngFormFields(point.lat(), point.lng());
			} else {
				alert("Adresse nicht gefunden: [" + address + "]");
			}
		});
	}

	this.addGeocoderAddressField = function(field_id) {
		this._geocoder_address_fields.push(field_id);
	}

	this.findAddressFromFields = function() {
		var address_str = this.getAddressStringFromFields();
		this.findAddress(address_str);
	}

	this.updateLatLngFormFields = function(lat, lng) {
		if (this._lat_field_id && this._lng_field_id) {
			$(this._lat_field_id).value = lat;
			$(this._lng_field_id).value = lng;
		}
	}

	/**
	 * setzt einen Marker an einen bestimmten Punkt mit Adressinformationen
	 * @param point GLatLng
	 * @param info_str string - Adressinformationen
	 */
	this.setAddressMarker = function(point, info_str) {
		var me = this;
		this._position_marker = this.getAddressMarker(point, info_str);
		me.getGMap().clearOverlays();
		me.getGMap().addOverlay(this._position_marker);
	}

	/**
	 * setzt einen Marker an einen bestimmten Punk
	 * @param point GLatLng
	 * @param info_str string - text/html fue Tooltip
	 * @param show_tooltip - ob Tooltip angezeigt werden soll 
	 */
	this.setMarker = function(point, info_str, show_tooltip) {
		var me = this;
		this._position_marker = new GMarker(point);
		GEvent.addListener(this._position_marker, "click", function() {
			me._position_marker.openInfoWindowHtml(info_str);
		});

		me.getGMap().addOverlay(this._position_marker);
		if (show_tooltip) {
			this._position_marker.openInfoWindowHtml(info_str);
		}
	}

	/**
	 * Zentriert die Map auf einen zuvor gesetzten Address Marker
	 * @param zoomlevel
	 * @return
	 */
	this.centerMapToPositionMarker = function(zoomlevel) {
		var me = this;
		if (!zoomlevel) zoomlevel = me.getInitZoomLevel();
		me.getGMap().setCenter(this._position_marker.getLatLng(), zoomlevel);
	}

	this.getAddressMarker = function(point, info_str) {
		var marker = new GMarker(point);
		GEvent.addListener(marker, 'click', function() {
			marker.openInfoWindowTabsHtml([new GInfoWindowTab('Address', info_str) ,
			new GInfoWindowTab('Coordinates',point.toString())]);

		});
		marker.content = info_str;
		return marker;
	}

	this.getDrivingStatus = function() {
		return this._directions.getStatus();
	}

	/**
	* Erstellt eine Anfahrtsbeschreibung
	* @param from String
	* @param to String
	*/
	this.createDrivingDirections = function(from, to, locale) {
		var me = this;
		var load_str = "from: " + from + " to: " + to;
		this._directions.load(load_str, {locale: locale, getPolyline: true});
	}
}

Redx_Map_Controler.prototype.addListener = function(event_type, event_function) {
	var me = this;
	GEvent.addListener(me.getGMap(), event_type, event_function);
}

/**
* Inititialisierung des Redx_Map_Controlers
* Erzeugt eigentliche GMap2
* Grundsätzliche Stuerungselemente für Karte erzeugen
* Erzeugt Hilfsobjekte für Adresssuche und Anfahrtsplan
*/
Redx_Map_Controler.prototype.init = function() {
	this.setMapContainer(document.getElementById(this.getMapContainerId()));
	Redx_Map_Controler.addInstance(this);
	this._map = new GMap2(this._map_container);
	this.addMapType(G_PHYSICAL_MAP);
	this.createControls(this._control_type);
}

Redx_Map_Controler.prototype.setControlType = function(control_type) {
	this._control_type = control_type;
}

function getCSSRule(ruleName) {               // Return requested style obejct
	ruleName=ruleName.toLowerCase();                       // Convert test string to lower case.
	if (document.styleSheets) {                            // If browser can play with stylesheets
		for (var i=0; i<document.styleSheets.length; i++) { // For each stylesheet
			var styleSheet=document.styleSheets[i];          // Get the current Stylesheet
			var ii=0;                                        // Initialize subCounter.
			var cssRule=false;                               // Initialize cssRule.
			do {	                                        // For each rule in stylesheet
				try {
					if (styleSheet.cssRules) {                    // Browser uses cssRules?
						cssRule = styleSheet.cssRules[ii];         // Yes --Mozilla Style
					} else {                                      // Browser usses rules?
						cssRule = styleSheet.rules[ii];            // Yes IE style.
					}                                             // End IE check.
					if (cssRule && cssRule.selectorText)  {                               // If we found a rule...
						if (cssRule.selectorText.toLowerCase() == ruleName) { //  match ruleName?
							return true;                         // return true, class deleted.
						}                                          // End found rule name
					}                                             // end found cssRule
					ii++;                                         // Increment sub-counter
				}
				catch (e) {
					// NS_ERROR_DOM_SECURITY_ERR if its an external css!
				}
			} while (cssRule)                                // end While loop
		}                                                   // end For loop
	}                                                      // end styleSheet ability check
	return false;                                          // we found NOTHING!
}

/**
* Spezial Layer Control - fügt control zum ein/aus-blenden von z.B. Panoramio und Wikipedia layern
* zu verwenden über Redx_Map_Controler::addSpecialLayerControl()
* zuvor Layer mit addSpecialLayer() hinzufügen
* CSS-Klassen:
* 		- redx_special_layer_item
* CSS-IDs
*		- redx_special_layer_container
*		- redx_special_layer_header
*		- redx_special_layer_down_img
*		- redx_special_layer_body
*
* @param mc Redx_Map_Controler
* @param control_type string - '' oder 'menu'
* @param display_name string - anzeigename
*/
function Redx_Map_Special_Layer_Control(mc, control_type, display_name) {
	this._mc = mc;
	this._control_type = control_type;
	this._display_name = display_name;

	this._container_id = "redx_special_layer_container";
	this._header_id = "redx_special_layer_header";
	this._body_id = "redx_special_layer_body";
	this._down_arrow_id = "redx_special_layer_down_img";
	this._item_id = "redx_special_layer_item";
}

Redx_Map_Special_Layer_Control.prototype = new GControl();
Redx_Map_Special_Layer_Control.prototype.initialize = function() {
	var me = this;
	if (this._control_type != 'menu') {
		this._control_type = '';
	}

	var container = document.createElement("div");

	container.id = this._container_id;

	if (this._control_type == 'menu') {
		// dropdown
		var header = document.createElement("div");
		header.id = this._header_id;
		header.appendChild(document.createTextNode(this._display_name));

		var select_div = document.createElement("div");
		select_div.style.display = "none";
		select_div.id = this._body_id;
		var down_arrow = document.createElement("img");
		down_arrow.id = this._down_arrow_id;
		down_arrow.src = "http://maps.google.com/intl/de_ALL/mapfiles/down-arrow.gif";
		header.appendChild(down_arrow);
		// close when clicking somewhere
		GEvent.addDomListener(me._mc.getGMap(), "click", function() {
			select_div.style.display = "none";
		});

		// add items
		for (var key in me._mc._special_layers) {
			select_div.appendChild(this.addItem(key));
		}

		GEvent.addDomListener(header, "click", function(e) {
			var display = select_div.style.display;
			if (display == "block") {
				select_div.style.display = "none";
			}
			else {
				select_div.style.display = "block";
			}
		});
		container.appendChild(header);
		container.appendChild(select_div);
	}
	else {
		this._control_type = '';
		for (var key in me._mc._special_layers) {
			container.appendChild(this.addItem(key));
		}
	}
	
	// special layer funktionieren nicht mit OpenStreetMap Karten
	// ausblenden des Controls wenn OSM-Karte ausgewählt wird
	GEvent.addListener(me._mc.getGMap(), "maptypechanged", function() {
		var type = me._mc.getGMap().getCurrentMapType().getUrlArg();
		if (type == 'm' || type == 'k' || type == 'h' || type == 'p') {
			$(me._container_id).style.display = "block";
		}
		else {
			for (var key in me._mc._special_layers) {
				if ($(key) && $(key).checked) {
					$(key).click();
				}
				$(me._container_id).style.display = "none";
			}
		}
	});

	me._mc.getGMap().getContainer().appendChild(container);
	if (this._control_type == 'menu') {
		me.initDropDownStyles();
	}
	else {
		me.initStyles();
	}
	return container;
}

/**
* Default styles für Special_Layer_Control in DropDown Darstellung, kann per CSS überschrieben werden
*/
Redx_Map_Special_Layer_Control.prototype.initDropDownStyles = function() {
	if (!getCSSRule("#"+this._container_id)) {
		var a = $(this._container_id);
		a.style.backgroundColor = "#FFFFFF";
		a.style.position = "absolute";
		a.style.top = "7px";
		a.style.right = "100px";
		a.style.width = "100px";
	}

	if (!getCSSRule("#"+this._header_id)) {
		var obj = $(this._header_id)
		obj.style.border = "1px solid #000000";
		obj.style.cursor = "pointer";
		obj.style.padding = "0 2px 0 2px";
	}

	if (!getCSSRule("#"+this._body_id)) {
		var obj = $(this._body_id)
		obj.style.border = "1px solid #000000";
	}

	if (!getCSSRule("#"+this._down_arrow_id)) {
		var obj = $(this._down_arrow_id);
		obj.style.position = "absolute";
		obj.style.top = "4px";
		obj.style.right = "5px";
		obj.style.width = "13px";
		obj.style.height = "9px";
	}

	if (!getCSSRule("."+this._item_id)) {
		var obj = $(this._item_id)
		obj.style.cssFloat = "left";
	}
}

/**
* Default styles für Special_Layer_Control, kann per CSS überschrieben werden
*/
Redx_Map_Special_Layer_Control.prototype.initStyles = function() {
	if (!getCSSRule("#"+this._container_id)) {
		var a = $(this._container_id);
		a.style.backgroundColor = "#FFFFFF";
		a.style.position = "absolute";
		a.style.border = "1px solid #000000";
		a.style.top = "7px";
		a.style.right = "100px";
	}

	if (!getCSSRule("."+this._item_id)) {
		var obj = $$("div."+this._item_id); // by className
		for(var i = 0; i < obj.length; i++) {
			obj[i].style.cssFloat = "left";
			obj[i].style.display = "inline";
			obj[i].style.padding = "0 2px 0 2px";
			obj[i].style.margin = "0";
		}
	}
}

/**
 * ein Eintrag dem ControlElement hinzufügen
 * @param item_key string
 */
Redx_Map_Special_Layer_Control.prototype.addItem = function(item_key) {
	var me = this;
	var item_div = document.createElement("div");
	item_div.setAttribute("class", this._item_id);
	item_div.setAttribute("className", this._item_id); // IE
	item_div.id = this._item_id;
	var input_field = document.createElement("input");
	input_field.type = "checkbox";
	input_field.id = item_key;

	GEvent.addDomListener(input_field, "click", function(e) {
		me.toggleLayer(e.target.id);
	});

	var input_label = document.createElement("label");
	input_label.setAttribute("for", item_key);
	input_label.appendChild(document.createTextNode(me._mc._special_layers[item_key]));
	item_div.appendChild(input_field);
	item_div.appendChild(input_label);
	return item_div;
}

/**
 * ein und ausblenden von layern
 * @param key string
 */
Redx_Map_Special_Layer_Control.prototype.toggleLayer = function(key) {
	if (!this._mc._special_layers[key+'_layer']) {
		this._mc._special_layers[key+'_layer'] = new GLayer(key);
	}

	if (document.getElementById(key).checked) {
		this._mc.getGMap().addOverlay(this._mc._special_layers[key+'_layer']);
	} else {
		this._mc.getGMap().removeOverlay(this._mc._special_layers[key+'_layer']);
	}
}

/**
* Adresssuch Control
* zu verwenden über Redx_Map_Controler::addSearchControl()
* @param mc Redx_Map_Controler-Object
* @param error_msg string - Adresse nicht gefunden Fehler
* CSS-Klassen:
* 	- redx_map_search_input_grey
* CSS-IDs
*		- redx_map_search_container			- Haupt div
*		- redx_map_search_input					- input Feld
*		- redx_map_search_button				- Suchbutton
*/
function Redx_Map_Search_Control(mc, error_msg) {
	this._error_msg = error_msg;
	this._mc = mc;
	this._container_id = "redx_map_search_container";
	this._input_id = "redx_map_search_input";
	this._input_class_grey = "redx_map_search_input_grey";
	this._button_id = "redx_map_search_button";
}

Redx_Map_Search_Control.prototype = new GControl();
Redx_Map_Search_Control.prototype.initialize = function() {
	var me = this;
	var default_input_value = "Address";

	var container = document.createElement("div");
	container.id = this._container_id;

	var input = document.createElement("input");
	input.type = "text";
	input.value = default_input_value;
	input.id = this._input_id;
	input.setAttribute("class", this._input_class_grey); //For Most Browsers
	input.setAttribute("className", this._input_class_grey); //For IE; harmless to other browsers.

	// Adresse suchen bei "Enter"
	GEvent.addDomListener(input, "keyup", function(e) {
		if (e.keyCode == 13 && input.value != "")
			me.findLocationByAddress(input.value);
	});

	// default wert entfernen und css Klasse ändern
	GEvent.addDomListener(input, "click", function(e) {
		if (input.getAttribute("class") == me._input_class_grey) {
			input.setAttribute("class", ""); //For Most Browsers
			input.setAttribute("className", ""); //For IE; harmless to other browsers.
			input.style.color = "#000000";
			input.value = "";
		}
	});

	// default wert wieder einfügen wenn feld leer und Focus lost
	GEvent.addDomListener(input, "blur", function() {
		if (input.value == "") {
			input.value = default_input_value;
			input.setAttribute("class", me._input_class_grey); //For Most Browsers
			input.setAttribute("className", me._input_class_grey); //For IE; harmless to other browsers.
			if (!getCSSRule("."+me._input_class_grey)) {
				input.style.color = "#808080";
			}
		}
	});

	// inhalt des textfeldes entfernen
	GEvent.addDomListener(input, "dblclick", function(e) {
		input.value = "";
	});

	container.appendChild(input);

	var button = document.createElement("img");
	button.src = "/res/pics/search_trans.gif";
	button.id = this._button_id;

	GEvent.addDomListener(button, "click", function() {
		if (input.value != "" && input.value != default_input_value) {
			me.findLocationByAddress(input.value);
		}
	});
	container.appendChild(button);

	me._mc.getGMap().getContainer().appendChild(container);
	this.initStyles();
	return container;
}

/**
 * Default styles für SearchControl, kann per CSS überschrieben werden
 */
Redx_Map_Search_Control.prototype.initStyles = function() {
	if (!getCSSRule("#"+this._container_id)) {
		var obj = $(this._container_id);
		obj.style.position = "relative";
		obj.style.backgroundColor = "#FFFFFF";
		obj.style.border = "1px solid #000000";
		obj.style.height = "17px";
		obj.style.padding = "0px";
		obj.style.margin = "0px";
	}

	if (!getCSSRule("#"+this._input_id)) {
		var obj = $(this._input_id);
		obj.style.position = "relative";
		if (navigator.appName == "Microsoft Internet Explorer") {
			obj.style.top = "-3px";
		}
		else {
			obj.style.top = "1px";
		}
		obj.style.left = "1px";
		obj.style.border = "0";
		obj.style.cssFloat = "left";
		obj.style.padding = "0px";
		obj.style.margin = "0px";
	}

	if (!getCSSRule("."+this._input_class_grey)) {
		var obj = $(this._input_id);
		obj.style.color = "#808080";
	}

	if (!getCSSRule("#"+this._button_id)) {
		var obj = $(this._button_id);
		obj.style.position = "relative";
		obj.style.left = "2px";
		obj.style.top = "2px";
		obj.style.marginRight = "3px";
		obj.style.width = "15px";
		obj.style.height = "15px";
		obj.style.cursor = "pointer";
		obj.style.cssFloat = "left";
	}
}

/**
 * sucht einen Punk anhand einer Adresse
 * @param address string
 */
Redx_Map_Search_Control.prototype.findLocationByAddress = function(address) {
	var me = this;
	me._mc.enableGeocoder();
	me._mc._geocoder.getLatLng(address, function (point) {
		if(point) {
			me._mc.setCenter(point, 10);
		} else {
			alert(me._error_msg);
		}
	});
}

Redx_Map_Search_Control.prototype.getDefaultPosition = function() {
	return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(80, 10));
}
