var googleMapsLibraries = [];
/*googleMapsLibraries.push('markerclusterer.js');*/
googleMapsLibraries.push('smartinfowindow.js');

/**
 * GoogleMaps class
 * 
 * @param elementid
 * @param apikey
 * @param width
 * @param height
 * @param type
 * @param points
 * @return
 */
function GoogleMaps(elementid, apikey, width, height, type, points, editable, max, enablesearch, enableoverview, enablecluster, initCallback,position,moveable,markerImage,clusterImage,routeMarkerVisibility,tryClientLocation,rewrite,loadJavascript,loadCss)
{
	/**
	 * @todo modify by params
	 */	
	this.images = [];
	this.setApiKey(apikey);
	this.setElementId(elementid); 
	this.addMap(elementid, this);
	
	this.loadJavascript = json_decode(base64_decode(loadJavascript));
	this.loadedJavascript = 0;
	this.loadCss = json_decode(base64_decode(loadCss));
	this.loadedCss = 0;
	this.markerImage = markerImage;
	this.clusterImage = clusterImage;
	this.images.marker = [];
	
	this.editable = editable;
	this.moveable = moveable;
	this.routeMarkerVisibility = routeMarkerVisibility;
	this.tryClientLocation = tryClientLocation;
	
	// can be modified:
	this.width = width;
	this.height = height;
	this.type = type;
	this.points = json_decode(points);
	this.position = new Array(parseFloat(position[0]), parseFloat(position[1]), parseInt(position[2]));
	this.max = parseInt(max);
	this.enablesearch = enablesearch;
	this.enableoverview = enableoverview;
	this.enablecluster = enablecluster;
	this.initCallback = initCallback;
	
	// a list of markers used to define a polyline
	this.markers = [];

	if (GoogleMaps.prototype.initialized)
	{
		this.initMap();
	}

}

GoogleMaps.maps = [];
GoogleMaps.mapIds = [];
GoogleMaps.prototype.initialized = false;


/**
 * methods
 */

/**
 * sets the google api key
 * 
 * @param key
 * @return void
 */
GoogleMaps.prototype.setApiKey = function (key)
{
	this.apiKey = key;
};

/**
 * sets the element id
 * 
 * @param elementid
 * @return sets the elementid
 */
GoogleMaps.prototype.setElementId = function (elementid)
{
	this.elementid = elementid;
};

/**
 * add this map to maplist
 * 
 * @param elementid
 * @param GoogleMaps map
 * @return void
 */
GoogleMaps.prototype.addMap = function(elementid, map)
{
	if (GoogleMaps.maps[elementid])
	{
		GoogleMaps.maps[elementid].destroy();
	}
	GoogleMaps.maps[elementid] = map;
	GoogleMaps.mapIds.push(elementid);
};

/**
 * 
 * @return
 */
GoogleMaps.prototype.destroy = function()
{

};

/**
 * get a map object from list
 * 
 * @param elementid
 * @return GoogleMaps
 */
GoogleMaps.prototype.getMap = function(elementid)
{
	return GoogleMaps.maps[elementid];
};

GoogleMaps.prototype.onSearch = function(event)
{
	var searchStr = this.searchInput.value;
	var googleMap = this;
	var geocoder = new google.maps.Geocoder();
	var map = googleMap.map;
	
    if (geocoder) 
    {
        geocoder.geocode({'address': searchStr}, function(results, status) 
        {
          if (status == google.maps.GeocoderStatus.OK) 
          {
             //googleMap.hideAllMarkers();	  
           map.setCenter(results[0].geometry.location);
		   googleMap.addMarker(results[0].geometry.location.lat(), results[0].geometry.location.lng());
			 setLocationInfo(googleMap, results[0].geometry.location);

        	  //map.fitBounds(results[0].geometry.bounds);
        	  Element.fire($(googleMap.elementid), 'googlemaps:gotSearchResults', {googleMap: googleMap, results: results});
          } else {
            //alert("Geocoder failed due to: " + status);
        	// just do nothing if no data could be get
          }
        });
    }
};

