var routeManager = new Object();
var conversationId;
var i8nMessages;
var radiusPolygon = null;
var zoomArea = new GLatLngBounds();
var currentCountryCode;

var mapWidth;

//defines whether a search for cabrio route should be executed on map load
var startSearch = false;
var searchOnStartUp = null;

//Will be set to true if an update request for a deferred search should be done
var doDeferredSearch = false;

var registerMapSearchMode;
var registerCreatePOIMode;

var defRouteSearchZoom;
var defPoiSearchZoom;

var zoomToZoomArea = true;

//will be set to true, if only view pois are on the map
var zoomToPOIView = false;
var viewPOIToZoom;
var elementCount = 0;

//save the id of the cabrio route the user had the mouse over
//to disable the route if the user gets to another one
var lastRouteMouseOver;

var overlappingMarkerStrategy;

//Constans for accessing i8n messages
var CLUSTER_ICON_TITLE = "js_cluster_icon_title";


function initMap(cid, defaultRouteSearchZoom, defaultPOISearchZoom, jsMessages, addResizeControl) {
	conversationId = cid;
	i8nMessages = jsMessages;
	defRouteSearchZoom = parseInt(defaultRouteSearchZoom);
	defPoiSearchZoom = parseInt(defaultPOISearchZoom);
	map.setUIToDefault();
	map.disableScrollWheelZoom();
	if (addResizeControl) {
		map.addControl(new ResizeMapControl());
	}
	setCurrentCountry();
	overlappingMarkerStrategy = new ClusteredStrategy();
	if (startSearch) {
		new GClientGeocoder().getLatLng(currentCountryCode, function(point) {
			map.setCenter(point);
			startSearch = false;
			disableMapSearchMode();
			enableMapSearchMode(doSearch);
			searchOnStartup();
		});
	} else {
		if (registerMapSearchMode) {
			// only register map search mode. Needed to pan the map to the place
			// before adding a route/poi to an event
			registerMapSearchMode = false;
			disableMapSearchMode();
			enableMapSearchMode(doSearch);
		} else if (registerCreatePOIMode) {
			handleCreatePOIMode();
		}
		// Updating the map with possible route information.
		updateRemoteData();
	}
	
	GEvent.addListener(map, "infowindowclose", function() {
		if (enableSearchDependentListeners) {
			enableSearchDependentListeners();
		}
	});
	GEvent.addListener(map, "infowindowopen", function() {
		if (disableSearchDependentListeners) {
			disableSearchDependentListeners();
		}
	});
}

function handleCreatePOIMode() {
	//enable CreatePOIMode at create poi page. click to create poi
	registerCreatePOIMode = false;
	disableCreatePOIMode();
	enableCreatePOIMode();
}

function setCurrentCountry() {
	// Setting the users current country. 
	// Default one is switzerland.
	currentCountryCode = 'CH';
	if (google.loader.ClientLocation)
		currentCountryCode = google.loader.ClientLocation.address.country_code;
	initGeocoder(currentCountryCode);
}

function countDirectionsRequest(mapUpdates) {
	var requestCounter = 0;
	for (var i = 0; i < mapUpdates.length; i++) {
		if (isDirectionsRequest(mapUpdates[i])) {
			requestCounter++;
		} 
	}
	return requestCounter;
}

function resetMap() {
	zoomArea = new GLatLngBounds();
	elementCount = 0;
	map.clearOverlays();
	overlappingMarkerStrategy.clear();
	routeManager = new Object();
	lastRouteMouseOver = null;
}

/*
 * This function is called:
 * - after every modification on the planned route. If
 * mapUpdateRequest are in the list, a google-request is done and the received
 * routes are sent to the server.
 * - to view the route on the map in the detail route view
 * - to view the route on the map after the route has been upladed
 * - to view the cabrioPOI's
 * - if a searchRequest is in the list, a local search is executed with the received keywords
 */
