var LayerMarkerManager = Class.create({
  initialize: function() {
    this.layerManager = $H({});
    this.markersByLayer = $H({});
    this.markerById = $H({});
  },

  addLayer: function(name,layer) {
    this.layerManager.set(name,layer);
    this.markersByLayer.set(name, $A());
  },

  getLayerNames: function() {
    return this.layerManager.keys();
  },

  getLayer: function(name) {
    return this.layerManager.get(name);
  },

  getMarker: function(id) {
    return this.markerById.get(id);
  },

  containsId: function(id) {
    return typeof(this.markerById.get(id))!='undefined';
  },

  removeMarker: function(name,id) {
    var marker = this.markerById.unset(id);

    var markers = this.markersByLayer.get(name);
    var layerMarkers = markers.findAll(function(layer_marker) {
      return layer_marker==marker;
    });
    this.markersByLayer.set( name, layerMarkers);

    marker.setMap(null);
    return marker;
  },

  filterByBound: function(bounds, layerName) {
//    console.log("filterByBound layerName="+layerName+", this.markerById "+this.markerById.size()+" "+layerName);
    var selected_markers = this.markersByLayer.unset(layerName).select( function(marker) {
      var inside = bounds.contains(marker.getPosition());
      if (!inside) {
        marker.setMap(null);
        this.markerById.unset(marker.get("locationId"));
      }
      return inside;
    }.bind(this));
    this.markersByLayer.set(layerName,selected_markers);
  },

  clean: function( layerName) {
//    console.log("clean(layerName="+layerName+")");
    var markers = this.markersByLayer.get( layerName);
    markers.invoke( "setMap", null);
    markers.each(function(marker){
      this.unset(marker.get("locationId"));
    }.bind(this.markerById));
    markers.clear();
  },

  addMarker: function(marker,name,id) {
    var markers = this.markersByLayer.get(name);
    if ( typeof(markers) == 'undefined') {
      console.log("unknown layer "+name);
    }
    markers.push( marker);
    this.markerById.set(id,marker);

    return marker;
  },

  log_state: function() {
    console.log( "markerById size "+this.markerById.keys().size()+"markersByLayer size "+this.markersByLayer.values().flatten().size());
  },

  bounds: function() {
    var bounds = new google.maps.LatLngBounds();
    this.markerById.values().each( function(marker){
      bounds.extend(marker.getPosition());
    });
    return bounds;
  }

});

var MapView = Class.create({

  initialize: function(element, options) {
    this.map_element = $(element);

    this.options = Object.extend(Object.extend({ },this.defaultOptions), options || { });

    var mapTypeId = google.maps.MapTypeId.ROADMAP;
    if (this.options.mapType=="normal") {
      mapTypeId = google.maps.MapTypeId.ROADMAP;
    } else if (this.options.mapType=="physical") {
      mapTypeId = google.maps.MapTypeId.TERRAIN;
    }

    this.map = new google.maps.Map(this.map_element, {
      zoom: this.options.zoom,
      center: new google.maps.LatLng(this.options.centerLat, this.options.centerLng),
      mapTypeId: mapTypeId,
      mapTypeControlOptions: {style: google.maps.MapTypeControlStyle.DROPDOWN_MENU},
      mapTypeControl: true,
      navigationControl: true,
      scaleControl: true,
      scrollwheel: true
    });
    this.infoWindow = new google.maps.InfoWindow();

    this.layerMarkerManager = new LayerMarkerManager();

    this.label = new Label({ map: this.map });
  },

  addLayer: function(name,layerProperty) {
    this.layerMarkerManager.addLayer(name,layerProperty);
  },

  map: function() {
    return this.map;
  },

  cleanLocationsOutOfBound: function( layerName) {
//    console.log( "MapView.cleanLocationsOutOfBound(layerName="+layerName+")");
    this.layerMarkerManager.filterByBound(this.map.getBounds(), layerName);
  },

  cleanLocations: function( layerName) {
//    console.log( "MapView.cleanLocations(layerName="+layerName+")");
    this.layerMarkerManager.clean(layerName);
  },

  log_state: function() {
    this.layerMarkerManager.log_state();
  },

  // The marker name is a unique key to not re-display 2 times the same point
  displayLocation: function(location) {
    if ( !this.layerMarkerManager.containsId(location.getId())) {

      var layerName = location.getLayer();
      var layer = this.layerMarkerManager.getLayer(layerName);
      var options = {
        icon: layer.getImage(location),
        position: new google.maps.LatLng(location.getLat(), location.getLng()),
        map: this.map,
        visible: true,
        locationId: location.getId(),
        text: location.getName()
      };

      var marker = new google.maps.Marker( options);

      layer.markerCallBack(this,marker,location);
      
      this.layerMarkerManager.addMarker( marker, layerName, location.getId());

      var my_label = this.label;
      google.maps.event.addListener(marker, 'mouseover',
        function() { my_label.set("marker", marker); });
      google.maps.event.addListener(marker, 'mouseout',
        function() { my_label.hide(); });
    }

    return this.layerMarkerManager.getMarker(location.getId());
  },

  closeInfoWindow: function() {
    this.infoWindow.close();
  },

  extendedBounds: function() {
    var bounds = this.map.getBounds();
    var sw = bounds.getSouthWest();
    var ne = bounds.getNorthEast();

    var lngSize = ne.lng() - sw.lng();
    var latSize = ne.lat() - sw.lat();

    return {
      minLat: (sw.lat() - latSize/2),
      maxLat: (ne.lat() + latSize/2),
      minLng: (sw.lng() - lngSize/2),
      maxLng: (ne.lng() + lngSize/2)
    };
  },

  getZoom: function() {
    return this.map.getZoom();
  },
  setZoom: function(zoom) {
    this.map.setZoom(zoom);
  },

  centerMap: function(center) {
    this.map.setCenter(center);
  },

  centerMapOnMarkers: function() {
    this.map.fitBounds(this.layerMarkerManager.bounds());
  }

});