GoogleMaps.prototype.allScriptsLoaded = function()
{
	return this.loadJavascript.length == this.loadedJavascript;
};

GoogleMaps.prototype.loadJavascripts = function()
{
	if (this.loadJavascript)
	{
		var x;
		for(x=0;x<this.loadJavascript.length;x++)
		{
			var script = this.createElement('SCRIPT');
			script.type = 'text/javascript';
			script.src = this.loadJavascript[x];
			$$('head')[0].appendChild(script);
			var googlemaps = this;
			Element.observe(script, "load", function ()
			{
				googlemaps.loadedJavascript++;
				
				if (googlemaps.allScriptsLoaded())
				{
					Element.fire(googlemaps.elementid, 'googlemaps:javascriptLoaded');
				}
			});
		}
		if (x==0)
		{
			Element.fire(this.elementid, 'googlemaps:javascriptLoaded');
		}
	}
	else
	{
		Element.fire(this.elementid, 'googlemaps:javascriptLoaded');
	}
};

GoogleMaps.prototype.loadCssFiles = function()
{
	if (this.loadCss)
	{
		for(var x=0;x<this.loadCss.length;x++)
		{
			var link = this.createElement("LINK");
			link.rel = "stylesheet";
			link.type = "text/css";
			link.href = this.loadCss[x];
			$$('head')[0].appendChild(link);
		}
	}
};

GoogleMaps.prototype.initAll = function()
{
	GoogleMaps.prototype.initialized = true;

	for (var i=0;i<GoogleMaps.mapIds.length;i++)
	{
		var mapId = GoogleMaps.mapIds[i];
		var map = GoogleMaps.maps[mapId];
		map.initMap();
	}
};

/**
 * initialize map
 * 
 * @return void
 */
