/**           
  * not supported yet in v3                       
  **/

google.maps.LatLng.prototype.distanceFrom = function(latlng) {
    var lat = [this.lat(), latlng.lat()]
    var lng = [this.lng(), latlng.lng()]

  //var R = 6371; // km (change this constant to get miles)
  var R = 6378137; // In meters
  var dLat = (lat[1]-lat[0]) * Math.PI / 180;
  var dLng = (lng[1]-lng[0]) * Math.PI / 180;
  var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
  Math.cos(lat[0] * Math.PI / 180 ) * Math.cos(lat[1] * Math.PI / 180 ) *
  Math.sin(dLng/2) * Math.sin(dLng/2);
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  var d = R * c;

  return Math.round(d);
}
google.maps.Marker.prototype.distanceFrom = function(marker) {
  return this.getPosition().distanceFrom(marker.getPosition());
}


/**
  *
  *  HM.GooglemapsMethods
  *
  *  Copyright 2010 Holiday Media
  *  Author: Sales Bansie <dev@holiday.nl>
  *  Version: 0.4 Beta version
  *  
  *  20101709: Added fitBounds option
  *  20101509: Changed mouseClick option to markerClick
  *  20101509: Added support for polygons
  *
  **/

HM.GooglemapsMethods = Class.create({
 
 /**
  * Marker methods
  **/
 
  addMarker: function() {
    var data  = arguments[0];
    this._addMarker(data);
    this.element.fire(this.clusterInitialize);
  },
  
  addMarkers: function() {
    var data  = arguments[0];
    $A(data).each(function(el) {
      this._addMarker(el); 
    }.bind(this));
    this.element.fire(this.clusterInitialize);
  },
  
  getMarker: function() {
    var id    = arguments[0],
        flag  = arguments[1] || false,
        marker;
    if ((marker = this._getMarkerById(id))  && id) {
      return flag ? marker : marker.get("marker");
    }
  },
  
  getMarkerById: function() {
     var id   = arguments[0],
        flag  = arguments[1] || false;
    return this.getMarker(id,flag);
  },
  
  getMarkers: function() {
    var flag = arguments[0] || false;
    return flag ? this.markers : this._toGooglemapsMarkerObject();
  },
  
  getMarkersByType: function() {
    var type    = arguments[0],
        flag    = arguments[1],
        markers = this._getMarkersByType(type);   
    
     return flag ? markers : this._toGooglemapsMarkerObject(markers);
  }, 
  
  hideMarker: function() {
    var id = arguments[0],
        marker;
    if (marker = this.getMarker(id)) {
      marker.setVisible(false);
      //this.element.fire(this.clusterInitialize);
    }
  },
  
  hideMarkerById: function() {
    this.hideMarker(arguments[0]);
  },
  
  hideMarkers: function() {
    this.getMarkers().each(function(el){
      el.setVisible(false);
    }.bind(this));
    //this.element.fire(this.clusterInitialize);
  },
  
  hideMarkersByType: function() {
    this.getMarkersByType(arguments[0]).each(function(el) {
      el.setVisible(false);
    }.bind(this));
    //this.element.fire(this.clusterInitialize);
  },
  
  removeMarker: function() {
    var id = arguments[0],
        marker;
    if (marker = this.getMarker(id)) {
      marker.setMap(null);
      this._deleteMarkerDataFromObject(id);
    }
   // this.element.fire(this.clusterInitialize);
  },
  
  removeMarkerById: function() {
    var id = argumetents[0];
    this.removeMarker(id);
  },
  
  removeMarkers: function() {
    this.getMarkers().each(function(el) {
      el.setMap(null);  
    }.bind(this));
    this._deleteMarkerDataFromObject();
    //this.element.fire(this.clusterInitialize);
  },
  
  removeMarkersByType: function() {
    this.getMarkersByType(arguments[0],true).each(function(el) {
      this.removeMarker(el.get('id'));
    }.bind(this));
    //this.element.fire(this.clusterInitialize);
  },
  
  retrieveAndAddHotspots: function() {
    var param = arguments[0] || {};
    this.retrieveAndAddMarkers(param);
  },
  
  retrieveAndUpdateHotspots: function() {
    var param  = arguments[0] || {};
    this.retrieveAndUpdateMarkers(param);
  },
  
  retrieveAndAddMarkers: function() {
    var param            = arguments[0] || {};
        param.addMarkers = true;
    this.retrieveHotspots(param,true);
  },
  
  retrieveAndUpdateMarkers: function() {
    var param            = arguments[0] || {};
        param.addMarkers = true;
    this.retrieveHotspots(param);
  },
  
  retrieveMarkers: function() {
    var param  = arguments[0] || {};
    this.retrieveHotspots(param);
  },

  retrieveHotspots: function() {
    var param          = arguments[0] || {},
        keepOldMarkers = arguments[1] || false,
        addMarkers     = param.addMarkers;
        
    delete param.addMarkers;    
    
    this.element.fire(this.beforeMarkersLoadingEvent);
        
    new Ajax.Request(this.options.hotspotsURI, {  
    	method          : 'post',   
    	parameters      : param,
      requestHeaders  : { Accept: "application/json" },
      evalJSON        : "force",
      //evalJS          : "force",
    	onSuccess       : function(request) {
    		try{
    			var json = request.responseJSON;  
          if (json.result != 1) {
            console.warn(json.error);
            if (!addMarkers) return {};
          
          }else{
            this.element.fire(this.markersLoadedEvent, {
              data  : json.data,
              extra : json.extra
            });
            
            if (!keepOldMarkers) {
              this.removeMarkers();
              this.bounds = new google.maps.LatLngBounds();
            }
            
            if (!addMarkers) return json.data
            
            this.addMarkers(json.data);
            
            this.element.fire(this.markersInitializedEvent, {
              data    : json.data,
              extra   : json.extra,
              bounds  : this.bounds
            });
            return false;
          }
          
    		}catch(e){
          console.warn(e);
          if (!addMarkers) return {};
        }
    	}.bind(this)
    }); 
  },
  
  showMarker: function() {
    var id = arguments[0],
        marker;
    if (marker = this.getMarker(id)) {
      marker.setVisible(true);
      this.element.fire(this.clusterInitialize);
    }
  },
  
  showMarkerById: function() {
    this.showMarker(arguments[0]);
  },
  
  showMarkers: function() {
    this.getMarkers().each(function(el){
      el.setVisible(true);
    }.bind(this));
    this.element.fire(this.clusterInitialize);
  },
  
  showMarkersByType: function() {
    this.hideMarkers();
    this.getMarkersByType(arguments[0]).each(function(el) {
      el.setVisible(true);
    }.bind(this));
    this.element.fire(this.clusterInitialize);
  },

  updateMarkers: function() {
    var data  = arguments[0];
    this.removeMarkers();
    $A(data).each(function(el) {
      this._addMarker(el); 
    }.bind(this));
    this.element.fire(this.clusterInitialize);
  },
  
  /**
    * Directions methods
    **/
  
  calculateRoute: function() {
    var param = arguments[0] || {};
    
    if (!this.options.directionsService.init) return false;
    
    if (!this.directionsDisplay.getMap()) {
      this.directionsDisplay.setMap(this.getMap());
    }
    if (!this.directionsDisplay.getPanel()) {
      this.directionsDisplay.setPanel(document.getElementById(this.options.directionsService.directionsPanel));
    }
    
    var request = {
      origin                    : (param.origin      == 'user' ? (this.getUserLocation() || this.options.directionsService.origin) : param.origin)      || this.options.directionsService.origin,
      destination               : (param.destination == 'user' ? (this.getUserLocation() || this.options.directionsService.origin) : param.destination) || this.options.directionsService.destination,
      
      travelMode                : google.maps.DirectionsTravelMode[param.travelMode]  || google.maps.DirectionsTravelMode[this.options.directionsService.travelMode],
      unitSystem                : google.maps.DirectionsUnitSystem[param.unitSystem]  || google.maps.DirectionsUnitSystem[this.options.directionsService.unitSystem],
      
      waypoints                 : param.waypoints                 || this.options.directionsService.waypoints,
      optimizeWaypoints         : param.optimizeWaypoints         || this.options.directionsService.optimizeWaypoints,
      provideRouteAlternatives  : param.provideRouteAlternatives  || this.options.directionsService.provideRouteAlternatives,
      avoidHighways             : param.avoidHighways             || this.options.directionsService.avoidHighways,
      avoidTolls                : param.avoidTolls                || this.options.directionsService.avoidTolls,
      region                    : param.region                    || this.options.directionsService.region
    };
    this.directionsService.route(request, function(response, status) {
      $(this.options.directionsService.directionsPanel).update('');
      if (status == google.maps.DirectionsStatus.OK) {
        this.directionsDisplay.setDirections(response);
        this.element.fire(this.directionsLoadedEvent, {
          panel: $(this.options.directionsService.directionsPanel)
        });
        
      }else{
         this.element.fire(this.directionsErrorEvent, {
          error: status,
          panel: $(this.options.directionsService.directionsPanel)
        });
        console.warn(status);
      }
    }.bind(this));
  }, 
  
  initializeDirectionsService: function() {
    this.options.directionsService.origin       = this.options.latlng; 
    this.options.directionsService.destination  = this.options.latlng;
    
    this.directionsService = new google.maps.DirectionsService();
    this.directionsDisplay = new google.maps.DirectionsRenderer();
  },
  
  removeDirections: function() {
    this.directionsDisplay.setMap(null);
    this.directionsDisplay.setPanel(null);
    this.element.fire(this.directionsCloseEvent, {
      panel: this.options.directionsService.directionsPanel
    });
  },
  
  /**
    * Distance methods
    **/
  
  distanceBetween: function() {
    var a = this.latLngFromString(arguments[0]),
        b = this.latLngFromString(arguments[1]);
    return (a && b) && a.distanceFrom(b);
  },
  
  distanceBetweenMarkers: function() {
    var  a,
         b;
    if ((a = this.getMarker(arguments[0])) && (b = this.getMarker(arguments[1]))) {
      return a.getPosition().distanceFrom(b.getPosition());
    }
  },
  
  distanceFromMarkerById: function() {
    var id = arguments[0],
        c  = (arguments[1] == 'user' ? this.getUserLocation() : this.latLngFromString(arguments[1])) || this.maporigin;
    if (id && (marker = this.getMarker(id))) {
      return marker.getPosition().distanceFrom(c);
    }
  },
  
  distanceFrom: function() {
    var latlng = this.latLngFromString(arguments[0]);
    return latlng && latlng.distanceFrom(this.maporigin);
  },
  
  inRadius: function() {
    var r      = arguments[0],
        param  = arguments[1] || {};
    
    return this._inRadius(r,{
      c   : param.c,
      flag: param.flag
    });
  },
  
  inRadiusAll: function() {
    var r      = arguments[0],
        param  = arguments[1] || {};
    
    return this._inRadius(r,{
      c     : param.c,
      flag  : param.flag,
      all   : true
    });
  },
  
  /**
    * Latlng methods
    **/
  
  latLng: function(lat,lng) {
    return (lat && lng) && new google.maps.LatLng(lat,lng);
  },
  
  latLngFromString : function() {
    var s = arguments[0] || '',
        latlng;
    if (!s || !Object.isString(s)) return;
  
    latlng = s.split(/,s*/);
    
    var lat = parseFloat(latlng[0]);
    var lng = parseFloat(latlng[1]);
    
    if (isNaN(lat) || isNaN(lng)) {
      return '';
    }else{
      return this.latLng(lat, lng);
    }
  },
  
  /**
    * Infowindows methods
    **/
   
  showInfoWindow: function() {
    var id   = arguments[0],
        html = arguments[1] || this.markersHTML.get(id),
        marker;

    if (!id) return;
    if (!(marker = this.getMarker(id))) return;
    
    if (!marker.getVisible()) marker.setVisible(true);
    this.infowindows.get(id).setContent(html);
    this.infowindows.get(id).open(this.getMap(),marker);
    
    // Close all other windows, except the one referenced by id
    this.closeInfoWindowsExcept(id);
  }, 
  
  closeInfoWindowsExcept: function() {
    var id = arguments[0] || 0;
    this.getMarkers(true).each(function(el) {
      if (el.get('id') != id) this.infowindows.get(el.get('id')).close();
    }.bind(this));
  },
  
  closeInfoWindows: function() {
    var id = arguments[0];
    
    if (id) {
      return this.closeInfoWindowsById(id);
    }
    
    this.closeInfoWindowsExcept();
  },
  
  closeInfoWindowsById: function() {
    var id = arguments[0];
    if (!id) return;
    
    this.getMarkers(true).each(function(el) {
      if (el.get('id') == id) this.infowindows.get(el.get('id')).close();
    }.bind(this));
  },
  
  /**
    * Icon methods
   **/
  
  getIcon: function() {
    return this._icon(arguments[0]);
  },
  
  /**
    * Googlemaps object methods
   **/
  
  getGmap: function() {
    return this.Gmap;
  },
  getMap: function() {
    return this.Gmap;
  },
  initializeGmap: function(options) {
    this.Gmap = new google.maps.Map(this.element, options);
    google.maps.event.addListenerOnce(this.Gmap, 'tilesloaded', function() {
      this.options.afterTilesLoaded();
    }.bind(this));
  },
  
  /**
    * Point functions
   **/
  
  point: function() {
    var data  = arguments[0] || {};
    return (data.x && data.y) && new google.maps.Point(data.x, data.y);
  },
  
  /**
    * User location functions
   **/
  
  forceUserLocation: function() {
    this.options.userlocation = true;
    this.userLocation(true);
  },
  
  userLocation : function() {
    var forceDetect = arguments[0] || false;
    
    if (!this.options.userlocation) return;
    if (this.getUserLocation() && !forceDetect) return this.getUserLocation();
    
    this.userlocation = '';

    if (navigator.geolocation) {
      console.info("Navigator.geolocation for geolocation");
      navigator.geolocation.getCurrentPosition(function(position) {
        this.element.fire(this.geolocationLocationFound, {
          lat: position.coords.latitude,
          lng: position.coords.longitude
        });
      }.bind(this));
    }else if (google.gears) {
      console.info("Google.gears for geolocation");
      var geo = google.gears.factory.create('beta.geolocation');
          geo.getCurrentPosition(function(position) {
            this.element.fire(this.geolocationLocationFound, {
              lat: position.coords.latitude,
              lng: position.coords.longitude
            });
          }.bind(this));
    }
  },
  
  setUserLocation: function() {
    var loc = arguments[0];
    if (loc.lat && loc.lng) {
      console.info("Geolocation set");
      this.userlocation = new google.maps.LatLng(loc.lat,loc.lng);
    }
  },
  getUserLocation: function() {
    return this.userlocation;
  },
  
  /**
    * Internal methods
   **/
  
  initializeEventNames: function() {
    // Register eventhandlers
    this.beforeMarkersLoadingEvent  = 'markers:beforeLoading';
    this.markersLoadedEvent         = 'markers:afterLoading';
    this.markersInitializedEvent    = 'markers:initialized';
    this.directionsLoadedEvent      = 'directions:loaded';
    this.directionsErrorEvent       = 'directions:error';
    this.directionsCloseEvent       = 'directions:close';
    this.geolocationLocationFound   = 'geolocation:locationFound';
    
    this.clusterInitialize = 'cluster:initialize';
    this.clusterRethink    = 'cluster:rethink';
  },
  
  
  /**
    * Internal methods
   **/
  
  _addMarker: function() {
    var data    = arguments[0] || {},
        options = {},
        point;
    
    // ID must be defined
    data.id = data.id || new Date().getTime()+"_"+Math.ceil(1000*Math.random());
    
    // Coordinates must be defined
    if (!(point = this.latLng(data.x,data.y))) {
      console.warn("LatLng not defined for:",data.id,data.title);
      return;
    }
    if (!this.bounds) this.bounds = new google.maps.LatLngBounds();
    this.bounds.extend(point);
    
    var marker = new google.maps.Marker({
      'position'  : point,
      'map'       : this.options.useClusters ? null : this.getMap(),
      'title'     : data.title  ? data.title : null,
      'icon'      : data.icon   ? this.getIcon(data.icon) : null,
      'clickable' : this.options.markerClick ? true : false,
      'visible'   : true,
      'zIndex'    : data.zIndex || 10
    });
 
    this.markers.push($H({
      marker: marker,
      id    : data.id,
      type  : data.type   || 'default',
      extra : data.extra  || {}
    }));
    
    if (data.html) {
      this.markersHTML.set(data.id,this._myMap_genHTML(data.html));
      this.infowindows.set(data.id,new google.maps.InfoWindow());
    }
    
    if (this.options.markerClick) {
      google.maps.event.addListener(marker, 'click', function() {
        this.options.onMarkerClick.bind(this)(data);
      }.bind(this));
    }
    if (data.html && data.openInfoWindow) {
      this.showInfoWindow(data.id);
    } 
  },
  
  _deleteInfowindows: function() {
    var id = arguments[0];
    if (id && this.infowindows.get('id')) {
      this.infowindows.unset(id);
    }else{
      this.infowindows = new Hash();
    }
  },
  
  _deleteMarkerDataFromObject: function() {
    var id = arguments[0];
    this.closeInfoWindows(id);
    this._deleteMarkers(id);
    this._deleteMarkersHTML(id);
    this._deleteInfowindows(id);
    //this.element.fire(this.clusterInitialize);
  },
  
  _deleteMarkers: function() {
    var id = arguments[0],
        marker;
    if (id && (this.getMarker(id))) {
      this.markers.each(function(el,index) {
        if (el.get('id') == id) {
          this.markers.splice(index,1);
          return;
        }
      }.bind(this));
    }else{
      this.markers.clear();
    }
  },
  
  _deleteMarkersHTML: function(el) {
    var id = arguments[0];
    if (id && this.markersHTML.get(id)) {
      this.markersHTML.unset(id);
    }else{
      this.markersHTML = new Hash();
    }
  },
   
  _getMarkerById: function() {
    var id     = arguments[0],
        marker = this.markers.find(function(el) {
          return el.get('id') == id;
        });
    return marker && marker;
  },
  
  _getMarkersByType: function() {
    var type = arguments[0] || 'default';
    return this.markers.findAll(function(el) { 
      return (el.get('type') == type || new RegExp("(^|\\s)" + type + "(\\s|$)").test(el.get('type'))); 
    });
  },
  
  _icon: function() {
    var _image  = arguments[0]  || {},
        width   = _image.width  || 20,
        height  = _image.height || 20,
        originx = _image.originx || 0,
        originy = _image.originy || 0,
        anchorx = _image.anchorx || 10,
        anchory = _image.anchory || 10;
    
    var image = new google.maps.MarkerImage(_image.uri,
                new google.maps.Size(width, height),
                new google.maps.Point(originx,originy),
                new google.maps.Point(anchory, anchory)); 
    return image; 
  },
  
  _inRadius: function() {
    var r     = arguments[0],
        param = arguments[1],
        c     = param.c,
        all   = param.all  || false,
        flag  = param.flag || false;
        
    return this.getMarkers(true).collect(function(el) {
      if (!el.get('marker').getVisible() && !all) return;
      
      if (!r) {
        el.get('marker').setVisible(true);
        return flag ? el : el.get('marker');
      }else{
        if ( parseInt( this.distanceFromMarkerById(el.get('id'),c) ) > r) {
          el.get('marker').setVisible(false);
        }else{
          el.get('marker').setVisible(true);
          return flag ? el : el.get('marker');
        }
      }
    }.bind(this));
  },
  
  _myMap_genHTML: function(content) {
    var html  = '<div class="GMapLocationPopup">';
        html += content;
        html += '</div>';
    return html;
  },
  
  _toGooglemapsMarkerObject: function() {
    var markers = arguments[0] || this.markers;
    return markers.collect(function(el) {
      return el.get('marker');
    });
  },
  
  nop: function() {}
  
});

