/**
 * Klassik Stiftung Weimar Map Application
 * 
 * This class sets up our application. It is the only class that needs to be and should be
 * called from the javascript embeded in the HTML (template.tx_lombpointofinterest_map.php).
 * This is our public API so to speak.
 * 
 * 
 * @author Jesse Hemminger <jh@lombego.de>
 * @version 1.0
 * @package	TYPO3
 * @subpackage	tx_lombpointofinterest
 * @copyright lombego systems
 * 
 * @param array poiArray	Array of poi data objects
 * @param array routeArray	Array of route data objects
 * @param LatLng defaultLatLng	A google maps LatLng object (google.maps.LatLng) representing the center point
 * 								for the starting view and for the "Stadtuebersicht" view
 * @param int defaultZoom	The zoom level for the starting view and for the "Stadtuebersicht" view
 * 							(normally between 0 - 18, when more detail is available can zoom til 23 or more)
 * @param string mapContainerDomId	The HTML DOM element with this ID will be used for displaying the map
 * @param string poiMenuDomId	The HTML DOM element with this ID will have event listeners attached to all 
 * 								the child elements with the class "poiButton" and the attribute 'uid' set. When 
 * 								clicked the 'uid' value will be used to center the map on the poi marker with that
 * 								uid and open its info window. The "poiButton" element will then have the class "active" 
 * 								added and all other "poiButton" elements will have the class "active" removed. When
 * 								a poi marker is clicked on the map, the "poiButton" element(s) with a matching uid
 * 								attribute will receive the "active" class and all others will have it removed.
 * 								When the map is returned to the default view (by clicking the "Stadtuebersicht" button)
 * 								all "poiButton" elements will have the class "active" removed.
 * @param string routeMenuDomId	Same as poiMenuDomId but with the routes instead of pois, and "routeButton" instead of "poiButton"
 * @param string mapNavDomId	The HTML DOM element with this ID will have an event listener attached to 
 * 								the child element(s) with the class "defaultViewButton". When clicked the map
 * 								will return to the default zoom level and center point as defined by the defaultLatLng
 * 								and defaultZoom parameters. In addition all the poi info windows will be closed.
 * @param string mapContentPage Id to Contentpage
 */
function MapApplication(poiArray, routeArray, conf) {
	//private vars:
	var myMapView;
	var myMapNavView;
	var myPoiMenuView;
    var myRouteMenuView;
    var myConf;
    var myModel;
    var defaultConf = {
         mapContainerDomId : '',
	     poiMenuDomId : '',
	     routeMenuDomId : '',
         routeInfoWrapper : '',
	     mapNavDomId : '',
         poiCatContainer : '',
         defaultMode : '',
         defaultZoom : 15,
         defaultLatLng : {
               x: 0,
               y: 0
         },
         imageLocations : {
             imageFolder : '',
             iconFolder : ''
         },
         poiCategories : [],
         mapContentPage : 0,
         routeParams : {
             color: '#FFFFFF',
		    opacity : 0,
	        weight : 0,
	    	startTitle : '',
	    	endTitle : '',
	    	startIcon : '',
	    	endIcon : '',
            iconSizeX : 0,
            iconSizeY : 0
         },
		 shownElements : [
		  false,
		  [],
		  0,
		  0
		 ]
    }
	    if(conf.shownElements[0] && conf.shownElements[2]){
            var openPoiMenu = false;
        }else{
            var openPoiMenu = true;
        }
	
        myConf = $.extend({}, defaultConf, conf ||{});
        myModel = new MapModel(poiArray, routeArray, myConf);
        myMapView = new MapView(myModel, myConf);
        myMapNavView = new MapNavView(myConf.mapNavDomId);
        myPoiMenuView = new POIMenuView(myConf.poiMenuDomId,myConf.mapContainerDomId,openPoiMenu);
        myRouteMenuView = new RouteMenuView(myConf.routeMenuDomId);
        myPoiCatMenuView = new PoiCatMenu(myModel, myConf.poiCatContainer);
        myRouteInfoView = new RouteInfoView(myConf.routeInfoWrapper);
        myController = new Controller(myModel, myMapView, myMapNavView, myPoiMenuView, myRouteMenuView, myPoiCatMenuView,myRouteInfoView);
}