GoogleMaps.prototype.initMap = function()
{
	if (this.map) return;

	if (!this.markerImage)
	{
		this.images.marker.src = "http://"+window.location.hostname+  "/layout/images/gmapicon.png";
		this.images.marker.width = 44;
		this.images.marker.height = 65;
	}
	else
	{
		this.images.marker.src = this.markerImage[0];
		this.images.marker.width = this.markerImage[1];
		this.images.marker.height = this.markerImage[2];
	}
	if (!this.clusterImage)
	{
		this.images.cluster = "http://"+window.location.hostname+  FormGenerator.path + "images/googlemaps/cloud.png";
	}
	else
	{
		this.images.cluster = this.clusterImage;
	}

	
	this.frame_head = $$('head')[0];
	this.searchDiv = this.createElement('div');
	this.searchDiv.id = this.elementid+'_search';
	this.mapDiv = this.createElement('div');
	this.mapDiv.id = this.elementid+'_map';
	this.mapDiv.style.width = this.width;
	this.mapDiv.style.height = this.height;	

	$('mapdiv_'+this.elementid).innerHTML = '';
	$('mapdiv_'+this.elementid).appendChild(this.searchDiv);
	$('mapdiv_'+this.elementid).appendChild(this.mapDiv);

	var zoom = 8;
	var latlng = null;
	var clientLocation = null;
	if (google.loader.ClientLocation)
	{
		/* get location by googles clientlocation api */
		clientLocation = new google.maps.LatLng(google.loader.ClientLocation.latitude,google.loader.ClientLocation.longitude);
	}
	/* define start position */
	if (this.tryClientLocation && clientLocation!=null)
	{
		/* set position got by googles clientlocation api */
		zoom = 11;
		latlng = clientLocation;
    }
    else
    {
    	/* set default position */
    	zoom = this.position[2];
    	latlng = new google.maps.LatLng(this.position[0],this.position[1]);		
	}
    var mapOptions = {
    		zoom: zoom,
    		center: latlng,
    		mapTypeId: google.maps.MapTypeId.TERRAIN
    };
	this.map = new google.maps.Map(this.mapDiv, mapOptions);
	if (clientLocation!=null)
	{
		Element.fire($(this.elementid), 'googlemaps:clientLocation', {googleMap: this, location: clientLocation});		
	}

	// define user interface 
	if (!this.moveable)
	{
		//this.map.disableDragging();
		this.map.setOptions({
			draggable: false, 
			disableDoubleClickZoom: true,
			scrollwheel: false
		});
	}
	else
	{
		//this.map.addControl(new this.frame.GSmallMapControl());
		//this.map.setUIToDefault();
	}

	if (this.enablesearch)
	{
		this.loadSearchControl();
	}
	if (this.enableoverview)
	{ 
		//this.map.addControl(new this.frame.GOverviewMapControl());
	}

	// define icons
	this.icons = {};
	/*this.icons.marker = new google.maps.MarkerImage(
			this.images.marker.src,
			new google.maps.Size(this.images.marker.width, this.images.marker.height),
			new google.maps.Point(Math.ceil(this.images.marker.width/2), this.images.marker.height),
			new google.maps.Point(32,1));
	*/

	// define clusterer
	if (this.enablecluster)
	{
		var styles = {
			height: 65,
			width: 65,
			anchor: [15, 40]
		};
		var clusterOptions = {
			imagePath: this.images.cluster,
			imageExtension: 'png',
			maxZoom: 12,
			styles: styles
		};
		this.cluster = new MarkerClusterer(this.map, [], clusterOptions);
	}

	var googlemap = this;

	google.maps.event.addListener(this.map, "bounds_changed", function()		
	{
		// get map bounds
		var visiblebounds = googlemap.map.getBounds();
		googlemap.bounds = [];
		googlemap.bounds.NE = visiblebounds.getNorthEast();
		googlemap.bounds.SW = visiblebounds.getSouthWest();	
		googlemap.showPoints();
	});

	Element.observe(this.elementid, 'googlemaps:javascriptLoaded', function()
	{
		Element.fire($(googlemap.elementid), 'googlemaps:loaded', {googleMap: googlemap});
		if (googlemap.initCallback)
		{
			if (googlemap.map.getBounds()==null)
			{
				var listener = google.maps.event.addListener(googlemap.map, "bounds_changed", function()		
				{
					var visiblebounds = googlemap.map.getBounds();
					googlemap.bounds = [];
					googlemap.bounds.NE = visiblebounds.getNorthEast();
					googlemap.bounds.SW = visiblebounds.getSouthWest();	
					call_user_func(googlemap.initCallback, googlemap.elementid);
					google.maps.event.removeListener(listener);
				});
			}
			else
			{
				call_user_func(googlemap.initCallback, googlemap.elementid);
			}
		}
	});
	this.loadJavascripts();	
	this.loadCssFiles();
};

GoogleMaps.prototype.createElement = function(tagName)
{
	//return this.frame_content.createElement(tagName);
	return document.createElement(tagName);
};

/**
 * loads the search control
 *
 * @return void
 */
GoogleMaps.prototype.loadSearchControl = function()
{	
	var div = this.searchDiv;
	var searchText = this.createElement('span');
	searchText.innerHTML = 'Suchen: ';
	div.style.fontFamily = "Verdana,Arial,sans-serif";
	div.style.fontSize = "11px";
	div.appendChild(searchText);
	var input = this.createElement('input');
	input.id='map_searchinput_'+this.elementid;
	input.type = 'text';
	div.appendChild(input);
	this.searchInput = input;
	var submitButton = this.createElement('input');
	submitButton.type = 'button';
	submitButton.value = 'Suchen';
	div.appendChild(submitButton);
	this.searchSubmitButton = submitButton;
	var map = this;
	this.lastSearched = '';
	Element.observe(submitButton,'click', GoogleMaps.prototype.onSearch.bindAsEventListener(map));
	Element.observe(this.searchInput, 'keypress', GoogleMaps.prototype.searchKeyPressEvent.bindAsEventListener(map));
	this.searchInput.setAttribute('onkeypress', 'return GoogleMaps.prototype.disableReturnForSearchField(this, event)');
};