function refreshMap(mapUpdates) {
	//if nothing to draw on map -> center to users current country
	if (mapUpdates.length == 0) {
		//initGeocoder(countryCode);
		if (zoomToZoomArea) {
			new GClientGeocoder().getLatLng(currentCountryCode, function(point) {
				map.setCenter(point);
			});
		}
		resetMap();
	} else {
		resetMap();
		var numberOfDirectionsRequest = countDirectionsRequest(mapUpdates);
		createRouteCollector(numberOfDirectionsRequest);
		for (var i = 0; i < mapUpdates.length; i++) {
			var mapUpdate = mapUpdates[i];
			if (isDirectionsRequest(mapUpdate)) {
				computeUpdateDirection(mapUpdate.requestData.elementNr, mapUpdate.requestData.addresses);
			} else if (isLocalSearchRequest(mapUpdate)) {
				startLocalSearch(mapUpdate.requestData.localSearchKeywords);
			} else if (isSearchRequest(mapUpdate)) {
				doDeferredSearch = true;
			} else {
				elementCount++;
				zoomToPOIView = false;
				putToMap(mapUpdate.mapObject);
			}
		}
		//if no DirectionRequests -> center Map and send zoomArea to Server
		if (numberOfDirectionsRequest == 0) {
			if (zoomToPOIView) //zoom to POI if no other element is on the
				addBufferToZoomArea(viewPOIToZoom);
			overlappingMarkerStrategy.show();
			overlappingMarkerStrategy.refresh();
			// reset to avoid putting markers twice, but are on map before
			// the search
			overlappingMarkerStrategy.reset()
			if (zoomToZoomArea) {
				if (!doDeferredSearch)
					sendBoundsObject(map.getZoom()); //when search will be done, do not send the bounds
				map.setCenter(zoomArea.getCenter(), map.getBoundsZoomLevel(zoomArea));
			}
			if (doDeferredSearch) {
				startDeferredSearch();
			}
			if (registerCreatePOIMode && !zoomToPOIView) {
				//flag registerCreatePOIMode is set and not only a ViewPOI Marker is on the map
				//"!zoomToPOIView" is needed to avoid enabling the CreatePOIMode just after the poi is created
				handleCreatePOIMode();
			}
		}
	}
}

/*
 * This function is called after all PartRoutes are calculated and saved on the
 * server. They must be drawn to the map now. A search on the current map area
 * must be done now.
 */
function updatePartRoutes(mapUpdates) {
	//don't clear overlay
	for (var i = 0; i < mapUpdates.length; i++) {
		if (isSearchRequest(mapUpdates[i])) {
			doDeferredSearch = true;
		} else {
			putToMap(mapUpdates[i].mapObject);
		}
	}
	overlappingMarkerStrategy.show();
	// reset clusterMarker to avoid putting markers twice, but are on map before
	// the search
	overlappingMarkerStrategy.reset();
	map.setCenter(zoomArea.getCenter(), map.getBoundsZoomLevel(zoomArea));
	if (doDeferredSearch) {
		startDeferredSearch();
	} else {
		sendBoundsObject(map.getZoom());
	}
}

function updateSearchResult(mapUpdates) {
	doDeferredSearch = false;
	disableMapSearchMode();
	enableMapSearchMode(updateRemoteData);
	//don't clear overlay
	for (var i = 0; i < mapUpdates.length; i++) {
		var mapUpdate = mapUpdates[i];
		if (isLocalSearchRequest(mapUpdate)) {
			startLocalSearch(mapUpdate.requestData.localSearchKeywords);
		} else {
			putToMap(mapUpdate.mapObject);
		}
	}
	overlappingMarkerStrategy.show();
	overlappingMarkerStrategy.refresh(true);
}

//send bounds to the server
function sendBoundsObject(zoom) {
	sendBounds(map.getCenter().lat(), map.getCenter().lng(), zoom);
}

function isRoute(obj) {
	return obj.route != null;
}

function isDirectionsRequest(mapUpdate) {
	if (mapUpdate.requestData) {
		if (mapUpdate.requestData.addresses)
			return true;
	}
	return false;
}

function isLocalSearchRequest(mapUpdate) {
	if (mapUpdate.requestData) {
		if (mapUpdate.requestData.localSearchKeywords)
			return true;
	}
	return false;
}

function isSearchRequest(mapUpdate) {
	if (mapUpdate.requestData) {
		if (mapUpdate.requestData.deferredSearch)
			return true;
	}
	return false;
}