/**
 * the Controller class
 * 
 * When something interesting happens in one of the views the controller handles 
 * updating the other views appropriately.
 * 
 * @param MapModel model
 * @param MapView myMapView
 * @param MapNavView myMapNavView
 * @param PoiMenuView myPoiMenuView
 * @param RouteMenuView myRouteMenuView
 */
function Controller(model, myMapView, myMapNavView, myPoiMenuView, myRouteMenuView,myPoiCatMenuView,myRouteInfoView) {
	//all of the arguments above are already available as private vars

	//private method
	var handleDefaultViewClick = function() {
		myMapView.gotoDefaultView();
        if(model.switchToInstitutionView()){
            myMapView.hideAllElements();
            myMapView.showCurrentElements();
        }
		myPoiMenuView.setActivePOI();
		myMapView.setActivePOI();
		myRouteMenuView.setActiveRoute();
        myPoiCatMenuView.setActiveCategory();
	}
	google.maps.event.addListener(myMapNavView,'defaultViewClicked', handleDefaultViewClick);
	
	
	var handleResizeClick = function(){
		myMapView.resizeMap();
	}
	
	google.maps.event.addListener(myPoiMenuView,'resizeMap',handleResizeClick);
	
	//private method
	var handlePoiMarkerClick = function(uid) {
		myPoiMenuView.setActivePOI(uid);
	}
	google.maps.event.addListener(myMapView,'poiMarkerClicked', handlePoiMarkerClick);
	
	
	//private method
	var handlePoiMenuClick = function(uid) {
        if(model.switchToInstitutionView()){
            myMapView.hideAllElements();
            myMapView.showCurrentElements();
        }
        if(uid){
            myMapView.setActivePOI(uid);
        }
		myRouteMenuView.setActiveRoute();
        myPoiCatMenuView.setActiveCategory();
	}
	google.maps.event.addListener(myPoiMenuView,'poiMenuClicked', handlePoiMenuClick);
	
    //private method
	var handlePoiCatMenuClick = function(uid) {
        myMapView.hideAllElements();
        myMapView.showCurrentElements();
		myMapView.setActivePOI();
		myRouteMenuView.setActiveRoute();
        myPoiCatMenuView.setActiveCategory();
	}
	google.maps.event.addListener(myPoiCatMenuView,'poiCatMenuClicked', handlePoiCatMenuClick);
	
    var handleRouteInfoClose = function(uid){
        myPoiMenuView.openList();
		handleDefaultViewClick();
    }
    
    google.maps.event.addListener(myRouteInfoView,'routeInfoClosed',handleRouteInfoClose);
    
    
	//private method
	var handlePoiInfoWindowClosed = function(uid) {
		myPoiMenuView.setActivePOI();
	}
	google.maps.event.addListener(myMapView,'poiInfoWindowClosed', handlePoiInfoWindowClosed);
	
	//private method
	var handlePoiSetDefaultPosition = function(uid) {
        if(model.switchToInstitutionView()){
            myMapView.hideAllElements();
            myMapView.showCurrentElements();
        }
        myMapView.gotoDefaultView();
	}
	google.maps.event.addListener(myMapView,'poiSetDefaultPosition', handlePoiSetDefaultPosition );

   //private method
	var handlePoiPrint = function(arg) {
		myMapView.print();
	}
	google.maps.event.addListener(myMapView,'poiPrint', handlePoiPrint );


	//private method
	var handlePoiSetResize = function(uid) {
		myMapView.SetResize();
	}
	google.maps.event.addListener(myMapView,'poiSetResize', handlePoiSetResize);


	
	//private method
	var handleRouteMenuClick = function(uid) {
        model.changeMode('routes');
        model.setCurrentRoute(uid);
        myMapView.hideAllElements();
        myPoiMenuView.setActivePOI();
		myPoiMenuView.closeList();
		myMapView.setActivePOI();
		myMapView.showCurrentElements();
        myPoiCatMenuView.setActiveCategory();
	}
	google.maps.event.addListener(myRouteMenuView,'routeMenuClicked', handleRouteMenuClick);
    
    
    var handleCategorieClick = function(uid){
        model.changeMode('poiCategory');
        model.setCurrentCategory(uid);
        myMapView.hideAllElements();
        myMapView.showCurrentElements();
        myPoiCatMenuView.setActiveCategory();
    }
}


/**
 * the Marker class
 * 
 * This class extends the google maps marker in a way, but not in the classical OOP way
 * that we are familiar with. Douglas Crockford calls it "Parasitic Inheritance" on his website:
 * http://javascript.crockford.com/inheritance.html
 * 
 * @param object poi	a poi info object to use to create the marker
 * @param Map map		a google.maps.Map to attache the marker to
 */