GoogleMaps.prototype.disableReturnForSearchField = function(input, event)
{
	var keyCode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
	if (keyCode == 13) {
		return false;
	}
	return true;
};

GoogleMaps.prototype.searchKeyPressEvent =  function(event)
{
	if (event.keyCode==13) // ENTER
	{
		this.onSearch(event);
	}
};

/**
 * defines html of info window
 *
 * @param marker
 * @param html
 * @param result
 *
 * @return html-element
 */
GoogleMaps.prototype.searchMarkerHtml = function(marker,html,result)
{
	var div = document.createElement('DIV');
	div.innerHTML = '' + result.titleNoFormatting;
	return div;
};

/**
 * decision which type will be used 
 * 
 * @return void
 */
GoogleMaps.prototype.showPoints = function()
{
    if (this.type == 'location')
    {
    	this.displayLocation();
    }
    else if (this.type == 'route')
    {
    	this.displayRoute();
    }
};

/**
 * adds a marker
 * 
 * @return GMarker
 */
GoogleMaps.prototype.addMarker = function(lat,lon)
{
	var pos = new google.maps.LatLng(lat, lon);
	var marker = new google.maps.Marker({
		position: pos
	});
	
	marker.setIcon(this.images.marker.src);
	//marker.setIcon(this.icons.marker);

	this.markers.push(marker);
	
	if (this.editable)
	{
		this.addLineMarkerHandlers(marker);
		marker.setDraggable(true);
	}
	else
	{
		marker.setDraggable(false);
	}

	if (this.enablecluster && this.type == 'location' && this.max!=1)
	{
		this.cluster.addMarker(marker);
	}
	else
	{
		marker.setMap(this.map);
	}
	return marker;
};

/**
 * hides all makers added by addMarker
 * @return
 */
GoogleMaps.prototype.hideAllMarkers = function()
{
	var markers = this.getMarkers();
	for (var i=0;i<markers.length;i++)
	{
		markers[i].setMap(null);
	}
};

/**
 * type == location
 * 
 * @return void
 */
GoogleMaps.prototype.displayLocation = function()
{
	// just to use "this" as "googlemap" in event listener
	var googlemap = this;
	if (this.markers.length==0 && this.points && this.points[0] && this.points[0]['lat'])
	{

		var limit = this.points.length;
		if (limit > this.max && this.max != 0) limit = this.max;
		for (var i=0; i<limit; i++)
		{
			this.addMarker(this.points[i]['lat'], this.points[i]['lon']);
		}
		googlemap.setValue();
	}

	if (!this.clickListener && this.editable)
	{
		this.clickListener = google.maps.event.addListener(this.map, "click", function (event)
		{
			var point = event.latLng;
			if (typeof point == 'undefined') return;
			
			if (googlemap.numMarkers() == 0 || googlemap.max == 0 || googlemap.numMarkers() < googlemap.max)
			{	
				var marker = googlemap.addMarker(point.lat(), point.lng());
				// marker created
				
				// event listener for dragging marker
				googlemap.addLineMarkerHandlers(marker);
			}
			else if (googlemap.firstMarker() && googlemap.max == 1)
			{
				// moving marker
				googlemap.firstMarker().setPosition(point);
			}
			Element.fire($(googlemap.elementid), 'googlemaps:locationChanged', {googleMap : googlemap});			
			googlemap.setValue();
		});
	}
};

/**
 * type == route
 * 
 * @return void
 */
