(function(){
	if (typeof window.Tag == "undefined"){
            var Tag = window.Tag = function(){
                this.extend = function(namespace,obj){
                    if (typeof this[namespace] == "undefined"){
                        this[namespace] = obj;
                    }
                };

                // We have a dependency on jQuery:
                if (typeof jQuery == "undefined"){
                    alert("Please load jQuery library first");
                }
            };
            window.Tag = new Tag();
	}
	window.Tag.extend("maps",{
            /**
             * Properties
             */
            loaded: false,
            config:{},
            infoWindow:null,
            isInit:false,

            /**
             * Initialize the options, and trigger the map initialization
             */
            load:function(cfg){
                var me = window.Tag.maps;
                me.config = cfg;

                if (typeof google != "undefined"){
                    this.isInit = true;
                    if (me.config.loader.useLoader){
                        me.showLoading.call(me);
                    }
                    me.loadMap.call(me);
                    if (typeof(me.config.googleMaps.initialMarkers) != 'undefined' &&  me.config.googleMaps.initialMarkers.length > 0){
                        //this.loadClusterManager.call(this,this.config.initialMarkers);
                        me.loadMakers.call(me,me.config.googleMaps.initialMarkers);
                    }
                    me.loaded = true;
                    if (me.config.loader.useLoader){
                        google.maps.Event.addListener(me.mapObject,'tilesloaded',function(){
                            if (window.Tag.maps.isInit){
                                window.Tag.maps.hideLoading.call(window.Tag.maps);
                            }
                        });
                    }
                    
                    // if after X seconds google maps still haven't loaded,
                    // we'll remove the loading screen anyway
                    if (me.config.loader.useLoader){
                        var amount = (typeof me.config.loader.timeout !== "undefined")?me.config.loader.timeout:15;
                        amount = amount*1000;
                        me.hideTimer = window.setTimeout(function(){
                            if (window.Tag.maps.isInit){
                                window.Tag.maps.hideLoading.call(window.Tag.maps);
                            }
                        },amount);
                    }

                    if (typeof(me.config.location) != 'undefined'){
                    	me.loadPointMarkerFromLocationFields.call(me);
                    }

                    // bind onDrag events to the map:
                    google.maps.Event.addListener(me.mapObject,'moveend',function(){
                        window.Tag.maps.notify('onMoveEnd');
                    });




                    me.notify('onReady');
                } else {
                }
            },

            /**
             * Initialize the map object
             */
            loadMap:function(){
                this.mapObject = new google.maps.Map2(document.getElementById((this.config.googleMaps.container)?this.config.googleMaps.container:"map"));
                if (typeof(this.config.googleMaps.startPoint) != 'undefined'){
                	var mapCenter = new google.maps.LatLng(parseFloat(this.config.googleMaps.startPoint[0]),parseFloat(this.config.googleMaps.startPoint[1]));
                	//this.mapObject.setCenter(mapCenter,(this.config.googleMaps.initialZoom)?this.config.googleMaps.initialZoom:5);
                        this.mapObject.setCenter(mapCenter,20);
                }
                this.mapObject.savePosition();
                //this.mapObject.addMapType(G_SATELLITE_MAP);
                this.mapObject.addControl(new google.maps.LargeMapControl());
                this.mapObject.addControl(new google.maps.ScaleControl());
                this.mapObject.addControl(new google.maps.OverviewMapControl());
                this.mapObject.addControl(new google.maps.MapTypeControl());
            },

            /**
             * Shows an overlay on top of the map with a spinner
             */
            showLoading:function(){
                var mapContainer = $("#" + this.config.googleMaps.container);

                var div = document.createElement("div");
                $(div).attr("id",(typeof this.config.loader.container !== "undefined")?this.config.loader.container:"mapLoader");
                $(div).css("background-color",this.config.loader.color);
                $(div).css("text-align","center");
                $(div).width(mapContainer.width() + "px");
                $(div).height(mapContainer.height() + "px");
                $(div).css("position","absolute");
                $(div).css("z-index","9999");
                var pos = mapContainer.position();
                $(div).css("top",pos.top);
                $(div).css("left",pos.left);

                if (typeof this.config.loader.loaderImage !== "undefined" && this.config.loader.loaderImage !== ""){
                    var img = document.createElement("img");
                    $(img).attr("src",this.config.loader.loaderImage);
                    $(div).append($(img));
                    $(div).css("padding-top",Math.floor(mapContainer.height()/2) - Math.floor($(img).height()/2));
                }
                mapContainer.parent().append($(div));
                $(div).fadeTo("fast",(typeof this.config.loader.maxOpacity !== "undefined")?this.config.loader.maxOpacity:0.85);
                this.loaderShown = true;
            },

            /**
             * Hide the overlay
             */
            hideLoading:function(){
                if (this.loaderShown){
                    this.notify('beforeHideLoading');
                    var id = (typeof this.config.loader.container !== "undefined")?this.config.loader.container:"mapLoader";
                    $("#" + id).fadeOut("fast");
                    $("#" + id).remove();
                    this.notify('afterHideLoading');
                    this.loaderShown = false;
                }
            },

            /**
             * Transform the initial marker data to GMarkers on the map
             *
             * @param arrMarkerData - (required) Array of marker data
             */
            loadMakers:function(arrMarkerData){
                this.addMarkers.call(this,arrMarkerData);
            },

            /**
             * Create a new clusterManager
             */
            refreshClusterManager:function(markers){
                if (typeof this.markerCluster !== "undefined"){
                    this.markerCluster.clearMarkers();
                }

                var options = {
                    maxZoom:17,
                    gridSize:37
                };
                if (typeof this.config.clusterManager.styles !== "undefined" && this.config.clusterManager.styles.length > 0){
                    var styles = this.config.clusterManager.styles;
                    for(var i=0,l=styles.length;i<l;i++){
                        if (typeof styles[i].url !== "undefined"){
                            styles[i].url = styles[i].url.replace("\/","/");
                        }
                    }
                    options.styles = styles;
                }

                this.markerCluster = new MarkerClusterer(this.mapObject, markers, options);
                //var curZoom = this.mapObject.getZoom();
                //this.mapObject.setZoom((this.config.googleMaps.initialZoom)?this.config.googleMaps.initialZoom:5);
            },

            /**
             * Add an array of markers
             *
             * @param arrMarkerData - (required) Array of marker data
             */
            addMarkers:function(arrMarkerData){
                this.mappedMarkers = {};
                this.mappedIdMarkers = {};
                var markers = [];
                for(var i=0,l=arrMarkerData.length;i<l;i++){
                    markers.push(this.addMarker.call(this,arrMarkerData[i]));
                }
                if (typeof this.config.clusterManager !== "undefined" && typeof this.config.clusterManager.useClusterManager !== "undefined" && this.config.clusterManager.useClusterManager){
                    this.refreshClusterManager.call(this,markers);
                } else {
                	var j = markers.length;
                	while (j--) {
                		this.mapObject.addOverlay(markers[j]);
                	}
                }
                this.markers = markers;
                this.notify('afterAddMarkers');
            },

            /**
             * Clears all the markers from the map
             */
            clearMarkers:function(){
                this.mapObject.clearOverlays();
                this.mappedMarkers = {};
                this.mappedIdMarkers = {};
                this.markers = [];
            },

            /**
             * Add a marker to the map
             *
             * @param markerData - (required) The data of the marker
             */
            addMarker:function(markerData){
                var iconWidth   = 32;
                var iconHeight  = 32;
                var icon                = new google.maps.Icon(G_DEFAULT_ICON);
                icon.image              = markerData.markerImage;
                icon.shadow             = markerData.markerShadow;
                icon.iconSize           = new google.maps.Size(iconWidth, iconHeight); // (width,height)
                icon.shadowSize         = new google.maps.Size(59, 32);
                icon.iconAnchor         = new google.maps.Point(0, 0);
                icon.infoWindowAnchor   = new google.maps.Point(iconWidth/2, 0); // (x,y), with (0,0) top left
                icon.imageMap           = [0,0, iconWidth,0, iconWidth,iconHeight, 0,iconHeight]; // topLeft, topRight, bottomRight, bottomLeft

                var dLocation = new google.maps.LatLng(parseFloat(markerData.lat),parseFloat(markerData.lon));
                var marker = new google.maps.Marker(dLocation,icon);

                this.attachMarkerClickListener.call(this,marker, markerData);
                this.addLatLonMappedMarker.call(this,marker,markerData);
                this.addIdMappedMarker.call(this,marker,markerData);

                return marker;
            },

            loadPointMarkerFromLocationFields:function(){
            	var lat = $('#'+this.config.location.latitude).val();
            	var lon = $('#'+this.config.location.longitude).val();
            	if (lat != '' && lon != ''){
            		var pointer = new GLatLng(lat, lon);
            		this.setPointMarker(pointer);
            	}
            },
            
            setPointMarker:function(pointer){
            	
            	this.mapObject.setCenter(pointer, 13);
        		if (typeof(this.marker) == 'undefined'){
	        		this.marker = new GMarker(pointer, {draggable: true});
	        		
	        		/*GEvent.addListener(this.marker, "dragstart", function() {
	        			map.closeInfoWindow();
	        		});
	        		*/
	        		var me = this;
	        		
	        		GEvent.addListener(this.marker, "dragend", function(latLng) {
	        			$('#'+me.config.location.latitude).val(latLng.lat());
	        			$('#'+me.config.location.longitude).val(latLng.lng());
	        		});
	        		
	        		this.mapObject.addOverlay(this.marker);
        		}
        		this.marker.setLatLng(pointer);
        		$('#'+this.config.location.latitude).val(pointer.lat());
    			$('#'+this.config.location.longitude).val(pointer.lng());
            },
            
            markAddress: function(){
            	if (typeof(this.geocoder) == 'undefined'){
            		this.geocoder = new GClientGeocoder();
            	}
            	var me = this;
            	var address = [];
            	var i = me.config.address.length;
            	while(i--){
            		address.push($('#'+me.config.address[i]).val());
            	}
            	address.reverse();
            	address = address.join(' ');
            	this.geocoder.getLatLng(
            	          address,
            	          function(point) {
            	            if (!point) {
            	              alert(address + " not found");
            	            } else {
            	            	me.setPointMarker.call(me, point);
/*            	            	me.mapObject.setCenter(point, 13);
            	              var marker = new GMarker(point);
            	              me.mapObject.addOverlay(marker);*/
            	            }
            	          }
            	        );

            },
            
            /**
             * Fetch new markers via AJAX
             *
             * @param ajaxOptions - (required) Object with AJAX options
             */
            fetchAjaxMarkerData:function(ajaxOptions){
                var options = {
                    type:       "POST",
                    url:        "",
                    data:       {},
                    async:      false,
                    dataType:   "json",
                    success: function(data){
                    }
                };

                var callback = null;
                // merge options
                for(var k in options){
                    if (k !== "success" && typeof ajaxOptions[k] !== "undefined"){
                        options[k] = ajaxOptions[k];
                    }
                    if (k === "success"){
                        callback = ajaxOptions[k];
                    }
                }

                if (null !== callback){
                    options.success = function(data){
                        // add markers to map
                        if (data.Success){
                            var me = window.Tag.maps
                            me.addMarkers.call(me,data.data);
                            callback(data);
                        }
                    }
                }

                $.ajax(options);
            },

            /**
             * Listen for a click event on a marker
             *
             * @param marker - (required) The GMarker to map
             * @param data - (required) The custom data associated with the Gmarker
             */
            attachMarkerClickListener:function(marker, data) {
                var me = this;
                google.maps.Event.addListener(marker,"click",function(){
                    // customize this
                    if (typeof data.html !== "undefined" && data.html !== ""){
                        marker.openInfoWindowHtml((typeof data.html != "undefined")?data.html:"");
                    }
                    if (typeof me.onAfterClickMarker !== "undefined"){
                        me.onAfterClickMarker.call(me,data);
                    }
                });

                //this.mapObject.addOverlay(marker);
            },

            onAfterClickMarker:function(data){
                if (typeof this.selectedMarker === "undefined"){
                    this.selectedMarker = [];
                }
                this.selectedMarker = this.getMarkerById(data.id);

                if (typeof this.config.misc !== "undefined") {
	                if (typeof this.config.misc.searchResultsContainer !== "undefined"){
	                    // hide all popups:
	                    $('.popContainer').hide();
	
	                    // TODO determine if the correct page is displayed
	
	                    // show popup:
	                    $("#" + data.id).find('.popContainer').show();
	                }
	
	                if (typeof this.config.misc.directionsFormContainer !== "undefined"){
	                    $("#" + this.config.misc.directionsFormContainer).show();
	                }
                }
            },

            /**
             * Create a set of markers, mapped to their Lat/Lon
             *
             * @param marker - (required) The GMarker to map
             * @param data - (required) The custom data associated with the Gmarker
             */
            addLatLonMappedMarker:function(marker,data){
                if (typeof this.mappedMarkers == "undefined"){
                    this.mappedMarkers = {};
                }

                if (typeof this.mappedMarkers[data.lat] == "undefined"){
                    this.mappedMarkers[data.lat] = {};
                }

                if (typeof this.mappedMarkers[data.lat][data.lon] == "undefined"){
                    this.mappedMarkers[data.lat][data.lon] = {
                        marker:marker,
                        data:data
                    };
                }
            },

            /**
             * Create a set of markers, mapped to their Id
             *
             * @param marker - (required) The GMarker to map
             * @param data - (required) The custom data associated with the Gmarker
             */
            addIdMappedMarker:function(marker,data){
                if (typeof this.mappedIdMarkers == "undefined"){
                    this.mappedIdMarkers = {};
                }

                if (typeof this.mappedIdMarkers[data.id] == "undefined"){
                    this.mappedIdMarkers[data.id] = {
                        marker:marker,
                        data:data
                    };
                }
            },

            /**
             * Pan the map to the given marker
             *
             * @param mrkr - (required) The marker to scroll to
             * @param zoomlevel - (optional) The zoomlevel to set the map to
             * @param openInfoWindow - (optional) Open the infolevel when panned to the marker?
             */
            panToMarker:function(){
                var args = arguments;

                if (typeof args[0] === "undefined"){
                    alert(this.config.l10n.specifyMarkerToPanTo);
                }

                // fetch from mappedMarkers
                var data = this.mappedMarkers[args[0].lat][args[0].lon];

                // pan to the marker:
                this.mapObject.setCenter(new google.maps.LatLng(args[0].lat,args[0].lon),args[1]);
                this.mapObject.savePosition();

                // when requested, open the infowindow
                if (typeof args[2] !== "undefined" && args[2]){
                    google.maps.Event.trigger(data.marker,"click");
                }
            },

            /**
             * Retrieves a mapped marker based on its Id
             *
             * @param id - (required) The id of the marker
             */
            getMarkerById:function(id){
                if (typeof this.mappedIdMarkers[id] !== "undefined"){
                    return this.mappedIdMarkers[id];
                }
                return null;
            },

            /**
             * Retrieves a mapped marker based on its Lat/Lon properties
             *
             * @param lat - (required) The latitude of the marker
             * @param lon - (required) The longitude of the marker
             */
            getMarkerByLatLon:function(lat,lon){
                if (typeof this.mappedMarkers[lat] !== "undefined" && typeof this.mappedMarkers[lat][lon] !== "undefined"){
                    return this.mappedMarkers[lat][lon];
                }
                return null;
            },

            /**
             * Retrieve the Lat/Lon coordinates from a query
             *
             * @param query - (required) The search query
             * @param fn - (required) Callback function
             */
            getLatLonFromQuery:function(query,fn){
                var client = new google.maps.ClientGeocoder();
                client.getLatLng(query,fn);
            },

            /**
             * Retrieves all possible locations from a given query
             *
             * @param query - (required) The search query
             * @param fn - (required) Callback function
             */
            getLocationsFromQuery:function(query,fn){
                var client = new google.maps.ClientGeocoder();
                client.getLocations(query,fn);
            },

            /**
             * Show the directions from a location to another on the map
             *
             * @param fromLocation - (required) Starting point of the directions
             * @param toLocation - (required) Ending point of the directions
             * @param locale - (required) The language in which to show the text directions
             * @param panel - The div in which to show the text directions
             */
            getDirections:function(fromLocation,toLocation,locale,panel){
                var opts = {
                    locale:locale
                };

                if (typeof this.directionsClient == "undefined"){
                    if (typeof panel === "string"){
                        panel = $("#" + panel).get(0);
                    }
                    this.directionsClient = new google.maps.Directions(this.mapObject,panel);
                }
                this.directionsClient.clear();
                this.directionsClient.load("from: " + fromLocation + " to: " + toLocation,opts);
            },

            /**
             * Get the directions from selected marker to another point
             *
             * @param toLocation - (required) from point
             * @param locale - (required) Language in which to display the directions
             * @param panel - the div to display the directions in
             */
            getDirectionsToMarker:function(fromLocation,locale,panel){
                if (typeof this.selectedMarker !== "undefined"){
                    // find address of the marker:
                    var myLocation = new google.maps.LatLng(parseFloat(this.selectedMarker.data.lat),parseFloat(this.selectedMarker.data.lon));
                    var client = new google.maps.ClientGeocoder();
                    client.getLocations(myLocation,function(resp){
                        if (!resp || resp.Status.code != 200) {
                            alert(this.config.l10n.couldNotDetermineAddress);
                        } else {
                            var foundAddress = false;
                            var ctr = 0;
                            var address;
                            while(!foundAddress){
                                if (typeof resp.Placemark[ctr].address !== "undefined"){
                                    foundAddress = true;
                                    address = resp.Placemark[ctr].address;
                                }
                                ctr++;
                            }
                            window.Tag.maps.getDirections.call(window.Tag.maps,fromLocation,address,locale,panel);
                        }
                    });
                }
            },

            /**
             * Zooms the current map to fit all markers
             */
            zoomToFit:function(){
                if (typeof this.markers !== "undefined" && this.markers.length > 0){
                    var bounds = new google.maps.LatLngBounds();
                    for (var i=0; i< this.markers.length; i++) {
                        bounds.extend(this.markers[i].getLatLng());
                    }
                    var zl = this.mapObject.getBoundsZoomLevel(bounds);
                   // zl = (zl < 7)?7:zl;
                    this.mapObject.setZoom(zl);
                    this.mapObject.setCenter(bounds.getCenter());
                }
            },

            subscribe:function(evt,fn){
                if (typeof this.callbacks === 'undefined'){
                    this.callbacks = {};
                }
                if (typeof this.callbacks[evt] === 'undefined'){
                    this.callbacks[evt] = [];
                }
                this.callbacks[evt].push(fn);
            },

            notify:function(evt){
                if (typeof this.callbacks === 'undefined'){
                    this.callbacks = {};
                }
                if (typeof this.callbacks[evt] !== "undefined"){
                    for(var i=0,l=this.callbacks[evt].length;i<l;i++){
                        this.callbacks[evt][i].call(this);
                    }
                }
            }
    });
})();