HM.Googlemaps= Class.create(HM.GooglemapsMethods, {

  initialize: function(){
    if (!(this.element = $(arguments[0]))) {
      console.error("Map element not defined");
      return false;
    };
    
    this.options = Object.extend({
      'hotspotsURI'             : '/handlers/json/hotspots.lp',
      'markerClick'             : true,
      'polygonClick'            : true,
      'latlng'                  : '51.685320,5.357615',
      'fitBounds'               : true,
      'zoom'                    : 8,
      'useClusters'             : false,
      'userlocation'            : false,
      'maptype'                 : 'ROADMAP',
      'mapTypeControlStyle'     : 'DROPDOWN_MENU',
      'navigationControlStyle'  : 'NORMAL',
      'directionsService'       : {
        'init'                      : true,
        'directionsPanel'           : 'GmapdirectionsPanel',
        'travelMode'                : 'DRIVING',
        'unitSystem'                : 'METRIC',
        'waypoints'                 : [],
        'optimizeWaypoints'         : false,
        'provideRouteAlternatives'  : false,
        'avoidHighways'             : false,
        'avoidTolls'                : false,
        'region'                    : 'nl'
      },
      
      'onMarkerClick'           : function(data) {
        this.showInfoWindow(data.id);
      },
      'beforeLoadingMarkers'    : function(event) {
        console.info("Before loading markers");
      },
      'markersLoaded'           : function(event) {
        console.info("Markers loaded");
      },
      'afterMarkersInitialized' : function(event) {
        console.info("Markers Initialized");
      },
      'onPolygonClick'            : function(data) {
        console.log(data);
      },
      'afterTilesLoaded'        : function(event) {
        console.info("Tiles loaded");
      },
      'afterDirectionsLoaded'   : function(event) {
        var element;
        if (element = $(event.memo.panel)) {
          if (!element.visible()) element.show();
        }
      },
      'afterDirectionsRemoved'  : function(event) {
        var element;
        if (element = $(event.memo.panel)) {
          if (element.visible()) element.hide();
        }
      },
      'directionsError'         : function(event) {
        var element;
        if (element = $(event.memo.panel)) {
          element.show();
          element.update('<p class="directionsError">'+event.memo.error+'</p>');
        }
      },
      'userLocationFound'       : function(event) {
        this.setUserLocation(event.memo);
      },
      'options'                 : {},
      'cluster'                 : {}   
    }, arguments[1] || {});
    
    // Center the map
    this.maporigin = this.latLngFromString(this.options.latlng);

    var options = Object.extend({
      zoom                  : this.options.zoom,
      center                : this.maporigin,
      mapTypeId             : google.maps.MapTypeId[this.options.maptype], // ROADMAP, HYBRID, SATELLITE, TERRAIN
      mapTypeControlOptions : {
        style: google.maps.MapTypeControlStyle[this.options.mapTypeControlStyle]  // HORIZONTAL_BAR 
      },
      navigationControlOptions: {
        style: google.maps.NavigationControlStyle[this.options.navigationControlStyle]  //
      }
    }, this.options.options || {});
    
    // Initialize googlemaps
    this.initializeGmap(options);
    
    // Initialize event names
    this.initializeEventNames();
    
    // Initialize directionsService
    if (this.options.directionsService.init) {
      this.initializeDirectionsService();
    }
    
    // Userlocation
    this.userLocation();
    
    this.element.observe(this.beforeMarkersLoadingEvent, this.options.beforeLoadingMarkers.bind(this));
    this.element.observe(this.markersLoadedEvent,       this.options.markersLoaded.bind(this));
    this.element.observe(this.markersInitializedEvent,  this.options.afterMarkersInitialized.bind(this));
    this.element.observe(this.directionsLoadedEvent,    this.options.afterDirectionsLoaded.bind(this));
    this.element.observe(this.directionsErrorEvent,     this.options.directionsError.bind(this));
    this.element.observe(this.directionsCloseEvent,     this.options.afterDirectionsRemoved.bind(this));
    this.element.observe(this.geolocationLocationFound, this.options.userLocationFound.bind(this));
    
    if (this.options.fitBounds) this.element.observe(this.markersInitializedEvent, this._fitMapBounds.bind(this));
    
    /*google.maps.event.addListenerOnce(this.Gmap, 'projection_changed', function() {
      alert("changed");
      console.log(this.getMap().getProjection().fromLatLngToDivPixel());
    }.bind(this));*/
    
    
    // Cluster
    if (this.options.useClusters) {
      this.element.observe(this.clusterInitialize, this.initializeClusters.bind(this));
      
      // Somehow this one is not working
      //this.element.observe(this.clusterReThink,    this.rethinkClusters.bind(this));
    }
    
    this.markers     = $A();
    this.polygons    = new Hash();
    this.markersHTML = new Hash();
    this.infowindows = new Hash();
    this.clusters    = null;
  },
  
  initializeClusters: function() {
    if (this.clusters == null) {
      this.clusters = new HM.GooglemapsClusters(this.getMap(),this.getMarkers(),this.options.cluster);
      if (this.timeout) window.clearTimeout(this.timeout);
      this.timeout = this.clusters.create.bind(this.clusters).delay(1);
      console.info("Clusters initialized");
    }else{
      this.rethinkClusters();
    }
  },
  
  rethinkClusters: function() {
    this.clusters.setMarkers(this.getMarkers());
    this.clusters.create();
    console.info("Clusters reset");
  },
  
  initializePolygon: function() {
    var paths = arguments[0] || null;
    var param = arguments[1] || {}; 
    
    if (!paths) return;
    
    var _paths = Object.isString(paths) ? this.stringToPathsArray(paths,param.mode) : paths;
    
    if (!Object.isArray(_paths)) return;
    if (!_paths.length) return;
    
    var polygon = new google.maps.Polygon({
      map           : this.getMap(),
      paths         : _paths,
      geodesic      : param.geodesic      || true,
      strokeColor   : param.strokeColor   || "#FF0000",
      strokeOpacity : param.strokeOpacity || 0.6,
      strokeWeight  : param.strokeWeight  || 2,
      fillColor     : param.fillColor     || "#FF0000",
      fillOpacity   : param.fillOpacity   || 0.2,
      zIndex        : param.zIndex
    });
    
    var bounds = new google.maps.LatLngBounds(_paths[0],_paths[0]);
    $A(_paths).each(function(el) {
      bounds.extend(el);
		});
    
    param.name = param.name || "default_"+Math.floor(Math.random()*1000000000000);
    
    this.polygons.set(param.name,new Hash({
      polygon : polygon,
      bounds  : bounds,
      name    : param.name,
      extra   : param.extra || {}
    }));
    
    if (this.options.polygonClick) {
      google.maps.event.addListener(polygon, 'click', function() {
        this.options.onPolygonClick.bind(this)(this.polygons.get(param.name));
      }.bind(this));
    }
  },
  
  removePolygons: function() {
    this.polygons.each(function(pair) {
      pair.value.get("polygon").setMap(null);
    });
  },
  
  stringToPathsArray: function() {
    var s    = arguments[0] || null,
        mode = arguments[1] || "lnglat"; 
    if (!s) return [];
    
    //var list = s.split('|');
    return $A(s.split('|')).collect(function(n) {
      var xy = n.split(",");
      return mode == "lnglat" ? new google.maps.LatLng(parseFloat(xy[1]),parseFloat(xy[0])) : new google.maps.LatLng(parseFloat(xy[0]),parseFloat(xy[1]));
    });
  },
  
  fitMapBounds: function() {
    var bounds = arguments[0] || this.bounds;
    if (bounds.isEmpty()) return;
    this.getMap().fitBounds(bounds);
  },
  
  _fitMapBounds: function() {
    if (this.bounds.isEmpty()) return;
    this.getMap().fitBounds(this.bounds);
  },
  
  nop: function() {}
  
});