function putToMap(mapObject) {
	if (isRoute(mapObject)) {
		handleCabrioRoute(mapObject.route);
	} else {
		handlePOI(mapObject.poi);
	}
}

function handleCabrioRoute(cabrioRoute) {
	if (cabrioRoute.marker != null) {
		putCabrioRouteMarker(cabrioRoute.marker);
		putEncodedRoute(cabrioRoute, false);
	} else {
		putEncodedRoute(cabrioRoute, true);
	}
}

function putCabrioRouteMarker(marker) {
	var cabrioMarker = addMarker(marker, GEvent.callback(overlappingMarkerStrategy,overlappingMarkerStrategy.addRoute));
	
	GEvent.addListener(cabrioMarker, "mouseover", function() {
		var cabrioRouteRemote = routeManager[marker.cabrioRouteId]; 
		if (!cabrioMarker.isInfoWindowOpen) {
			putRoutePolyline(cabrioRouteRemote);
		}
//		if (lastRouteMouseOver && (lastRouteMouseOver != marker.cabrioRouteId)) {
//			map.removeOverlay(routeManager[lastRouteMouseOver].polyline);
//		}
//		lastRouteMouseOver = marker.cabrioRouteId;
	});
	
	GEvent.addListener(cabrioMarker, "mouseout", function() {
		//if infoWindow of cabrioMarker is opened, the route is not removed
		if (!cabrioMarker.isInfoWindowOpen) {
			map.removeOverlay(routeManager[marker.cabrioRouteId].polyline);
		}
	});
	
	GEvent.addListener(cabrioMarker, "infowindowclose", function() {
		map.removeOverlay(routeManager[marker.cabrioRouteId].polyline);
		cabrioMarker.isInfoWindowOpen = false;
	});

	//provide small infoWindow, if the map width is less than 400px
	if (mapWidth < 400) {
		var url =  "http://"+ window.location.host + "/route/infoWindowSmall/"+marker.cabrioRouteId+"?cid="+conversationId;
	} else {
		var url = "http://"+ window.location.host + "/route/infoWindow/"+marker.cabrioRouteId+"?cid="+conversationId;
	}
	
	GEvent.addListener(cabrioMarker,"click",function() {
		GDownloadUrl(url, function(html) { 
			cabrioMarker.openInfoWindowHtml(html);
			cabrioMarker.isInfoWindowOpen = true;
			//overlay route if not already done. e.g. InfowWindow of a marker is opened through the cluster selection
			putRoutePolyline(routeManager[marker.cabrioRouteId]);
    	}); 
	});  	
}

function putRoutePolyline(cabrioRouteRemote) {
	if (!cabrioRouteRemote.polyline) {
		/*
		 * if polyline was not created until now, create it and save it
		 * as property in the cabrioRouteRemote Object
		 */
		var mouseOverPolyline = createEncodedRoute(cabrioRouteRemote);
		cabrioRouteRemote.polyline = mouseOverPolyline;
	}
	map.addOverlay(cabrioRouteRemote.polyline);	
}

function addMarker(markerRemote, addToStrategy) {
	var icon = createIcon(markerRemote);
	var marker = new GMarker(new GLatLng(markerRemote.coordinate.lat, markerRemote.coordinate.lng),{title:markerRemote.title,icon: icon, draggable:markerRemote.draggable});
	marker.isInfoWindowOpen = false;
	zoomArea.extend(marker.getLatLng());
	//map.addOverlay(marker);
	//append used icon as property
	marker.iconLink = markerRemote.iconLink;
	marker = addToStrategy(marker);
	return marker;
}

function createIcon(markerRemote) {
	var icon = new GIcon(G_DEFAULT_ICON, markerRemote.iconLink);
	if (markerRemote.iconShadowLink != null) { 
		icon.shadow = markerRemote.iconShadowLink;
		icon.shadowSize = new GSize(markerRemote.shadowWidth, markerRemote.shadowHeight);
	}
	return icon;
}

function createEncodedRoute(cabrioRouteRemote) {
	return new GPolyline.fromEncoded({
		color: cabrioRouteRemote.color,
		points: cabrioRouteRemote.route.latlngs,
		levels: cabrioRouteRemote.route.levels,
		numLevels: cabrioRouteRemote.route.numLevels,
		zoomFactor: cabrioRouteRemote.route.zoomFactor
	});
}