function Marker(poi, generalConf, model) {
	//private properties
	var latlng;
	var marker;
	var iconAnchor = {x:60,y:70};
	var cat = model.getCategoryWithId(poi.category);
	if(cat){
		iconAnchor = cat.anchor;
	}
	var defaultIcon = new google.maps.MarkerImage(generalConf.imageLocations.imageFolder+poi.icon,null, null, iconAnchor);
    var defaultShape = null;
	if(poi.latlng){
		latlng = new google.maps.LatLng(poi.latlng[0],poi.latlng[1]);
	}
	markerOptions = {
		position: latlng, 
		map: null,
		title: poi.title,
		icon: defaultIcon,
        zIndex: poi.sorting
	};
	if(poi.iconShapeCoordinates && poi.iconShapeCoordinates.length > 0){
		markerOptions.shape = {type: 'poly', coord: poi.iconShapeCoordinates};
        defaultShape = {type: 'poly', coord: poi.iconShapeCoordinates};
	}else{
    }
	marker = new google.maps.Marker(markerOptions);
    //save this info directly in the marker
	marker.uid = poi.uid;
    marker.getUid = function(){
        return this.uid;
    };
    marker.defaultIcon = defaultIcon;
    marker.defaultShape = defaultShape;
    
	return marker;
}

/**
 * the Route class
 * 
 * This class extends the google maps polyline in a way, but not in the classical OOP way
 * that we are familiar with. Douglas Crockford calls it "Parasitic Inheritance" on his website:
 * http://javascript.crockford.com/inheritance.html
 * 
 * @param object route	a route info object to use to create the route
 * @param Map map		a google.maps.Map to attache the route to
 */
function Route(route, generalConf, iconPath) {
	//private properties
	var uid = route.uid;
	var path = [];
	var polyline;
	var startMarker;
	var endMarker;
	var markers = [];
    var pois = route.pois;
	var bounds = new google.maps.LatLngBounds();	//the bounds of the route used to pan/zoom map so entire route is visible
	var latLng = null;
	for(var i=0,len=route.points.length;i<len;i++) {
		latLng = new google.maps.LatLng(route.points[i].latitude, route.points[i].longitude);
		path.push(latLng);
		bounds.extend(latLng);
	}
	polyline = new google.maps.Polyline({
		path: path, 
		strokeColor: generalConf.color,
		strokeOpacity: generalConf.opacity,
		strokeWeight: generalConf.weight
	});
	startMarker = new google.maps.Marker({
		position: path[0], 
		title: generalConf.startTitle,
		icon: iconPath+generalConf.startIcon
	});
	endMarker = new google.maps.Marker({
		position: path[path.length - 1], 
		title: generalConf.endTitle,
		icon: iconPath+generalConf.endIcon
	});
	
	this.getUid = function() {
		return uid;
	}
    
    this.getPois = function() {
		return pois;
	}
	
	this.show = function(map) {
		polyline.setMap(map);
		startMarker.setMap(map);
		endMarker.setMap(map);
		map.fitBounds(bounds);
		for(var i in markers) {
			markers[i].setMap(map);
		}
		$('#route_'+route.uid).fadeIn();
	}
	
	this.hide = function() {
		polyline.setMap(null);
		startMarker.setMap(null);
		endMarker.setMap(null);
		for(var i in markers) {
			markers[i].setMap(null);
		}
		$('#route_'+route.uid).hide();
	}
	
}