GoogleMaps.prototype.displayRoute = function()
{
	var googlemap = this;
	if (this.markers.length==0 && this.points && this.points[0])
	{
		for (var i=0;i<this.points.length;i++)
		{
			var pos = new google.maps.LatLng(this.points[i]['lat'], this.points[i]['lon']);
			
			var marker = this.addMarker(pos.lat(), pos.lng());
		}
		this.hideAllMarkers();
		if (!this.editable && this.routeMarkerVisibility!='hidden')
		{
			var m = this.getMarkers();
			if (this.routeMarkerVisibility=='all')
			{
				for (var i=0;i<m.length;i++)
				{
					m[i].setMap(this.map);
				}
			}
			else if (this.routeMarkerVisibility=='start')
			{
				m[0].setMap(this.map);
			}
			else if (this.routeMarkerVisibility=='end')
			{
				m[m.length-1].setMap(this.map);
			}
			else if (this.routeMarkerVisibility=='start-end')
			{
				m[0].setMap(this.map);
				m[m.length-1].setMap(this.map);
			}
		}
		this.printLine();
		googlemap.setValue();		
		if (this.polylineBounds && !this.polylineBounds.isEmpty())
		{
			this.map.fitBounds(this.polylineBounds);
		}
	}
	
	if (!this.clickListener && this.editable)
	{
		this.clickListener = google.maps.event.addListener(googlemap.map, "click", function(event)
		{
			var point = event.latLng;
//			alert('click event: '+var_dump(event));
			
			if (typeof point == 'undefined') return;

			if (googlemap.numMarkers() == 0 || googlemap.max == 0 || googlemap.numMarkers() < googlemap.max)
			{
				googlemap.addMarker(point.lat(), point.lng());

				googlemap.printLine();
				Element.fire($(googlemap.elementid), 'googlemaps:routeChanged', {googleMap : googlemap});
			}
		});
	}
};

/**
 * event handler for dragging a line marker
 * 
 * @param event
 * @return void
 */
GoogleMaps.prototype.dragLineMarker = function(event)
{
	if (this.type == 'route') this.printLine();
	else this.setValue();
};

/**
 * add handlers
 *  
 * @return void
 */
GoogleMaps.prototype.addLineMarkerHandlers = function(marker)
{
	var googlemap = this;
	google.maps.event.addListener(marker, "drag", GoogleMaps.prototype.dragLineMarker.bindAsEventListener(googlemap));
	google.maps.event.addListener(marker, "dblclick", GoogleMaps.prototype.removeLineMarker.bindAsEventListener(googlemap, marker));
};

/**
 * remove a line marker
 * 
 * @return void
 */
GoogleMaps.prototype.removeLineMarker = function(point,marker)
{
	marker.setMap(null);

	google.maps.event.clearInstanceListeners(marker);
	
	for(var i=0; i<this.markers.length; i++)
	{
		if (this.markers[i] === marker)
		{
			this.markers[i] = null;
		}
	}
	
	if (this.type == 'route')
	{
		this.printLine();
		Element.fire($(this.elementid), 'googlemaps:routeChanged', {googleMap : this});
	}
	else
	{
		Element.fire($(this.elementid), 'googlemaps:locationChanged', {googleMap : this});
	}
};

/**
 * prints a polyline between the line markers
 * 
 * @return void
 */
GoogleMaps.prototype.printLine = function()
{
	if (this.polyline)
	{
		this.polyline.setMap(null);
	}
	else
	{
		this.polyline = new google.maps.Polyline();
	}
	
	this.polylineBounds = new google.maps.LatLngBounds();
	var polylinePath = [];
	var markers = this.getMarkers();
	for(var i=0; i<markers.length; i++)
	{
		polylinePath.push(markers[i].getPosition());
		this.polylineBounds.extend(markers[i].getPosition());
	}

	this.polyline.setPath(polylinePath);
	this.polyline.setOptions({
		strokeColor: '#ff0000', 
		strokeWeight: 5, 
		strokeOpacity: .5 
	});
	
	this.polyline.setMap(this.map);	
	this.setValue();
};

/**
 * save the route in a hidden input field
 * 
 * @return void
 */