function putEncodedRoute(cabrioRoute, visible) {
	/*
	 * Visible is set to false, if only the CabrioRouteMarker should
	 * be shown at first.
	 * The routeManager holds the route in an assoziative "array"
	 * to make it visible if mouse over CabrioRouteMarker
	 */
	if (!visible) {
		routeManager[cabrioRoute.marker.cabrioRouteId] = cabrioRoute;
	} else {
		var routeLine = createEncodedRoute(cabrioRoute);
		var mapObject = new MarkerPolyline(routeLine, cabrioRoute.startIcon, cabrioRoute.endIcon);
		map.addOverlay(mapObject);
		addPolylineToZoomArea(mapObject.polyline);
	}
	/*
	 * if Addresses of the CabrioRoute must be calculated by Google
	 * This is the case after uploading a new Route
	 */
	if (cabrioRoute.requestAddresses) {
		setAddressesOnCabrioRoute(routeLine);
	}
}

function handlePOI(poi) {
	if (poi.searchCenter) {
		putSearchCenterMarker(poi);
	} else if (poi.draggable) {
		putCabrioPOICreateMarker(poi);
	} else if (poi.showMapBlowup){
		putCabrioPOIViewMarker(poi);
	} else if (poi.radiusMarker) {
		putSearchRadius(poi);
	} else {
		putCabrioPOIMarker(poi);
	}
}

function putSearchRadius(radiusRemote) {
	newRadiusPolygon = new GPolygon(getLatLngArray(radiusRemote.buffer), "#f33f00", 1, 0.2, "#ff0000", 0.2);
	if (radiusRemote.visible) {
		if (radiusPolygon != null) {
			map.removeOverlay(radiusPolygon)
		}
		map.addOverlay(newRadiusPolygon);
	}
	radiusPolygon = newRadiusPolygon;
	addBufferToZoomArea(radiusRemote);
}

function printSearchAreaValue(print) {
	if (print) {
		map.addOverlay(radiusPolygon);
	} else {
		map.removeOverlay(radiusPolygon);
	}
}

function putSearchCenterMarker(markerRemote) {
	var searchCenter = addMarker(markerRemote);
	addBufferToZoomArea(markerRemote);
	//Make very short polyline to be able to show the search radius
	var polyline = getSmallPolylineFromStart(searchCenter.getLatLng());
	addPolylineToZoomArea(polyline);
}

function addBufferToZoomArea(markerRemote) {
	zoomArea.extend(new GLatLng(markerRemote.southWestBufferCoord.lat, markerRemote.southWestBufferCoord.lng));
	zoomArea.extend(new GLatLng(markerRemote.northEastBufferCoord.lat, markerRemote.northEastBufferCoord.lng));
}

function putCabrioPOIViewMarker(poi) {
	var poimarker = addMarker(poi, GEvent.callback(overlappingMarkerStrategy,overlappingMarkerStrategy.addPOI));
	viewPOIToZoom = poi;
	if (elementCount == 1) 
		zoomToPOIView = true;
	GEvent.addListener(poimarker,"click",function() { 
		poimarker.showMapBlowup();
	});
	disableCreatePOIMode();
}

function putCabrioPOIMarker(poi) {
	var poimarker = addMarker(poi, GEvent.callback(overlappingMarkerStrategy,overlappingMarkerStrategy.addPOI));
	var url = "http://"+ window.location.host + "/poi/infoWindow/"+poi.poiId+"?cid="+conversationId;
	
	GEvent.addListener(poimarker,"click",function() { 
		GDownloadUrl(url, function(html) { 
			poimarker.openInfoWindowHtml(html); 
    	}); 
	}); 
}

function putCabrioPOICreateMarker(poi) {
	var poimarker = addMarker(poi, GEvent.callback(overlappingMarkerStrategy,overlappingMarkerStrategy.addPOI));
	GEvent.addListener(poimarker,"dragend",function(latlng) { 
		reverseGeocode(geocoder, latlng, handleReverseGeocodeCreateMarker);
		zoomToZoomArea = false;//do not zoom/pan the map after draging to the new position
	}); 
}