function MapModel(poiArr,routeArr,conf){
    var displayMode = 'poiCategory';
	if(conf.shownElements[0]){
		displayMode = 'individual';
	}
	var individualItems = conf.shownElements;
    var currentPoiCat = conf.defaultCat;
    var currentRoute = null;
    var markers = [];
	var routes = [];
    var that = this;
    var routeIcons = [];
    var poiConf = {
        imageLocations : conf.imageLocations
    }
    var routeConf = conf.routeParams;
    this.changeMode = function(mode){
        if(mode=='poiCategory'){
            displayMode=mode;
        }else{
            displayMode='routes';
        }
    }
    this.switchToInstitutionView = function(){
        var instCat = this.getInstitutionCategory();
        if(displayMode!='poiCategory' || !instCat || instCat.uid!=currentPoiCat){
            this.changeMode('poiCategory');
            currentPoiCat = instCat.uid;
            return true;
        }
        return false;
    }
    
    this.getPoiIdsForCategory = function(id){
        var ret = [];
        for(var i=0,len=conf.poiCategories.length;i<len;i++){
            if(conf.poiCategories[i].uid==id && conf.poiCategories[i].pois){
                return conf.poiCategories[i].pois;
            }
        }
        return ret;
    }
    
    this.getInstitutionCategory = function(){
        for(var i=0,len=conf.poiCategories.length;i<len;i++){
            if(conf.poiCategories[i].isInstitutionCategory){
                return conf.poiCategories[i];
            }
        }
        return false;
    }
    
    this.getCurrentCategory = function(){
        if(displayMode!='poiCategory'){
            return false;
        }
        return currentPoiCat;
    }
    
    this.setCurrentCategory = function(id){
        currentPoiCat = id;
        currentRoute = null;
    }
    
    this.setCurrentRoute = function(id){
        currentRoute = id;
        currentPoiCat = null;
    }
    
    this.getCategoryWithId = function(id){
        for(var i=0,len=conf.poiCategories.length;i<len;i++){
            if(conf.poiCategories[i].uid==id){
                return conf.poiCategories[i];
            }
        }
        return null;
    }
    
    for(var i=0;i<20;i++){
        routeIcons.push(new google.maps.MarkerImage(conf.imageLocations.iconFolder+(i+1)+'.png',null, null, {x:20, y:20}));
    }
    
    //add our poi markers to the map and attach onClick event listeners to them
	for(var i in poiArr) {
		//create the marker and add it to the array of markers
		markers.push(new Marker(poiArr[i], poiConf,this));
		//attach a 'click' event listener to the newly created marker
	}
	
	
	//add our routes
	for(var i in routeArr) {
		//create the marker and add it to the array of markers
		routes.push(new Route(routeArr[i], routeConf, conf.imageLocations.iconFolder));
		//attach a 'click' event listener to the newly created routes
		//google.maps.event.addListener(polylines[routeArray[i].uid], 'click', handleMarkerClick);
	}
	
	var in_array = function(needle,haystack){
		for(var i=0,len=haystack.length;i<len;i++){
			if(haystack[i]==needle){
				return true;
			}
		}
		return false;
	}
	
	this.forceCenter = false;
    
    this.getElementsInCurrentView = function(){
        var ret = {
            routes: [],
            pois: [],
			rpois: []
        };
		if (displayMode == 'individual') {
			if(individualItems[0]){
				var rpoiIds = [];
				if (individualItems[2]) {
					var cRoute = getRouteWithId(individualItems[2]);
                    if (cRoute) {
						ret.routes = [cRoute];
						ret.rpois = getPoisForRoute(cRoute);
						for(var i=0,len=ret.rpois.length;i<len;i++){
							this.forceCenter = rpoiIds.getPosition();
							rpoiIds.push(ret.rpois[i].uid);
						}
					}
                }
				var pois = [];
				var poiIds = [];
				if(individualItems[3]){
					var tmp = getPoisForCategory(individualItems[3]);
					for(var i=0,len=tmp.length;i<len;i++){
						if(!in_array(tmp[i].uid,rpoiIds)){
							this.forceCenter = tmp[i].getPosition();
							pois.push(tmp[i]);
							poiIds.push(tmp[i].uid);
						}
                    }
				}
				var poi = null;
				for(var i=0,len=individualItems[1].length;i<len;i++){
					poi = getMarkerWithId(individualItems[1][i]);
					if(
					   poi 
					   && !in_array(individualItems[1][i],poiIds) 
					   && !in_array(individualItems[1][i],rpoiIds)
					){
						this.forceCenter = poi.getPosition();
						poiIds.push(individualItems[1][i]);
						pois.push(poi); 
					}
				}
				ret.pois = pois;
				return ret;
			}else{
				return ret;
			}
		}
        if(displayMode=='poiCategory'){
			this.forceCenter = false;
            if(currentPoiCat){
                ret.routes = false;
                ret.pois = getPoisForCategory(currentPoiCat);
				ret.rpois = false;
            }
        }else{
			this.forceCenter = false;
            if(currentRoute){
                var cRoute = getRouteWithId(currentRoute);
                if(cRoute){
                    ret.routes.push(cRoute);
                    ret.rpois = getPoisForRoute(cRoute);
					ret.pois = false;
                }
            }
        }
        return ret;
    }
    
    
    var getPoisForCategory = function(catId){
        var ret = [];
        var ids = that.getPoiIdsForCategory(catId);
        return getMarkerObjectsForArray(ids);
    }
    
    var getMarkerObjectsForArray = function(arr){
       var ret = [];
       var marker = null;
       for(var i=0,len=arr.length;i<len;i++){
           marker = getMarkerWithId(arr[i]);
           if(marker){
               ret.push(marker);
           }
       }
       return ret;
    }
    
    var getPoisForRoute = function(cRoute){
       return getMarkerObjectsForArray(cRoute.getPois());
    }
    
    var getRouteWithId = function(id){
        return getElementByUid(routes,id);
    }
    
     var getMarkerWithId = function(id){
        return getElementByUid(markers,id);
    }
    
    this.getMarkerWithId = function(id){
        return getMarkerWithId(id);
    }
    
    var getElementByUid = function(eleList,id){
        for(var i=0,len=eleList.length;i<len;i++){
            if(eleList[i].getUid()==id){
                return eleList[i];
            }
        }
        return null;
    }
    
    this.getRoutePointIcon = function(id){
        var icon = null;
        if(id<20){
            icon = routeIcons[id];
        }
        return icon;
    }
    
    this.getPidForPoi = function(id){
        for(var i=0,len=poiArr.length;i<len;i++){
            if(poiArr[i].uid == id && poiArr[i].pid>0){
                return poiArr[i].pid;
            }
        }
        return false;
    }
    
    this.getDefaultPid = function(){
        return conf.mapContentPage;
    }
    
    this.getAllMarkers = function(){
        return markers;
    }
    this.getAllRoutes = function(){
        return routes;
    }
}