GoogleMaps.prototype.setValue = function()
{
	var value = new Array();
	var points = new Array();
	var markers = this.getMarkers();
	for(var i=0; i<markers.length; i++)
	{
		var point = markers[i].getPosition();
		var lat = point.lat();
		var lon = point.lng();
		lat = str_replace(',', '.', lat);
		lon = str_replace(',', '.', lon);			
		points.push({'lat':lat, 'lon':lon});
	}
	if (this.type=='route')
	{
		value = {'points': points, 'distance': this.getPathDistance(this.polyline.getPath())};
	}
	else
	{
		value = {'points': points};	
	}
	$(this.elementid).value = urlencode(base64_encode(serialize(value)));
	Element.fire($(this.elementid), 'googlemaps:update', {googleMap: this, value: value});
};

GoogleMaps.prototype.getPathDistance = function(path)
{
	var pathDistance = 0;
	for (i=0;i<path.getLength()-1;i++)
	{
		pathDistance += this.getPositionDistance(path.getAt(i), path.getAt(i+1));
	}
	return pathDistance;
};

GoogleMaps.prototype.getPositionDistance = function(pos1, pos2)
{
	var lat1 = pos1.lat();
	var lon1 = pos1.lng();
	var lat2 = pos2.lat();
	var lon2 = pos2.lng();
	
	var R = 6371; // km (change this constant to get miles)
	var dLat = (lat2-lat1) * Math.PI / 180;
	var dLon = (lon2-lon1) * Math.PI / 180;
	var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
		Math.cos(lat1 * Math.PI / 180 ) * Math.cos(lat2 * Math.PI / 180 ) *
		Math.sin(dLon/2) * Math.sin(dLon/2);
	
	var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
	var d = R * c;
	return d*1000;
};

/**
 * returns the actual number of markers
 * 
 * @return void
 */
GoogleMaps.prototype.numMarkers = function()
{
	var x = 0; 
	for(var i=0; i<this.markers.length; i++)
	{
		if (this.markers[i] != null)
		{
			x++;
		}
	}
	return x;
};

/**
 * returns the first marker or null
 * 
 * @return GMarker
 */
GoogleMaps.prototype.firstMarker = function()
{
	for(var i=0; i<this.markers.length; i++)
	{
		if (this.markers[i] != null)
		{
			return this.markers[i];
		}
	}
	return null;
};

/**
 * returns the last marker or null
 * 
 * @return GMarker
 */
GoogleMaps.prototype.lastMarker = function()
{
	var marker = null;
	for(var i=0; i<this.markers.length; i++)
	{
		if (this.markers[i] != null)
		{
			marker = this.markers[i];
		}
	}
	return marker;
};

/**
 * get all markers which are not null
 * 
 * @return Array
 */
GoogleMaps.prototype.getMarkers = function()
{
	var markerList = [];
	for(var i=0; i<this.markers.length; i++)
	{
		if (this.markers[i] != null)
		{
			markerList.push(this.markers[i]);
		}
	}
	return markerList;
};

GoogleMaps.prototype.loadNextLibrary = function()
{
	if (typeof GoogleMaps.libraryLoadIterator == 'undefined')
	{
		GoogleMaps.libraryLoadIterator = 0;
	}
	else
	{
		GoogleMaps.libraryLoadIterator++;
	}
	if (typeof googleMapsLibraries[GoogleMaps.libraryLoadIterator] != 'undefined')
	{
		var i = GoogleMaps.libraryLoadIterator;
		var library = googleMapsLibraries[i];
		if (!library.match(/^http:\/\//))
		{
			library = FormGenerator.path + library;
		}
		var script = FormGenerator.prototype.require('script',library,GoogleMaps.prototype.loadNextLibrary);
	}
	else
	{
		GoogleMaps.prototype.initAll();
	}
};

GoogleMaps.prototype.apiLoaded = function()
{
	GoogleMaps.prototype.loadNextLibrary();
};

GoogleMaps.prototype.loadApi = function()
{
	google.load("maps", "3", {callback: GoogleMaps.prototype.apiLoaded, other_params:"sensor=false"});	
};

Element.observe(document, 'dom:loaded', function()
{
	GoogleMaps.prototype.loadApi();
});