function handleReverseGeocodeCreateMarker(response) {
	var place = response.Placemark[0];
	poiPositionVerified(place);
}

function getLatLngArray(myArray) {
	var returnArray = new Array();
	for (var i = 0; i < myArray.length; i++) {
		returnArray.push(new GLatLng(myArray[i].lat, myArray[i].lng));
	}
	return returnArray;
}

function addPolylineToZoomArea(polyline) {
	if (!zoomArea.containsBounds(polyline.getBounds())) {
		zoomArea.extend(polyline.getBounds().getSouthWest());
		zoomArea.extend(polyline.getBounds().getNorthEast());
	}
}

function getArrayOfVertexes(polyline) {
	var routePoints = new Array();
	var indexLastVertex = polyline.getVertexCount();
	for(var i = 0; i < indexLastVertex; i++) {
		routePoints.push(polyline.getVertex(i));
	}
	return routePoints;
}

function reverseGeocode(geoCoder, latLng, onReverseGeoCoded) {
	geoCoder.getLocations(latLng, onReverseGeoCoded);
}

function setAddressesOnCabrioRoute(polyline) {
	var g = new GClientGeocoder();
	reverseGeocode(g,polyline.getVertex(0), handleFromAddressResponse);
	reverseGeocode(g,polyline.getVertex(polyline.getVertexCount() - 1), handleToAddressResponse);
}

function handleFromAddressResponse(response) {
	var place = response.Placemark[0];
	var address = extractAddress(place);
	setCabrioRouteFromAddress(address["address"], address["plz"],
			address["city"], address["countryCode"], address["accuracy"],
			response.name);
}

function handleToAddressResponse(response) {
	var place = response.Placemark[0];
	var address = extractAddress(place);
	setCabrioRouteToAddress(address["address"], address["plz"],
			address["city"], address["countryCode"], address["accuracy"],
			response.name);
}

function getPlacemarkProperty(placemark,propertyname) {
  for ( var property in placemark) {
		if ((property == propertyname)) {
			return String(placemark[property]);
		} else if (typeof (placemark[property]) == 'object') {
			var r = this
					.getPlacemarkProperty(placemark[property], propertyname);
			if (r != null)
				return r;
		}
	}
	return null;
}

function setEmptyIfNull(obj) {
	if (obj == null)
		return "";
	else
		return obj;
}

function extractAddress(place) {
	var resultAddress = new Object();
	var locality;
	var city;
	var accuracy = place.AddressDetails.Accuracy;
	resultAddress["countryCode"] = "";
	resultAddress["city"] = "";
	resultAddress["plz"] = "";
	resultAddress["address"] = "";
	resultAddress["accuracy"] = accuracy;
	resultAddress["coordinate"] = place.Point.coordinates[1] + "," + place.Point.coordinates[0];
	if (accuracy != 9) {
		if (accuracy >= 1) {
			resultAddress["countryCode"] = place.AddressDetails.Country.CountryNameCode;
			resultAddress["areaName"] = setEmptyIfNull(getPlacemarkProperty(place,"AdministrativeAreaName"));
			if (accuracy >= 4) {
				var dependentLocalityName = getPlacemarkProperty(place, "DependentLocalityName");
				if (dependentLocalityName != null) {
					resultAddress["city"] = dependentLocalityName;
				} else {
					resultAddress["city"] = setEmptyIfNull(getPlacemarkProperty(place,"LocalityName"));
				}
				if (accuracy >= 5) {
					resultAddress["plz"] = setEmptyIfNull(getPlacemarkProperty(place, "PostalCodeNumber"));
					if (accuracy >= 6) {
						resultAddress["address"] = setEmptyIfNull(getPlacemarkProperty(place, "ThoroughfareName"));
					}
				}
			}
		}	
	}
    return resultAddress;	
}