/**
 * The MapView class
 * 
 * Sets up our map and adds markers to the map for all of the POIs
 * 
 * @param array poiArray	Array of poi data objects
 * @param array routeArray	Array of route data objects
 * @param LatLng defaultLatLng	A google maps LatLng object (google.maps.LatLng) representing the center point
 * 								for the starting view and for the "Stadtuebersicht" view
 * @param int defaultZoom	The zoom level for the starting view and for the "Stadtuebersicht" view
 * 							(normally between 0 - 18, when more detail is available can zoom til 23 or more)
 * @param string mapContainerDomId	The HTML DOM element with this ID will be used for displaying the map
 */
function MapView(model,conf) {
	//all of the arguments above are also available as private properties in the class

	//private proporties:
	var map;
	var mapOptions;
	var infoWindow;
	var latlng;
	var that = this;
	var defaultSpot = new google.maps.LatLng(conf.defaultLatLng.x,conf.defaultLatLng.y);
	mapOptions = {
		zoom: conf.defaultZoom,
		center: defaultSpot,
		mapTypeId: google.maps.MapTypeId.ROADMAP
	};
	map = new google.maps.Map(document.getElementById(conf.mapContainerDomId), mapOptions);

	//we only have a single info window and just replace its content and 
	//change its anchor to a different marker. This prevents two info windows
	//from beign open at the same time
	infoWindow = new google.maps.InfoWindow({pixelOffset: new google.maps.Size(0,30)});
    infoWindow.currentItem = null;
	
	//private function to handle an info window 'closeclick' event
	var handleInfoWindwoCloseClick = function() {
        this.currentItem = null;
		that.setActivePOI();
		google.maps.event.trigger(that, 'poiInfoWindowClosed');
	}
	google.maps.event.addListener(infoWindow, 'closeclick', handleInfoWindwoCloseClick);

	//private function to handle a Marker 'click' event
	var handleMarkerClick = function() {
		that.setActivePOI(this.uid);
		//hide the route if one is open
		//myMapView.setActiveRoute();
		google.maps.event.trigger(that, 'poiMarkerClicked', this.uid);
	}
	
	this.resizeMap = function(){
		google.maps.event.trigger(map, 'resize');
	}
    
    this.hideAllElements = function(){
        var allMarkers = model.getAllMarkers();
        for(var i=0,len=allMarkers.length;i<len;i++){
            allMarkers[i].setMap(null);
            google.maps.event.clearListeners(allMarkers[i], 'click');
        }
        var allRoutes = model.getAllRoutes();
        for(var j=0,len1=allRoutes.length;j<len1;j++){
            allRoutes[j].hide();
        }
    }
	
	/**
	 * Goto Default View
	 * 
	 * pans and zooms the map to the default view (as set by the defaultLatLng and defaultZoom private properties) 
	 * and closes all info windows. defaultLatLng and defaultZoom are actually parameters of the MapView constructor
	 * which makes them essentially private properties.
	 * 
	 * @access privileged	a public function with access to private properties, see Douglaus Crockford 
	 * 						http://javascript.crockford.com/private.html   under the heading Privileged
	 */
	this.gotoDefaultView = function() {
		infoWindow.close();
		map.setCenter(defaultSpot);
		map.setZoom(conf.defaultZoom);
	}
	
	/**
	 * Set the active POI
	 * 
	 * sets which POI marker is active (if any) and has its info window open.
	 * 
	 * @access privileged	a public function with access to private properties, see Douglaus Crockford 
	 * 						http://javascript.crockford.com/private.html   under the heading Privileged
	 * 
	 * @param int uid	[optional] if set, the poi with a matching uid will have its info window opened.
	 * 					All other info windows will be closed first.
	 * 					If uid is not set or is undefined or null, all info windows will be closed.
	 */
	this.setActivePOI = function(uid) {
		if(uid) {
            var pid = model.getPidForPoi(uid);
            if(!pid){
                pid = model.getDefaultPid();
            }
			$('#switch_to_text_link').attr('href','./index.php?id=' + pid );
			if(infoWindow.currentItem != 'poi_'+uid) {
				infoWindow.close();
                infoWindow.currentItem = 'poi_'+uid;
				infoWindow.setContent(document.getElementById('poi_'+uid).cloneNode(true));
			}
            infoWindow.open(map,model.getMarkerWithId(uid));
		} else {
			$('#switch_to_text_link').attr('href','index.php?id='+model.getDefaultPid());
			infoWindow.close();
            infoWindow.currentItem = null;
			infoWindow.setContent('');
		}
	}

	$('.show_all').click(function() {
		google.maps.event.trigger(that, 'poiSetDefaultPosition', $(this).attr('uid'));
		return false;
	});
    
    $('.print_map').click(function() {
		google.maps.event.trigger(that, 'poiPrint', {});
		return false;
	});

	$('.toggle_list_button').click(function() {
		google.maps.event.trigger(that, 'poiSetResize', $(this).attr('uid'));
		return false;
	});
    
    this.print = function(){
        map.setOptions({mapTypeControl:false,navigationControl:false});
        var mapEle = $('#'+conf.mapContainerDomId).parent().printElement(
        {
        printMode:'popup',
        overrideElementCSS:['fileadmin/templates/css/content.css','fileadmin/templates/css/printmap.css','fileadmin/templates/css/navigation.css'],
        height:600,
        width:950
        }
        );
        map.setOptions({mapTypeControl:true,navigationControl:true});
    }

	
	this.SetResize = function(uid){
		google.maps.event.trigger(map, 'resize');
	}
    
    this.showCurrentElements = function(){
        var ele = model.getElementsInCurrentView();
        if(ele.routes && ele.routes[0]){
            ele.routes[0].show(map);
            var altIcon = null;
            for(var i=0,len=ele.rpois.length;i<len;i++){
                ele.rpois[i].setMap(map);
                altIcon = model.getRoutePointIcon(i);
                ele.rpois[i].setIcon(altIcon);
                ele.rpois[i].setShape(null);
                google.maps.event.addListener(ele.rpois[i], 'click', handleMarkerClick);
            }
        }
		if(ele.pois){
            for(var i=0,len=ele.pois.length;i<len;i++){
                ele.pois[i].setMap(map);
                ele.pois[i].setIcon(ele.pois[i].defaultIcon);
                ele.pois[i].setShape(ele.pois[i].defaultShape);
                google.maps.event.addListener(ele.pois[i], 'click', handleMarkerClick);
            }
        }
		if(model.forceCenter){
			map.setCenter(model.forceCenter);
		}
    }
    this.showCurrentElements();
}

/**
 * The Map Navigation View class
 * 
 * Attaches listeners to buttons in the Map Navigation.
 * 
 * @param string mapNavDomId	The HTML DOM element with this ID will have an event listener attached to 
 * 								the child element(s) with the class "defaultViewButton". When clicked the map
 * 								will return to the default zoom level and center point as defined by the defaultLatLng
 * 								and defaultZoom parameters. In addition all the poi info windows will be closed.
 */
function MapNavView(mapNavDomId) {
	var that = this;
	$("#"+mapNavDomId + " > .defaultViewButton").click(function() {
		google.maps.event.trigger(that, 'defaultViewClicked');
		return false;
	});
}

/**
 * the POI Menu View class
 * 
 * Attaches listeners to buttons in the POI Menu.
 * 
 * @param string poiMenuDomId	The HTML DOM element with this ID will have event listeners attached to all 
 * 								the child elements with the class "poiButton" and the attribute 'uid' set. When 
 * 								clicked the 'uid' value will be used to center the map on the poi marker with that
 * 								uid and open its info window. The "poiButton" element will then have the class "active" 
 * 								added and all other "poiButton" elements will have the class "active" removed. When
 * 								a poi marker is clicked on the map, the "poiButton" element(s) with a matching uid
 * 								attribute will receive the "active" class and all others will have it removed.
 * 								When the map is returned to the default view (by clicking the "Stadtuebersicht" button)
 * 								all "poiButton" elements will have the class "active" removed.
 */