/**************************************************************************
New Class: MarkerPolyline implements GOverlay
***************************************************************************/
function MarkerPolyline(route, startIconRemote, endIconRemote) {
	this.polyline = route;
	this.startMarker;
	this.endMarker;
	if (route) {
		if (startIconRemote != null) {
			var startIcon = createIcon(startIconRemote);
			this.startMarker = new GMarker(new GLatLng(startIconRemote.coordinate.lat, startIconRemote.coordinate.lng),{title:startIconRemote.title, icon: startIcon, zIndexProcess:this.topZIndex });
			this.startMarker.zIndexIncrement = 1000;
			//append used icon as property
			this.startMarker.iconLink = startIconRemote.iconLink;
			overlappingMarkerStrategy.addRoute(this.startMarker);
			//this.startMarker = new GMarker(this.polyline.getVertex(0));
		}
		if (endIconRemote != null) {
			var endIcon = createIcon(endIconRemote);
			this.endMarker = new GMarker(new GLatLng(endIconRemote.coordinate.lat, endIconRemote.coordinate.lng),{title:endIconRemote.title, icon: endIcon, zIndexProcess:this.topZIndex});
			this.endMarker.zIndexIncrement = 1000;
			//append used icon as property
			this.endMarker.iconLink = endIconRemote.iconLink;
			overlappingMarkerStrategy.addRoute(this.endMarker);
			//this.endMarker = new GMarker(this.polyline.getVertex(this.polyline.getVertexCount() - 1));
		}
	}
} 

MarkerPolyline.prototype = new GOverlay();

MarkerPolyline.prototype.topZIndex = function (marker, b) {
	//Make sure these icons are on top
	var oldIndex = GOverlay.getZIndex(marker.getPoint().lat());
	return oldIndex + marker.zIndexIncrement;
}

MarkerPolyline.prototype.hide = function () {
	this.polyline.hide();
//	if (this.startMarker) 
//		this.startMarker.hide();
//	if (this.endMarker)
//		this.endMarker.hide();
}

MarkerPolyline.prototype.show = function () {
	this.polyline.show();
//	if (this.startMarker)
//		this.startMarker.show();
//	if (this.endMarker)
//		this.endMarker.show();
}

MarkerPolyline.prototype.initialize = function (map) {
	this.polyline.initialize(map);
//	if (this.startMarker)
//		this.startMarker.initialize(map);
//	if (this.endMarker)
//		this.endMarker.initialize(map);
}

MarkerPolyline.prototype.remove = function () {
	this.polyline.remove();
//	if (this.startMarker)
//		this.startMarker.remove();
//	if (this.endMarker)
//		this.endMarker.remove();
}

MarkerPolyline.prototype.copy = function () {
	this.polyline.copy();
//	if (this.startMarker)
//		this.startMarker.copy();
//	if (this.endMarker)
//		this.endMarker.copy();
}

MarkerPolyline.prototype.redraw = function (force) {
	this.polyline.redraw(force);
//	if (this.startMarker)
//		this.startMarker.redraw(force);
//	if (this.endMarker)
//		this.endMarker.redraw(force);
}

function resizeMapDiv(newWidth) {
    //Resize the width of the div containing the map.
	var mapDOM = map.getContainer();
	var shadowDOM = $('capoShadowContainer');
	if (shadowDOM) { //shadowDOM is undefined on IE6
		shadowDOM.style.width = newWidth + "px";
		var shadowBottomDOM = $$('.capo-shadow-b')[0];
		shadowBottomDOM.style.width = (newWidth - 83) + "px";
		var shadowBottomLeftDOM = $$('.capo-shadow-bl')[0];
		shadowBottomLeftDOM.style.right = (newWidth - 42) + "px";
	}
	var oldCenter = map.getCenter();
	mapDOM.style.width = newWidth + "px";
    map.checkResize();
    map.setCenter(oldCenter);
}

/**
 * TabChangedListener
 */
function routePlanningTabChanged(currentTab) {
	updateRemoteData();
}

function routeUploadTabChanged(currentTab) {
	if (currentTab == "tab2") {
		enableCreatePOIMode();
		disableMapSearchMode();
	} else {
		disableCreatePOIMode();
		zoomToZoomArea = true;
	}
	updateRemoteData();
}

function routeUploadTypeChanged(currentType) {
	if (currentType == "createByFields") {
		//searchMode is enabled when search results are received
		zoomToZoomArea = true;
	} else {
		disableMapSearchMode();
	}
	updateRemoteData();
}