function POIMenuView(poiMenuDomId,mapContainerDomId,openPoiMenu){
	var that = this;
    var viewWrapper = "#"+poiMenuDomId;
    var closeFirstLevel = function(){
        $(viewWrapper+' .sn_1_element').each(function(index) {
						$(this).addClass('sn_1_inactive');
						$(this).removeClass('sn_1_active_with_subs');
						$(this).removeClass('sn_1_active');
						$(this).parent().removeClass('sn_1_active_li_with_subs');
                        $(this).parent().removeClass('sn_1_active_li');
                        $(this).next('ul').hide();
		});
    }
    var closeSecondLevel = function(){
        $(viewWrapper+' .sn_2_element').each(function(index) {
						$(this).addClass('sn_2_inactive');
						$(this).removeClass('sn_2_active');
		});
    }
    var openElement = function(ele){
        if(ele.hasClass('sn_1_element')){
			//übergeordneten <li> activ Klasse zuweisen  
            if(ele.next('ul').length > 0){
                ele.parent().addClass('sn_1_active_li_with_subs');
			    ele.addClass('sn_1_active');
			    ele.addClass('sn_1_active_with_subs');
            }else{
                ele.parent().addClass('sn_1_active_li');
				ele.addClass('sn_1_active');
				ele.addClass('sn_1_active');
            }
			ele.removeClass('sn_1_inactive');
			ele.next('ul').show();
    }else{
        var parent = ele.parent().parent().parent().find('.sn_1_element');
        if(!parent.hasClass('sn_1_active')){
            openElement(parent);
        }
		ele.addClass('sn_2_active');
		ele.removeClass('sn_2_inactive');
    }
    }
	
	this.closeList = function(){
		 $('#show_list_is_on').hide();
        $('#show_list_is_off').show();
		$(viewWrapper).parent().hide();
        $("#"+mapContainerDomId).addClass('map_full_width');
		google.maps.event.trigger(that, 'resizeMap');
	}
	
	this.openList = function(){
		$('#show_list_is_on').show();
        $('#show_list_is_off').hide();
		$(viewWrapper).parent().show();
        $("#"+mapContainerDomId).removeClass('map_full_width');
		google.maps.event.trigger(that, 'resizeMap');
	}
	
	$('.list_switch .toggle_list_button').click(function() {
            if( $(this).parent().attr('id') == 'show_list_is_on' ){
                that.closeList();
            }else{
                that.openList();
            }
    });
	$(viewWrapper).delegate(
        'a',
        'click',
        function(){
            closeSecondLevel();
            closeFirstLevel();
            openElement($(this));
            if($(this).hasClass('poiButton')){
                var poiId = $(this).attr('rel').substr(4);
               
            }else{
                 var poiId = null;
            }
             google.maps.event.trigger(that, 'poiMenuClicked', poiId);
            return false;
        }
    );
	
	/**
	 * Set the active POI
	 * 
	 * sets which POI button is active (if any) and gives the active POI button the "active" css class, and removes
	 * the "active" css class from all other POI Buttons.
	 * 
	 * @access privileged	a public function with access to private properties, see Douglaus Crockford 
	 * 						http://javascript.crockford.com/private.html   under the heading Privileged
	 * 
	 * @param int uid	[optional] if set, the poi button with a matching uid attribute will have the "active"
	 * 					css class added. All other poi buttons will have the "active" css class removed first.
	 * 					If uid is not set or is undefined or null, all poi buttons will have the "active" css class removed.
	 */ 
	this.setActivePOI = function(uid) {
		closeSecondLevel();
        closeFirstLevel();
		if(uid) {
			openElement($(viewWrapper+' .poiButton[rel=poi_'+uid+']'));
		}
	}
	if(!openPoiMenu){
		this.closeList();
	}
}

function PoiCatMenu(model,poiCatContainer){
    var that = this;
    $('#'+poiCatContainer).delegate(
        '.filterCategory','click',function(){
        var uid = $(this).attr('rel').substr(7);
        model.changeMode('poiCategory');
        model.setCurrentCategory(uid);
        google.maps.event.trigger(that,'poiCatMenuClicked',uid);
        return false;
    });
    this.setActiveCategory = function(){
       var cCat =  model.getCurrentCategory();
       if(cCat){
           $('#'+poiCatContainer+' .filterCategory').parent().removeClass('active_poi_category');
           $('#'+poiCatContainer+' .filterCategory[rel=poiCat_'+cCat+']').parent().addClass('active_poi_category');
       }else{
           $('#'+poiCatContainer+' .filterCategory').parent().removeClass('active_poi_category');
       }
    }
    this.setActiveCategory();
}

function RouteInfoView(wrapperId){
    var that = this;
    $('#'+wrapperId).delegate(
        '.closeButton',
        'click',
        function(){
            var parent = $(this).parent();
            parent.hide();
			show(this);
            var uid = parent.attr('id').substr(6);
            google.maps.event.trigger(that,'routeInfoClosed',uid);
            return false;
        }
    );
	$('#'+wrapperId).delegate(
        '.minButton',
        'click',
        function(){
            var content = $(this).parent().find('.bodyContent');
			if(content.css('display')=='none'){
				show(this);
			}else{
				$(this).parent().css('height','0.437em');
				$(this).parent().find('.map_route_shadow').css('height','0em');
				$(this).parent().find('.minButton').addClass('maxButton');
				content.hide();
				
			}
            return false;
        }
    );
	
	show = function(ele){
		var content = $(ele).parent().parent().find('.bodyContent');
		$(ele).parent().parent().find('.map_route_info').css('height','29.687em');
		$(ele).parent().parent().find('.map_route_shadow').css('height','31.25em');
		$(ele).parent().parent().find('.minButton').removeClass('maxButton');
        content.show();
	}
}

/**
 * the POI Menu View class
 * 
 * Attaches listeners to buttons in the POI Menu.
 * 
 * @param string poiMenuDomId	The HTML DOM element with this ID will have event listeners attached to all 
 * 								the child elements with the class "poiButton" and the attribute 'uid' set. When 
 * 								clicked the 'uid' value will be used to center the map on the poi marker with that
 * 								uid and open its info window. The "poiButton" element will then have the class "active" 
 * 								added and all other "poiButton" elements will have the class "active" removed. When
 * 								a poi marker is clicked on the map, the "poiButton" element(s) with a matching uid
 * 								attribute will receive the "active" class and all others will have it removed.
 * 								When the map is returned to the default view (by clicking the "Stadtuebersicht" button)
 * 								all "poiButton" elements will have the class "active" removed.
 */
function RouteMenuView(routeMenuDomId){
	var that = this;
	
	var buttons = $("#"+routeMenuDomId + " .routeButton");
	
	buttons.click(function() {
			buttons.removeClass("active");
			$(this).addClass("active");
			google.maps.event.trigger(that, 'routeMenuClicked', $(this).attr('rel').substr(6));
		return true;
	});
	
	/**
	 * Set the active POI
	 * 
	 * sets which POI button is active (if any) and gives the active POI button the "active" css class, and removes
	 * the "active" css class from all other POI Buttons.
	 * 
	 * @access privileged	a public function with access to private properties, see Douglaus Crockford 
	 * 						http://javascript.crockford.com/private.html   under the heading Privileged
	 * 
	 * @param int uid	[optional] if set, the poi button with a matching uid attribute will have the "active"
	 * 					css class added. All other poi buttons will have the "active" css class removed first.
	 * 					If uid is not set or is undefined or null, all poi buttons will have the "active" css class removed.
	 */
	this.setActiveRoute = function(uid) {
		buttons.removeClass("active");
		if(uid) {
			buttons.filter(function(){
				return $(this).attr('uid') == uid;
			}).addClass("active");
		}
	}
}

