var MarkerSet = Class.create({

  treshold: 5,
  currentPromotion: 0,

  initialize: function() {
    this.markers = [ ];
  },

  /**
   * Test if two markers are equals
   */
  marker_equals: function(left, right) {
    var distance = left.getLatLng().distanceFrom(right.getLatLng());

    return left.getTitle() == right.getTitle()
            && distance < this.treshold;
  },

  /**
   * add a marker to the set.
   * returns the marker, if it is added, or null if it is not
   */
  add: function(newmarker) {
    var isUnique = !this.markers.any(this.marker_equals.curry(newmarker));

    if (isUnique) {
      this.markers.push(newmarker);
      return newmarker;
    }
    else {
      return null;
    }
  },

  /**
   * add all markers to the set
   * returns a list of the added markers.
   */
  extend: function(newMarkers) {
    var result = newMarkers.map(this.add.bind(this));
    return result.compact();
  },
  
  /**
   *
   */
  find: function(title, latitude, longitude) {
    result = [ ]
    latlng = new GLatLng(latitude, longitude);
    
    this.markers.each(function(marker) {
      if (title == marker.getTitle() && marker.getLatLng().distanceFrom(latlng) < this.treshold)
        result.push(marker);
    }.bind(this));
    
    return result;
  },
  
  findUser: function(userId) {
    user = null;
    
    this.markers.each(function(marker) {
      if (marker.userId == userId) user = marker;
    }.bind(this));
    
    return user;
  },
  
  findNextPromotion: function(bounds) {
    start = this.currentPromotion;
    while (this.markers.length > 0) {
      if (this.currentPromotion < this.markers.length) this.currentPromotion++;
      if (this.currentPromotion >= this.markers.length) this.currentPromotion = 0;

      currentMarker = this.markers[this.currentPromotion];
      if (
        !currentMarker.isHidden() &&
        currentMarker.promotions &&
        bounds.contains(currentMarker.getLatLng())
      ) return currentMarker;

      if (this.currentPromotion == start) return null;
    }
    return null;
  },

  /**
   * return an array of all the Markers that match the Visibilityhint
   * and remove them from the set.
   */
  splitByCategory: function(categoryKey) {
    var drop = this.markers.findAll(function(marker) {
      return marker.categoryKey == categoryKey;
    });
    this.markers = this.markers.findAll(function(marker) {
      return marker.categoryKey != categoryKey;
    });
    return drop;
  }

});

var MapHandler = Class.create({

  visibleCategoriesMax: 8,
  visibleCategoriesCount: 0,
  
  userPriority: 1,

  initialize: function(map, categories) {
    this.map = map;
    this.markerManager = new MarkerManager(map);

    GEvent.bind(map, "moveend", this, this.searchCategories);
    GEvent.bind(map, "zoomend", this, this.searchCategories);

    this.markerSet = new MarkerSet();
    this.hiddenMarkerSet = new MarkerSet();

    // add categories that are already selected
    this.categories = [ ];
    Object.values(categories).each(function(category) {
      if (category.selected) {
        this.categories.push(category);
      }
    }.bind(this));
    
    // trigger intial search if necessary
    if (this.categories.size() > 0) {
      this.searchCategories();
    }
  },

  toggleCategory: function(category) {
    if (this.categories.include(category)) {
      this.hideCategory(category);
      if (this.visibleCategoriesCount > 0) this.visibleCategoriesCount--;
    } else {
      if (this.visibleCategoriesCount < this.visibleCategoriesMax) {
        this.visibleCategoriesCount++;
      } else {
        this.hideCategory(this.categories[0]);
      }
      this.showCategory(category);
    }
  },

  addUser: function(user, loc, showRadius) {
    if (showRadius) {
      this.map.addOverlay(GPolyline.Circle(new GLatLng(loc.latitude, loc.longitude), loc.radius, '#00425c', 2, 1));
    } else {
      var iconPath = '/images/categories/25x25/user_marker.png';

      var marker = createMarker(
        loc.latitude, loc.longitude,
        this.userPriority,
        user.nick + (loc.time ? ' / ' + loc.timedelta : ''),
        iconPath,
        user.nick,
        null,
        null,
        null,
        this.showUserInfoWindow.curry(user)
      );

      this.addUserMarker(marker);
      this.showUserInfoWindow(user, marker);
    }
  },

  showUserInfoWindow: function(user, marker) {
    new Ajax.Request("/users/" + user.nick + "/marker", {
      asynchronous: true,
      method: 'get',
      onSuccess: function(transport) {
        map.map.openInfoWindowHtml(marker.getLatLng(), transport.responseText, { maxWidth: 300 });
      }
    });
  },

  searchCategories: function() {
    var bounds = this.map.getBounds();
    var callback = this.onResults.bind(this);

    new Search(callback, {
      northEast: bounds.getNorthEast(),
      southWest: bounds.getSouthWest(),
      categories: this.categories,
      userIds: [ ]
    });
  },
  
  findMarkers: function(title, latitude, longitude) {
    return this.markerSet.find(title, latitude, longitude);
  },

  onResults: function(markers) {
    markers.each(function(marker) {
      if (marker.userId != null) this.addUserMarker(marker);
      if (marker.spotId != null) this.addSpotMarker(marker);
    }.bind(this));
  },

  addUserMarker: function(marker) {
    existingMarker = this.markerSet.findUser(marker.userId);
    if (existingMarker) this.removeMarkers([ existingMarker ]);
    this.addMarkers([ marker ]);
  },
  
  addSpotMarker: function(marker) {
    if (!this.isCategorySelected(marker.categoryKey)) return;
    
    existingMarkers = this.markerSet.find(marker.getTitle(), marker.getLatLng().lat(), marker.getLatLng().lng());
    
    addMarker = true;
    existingMarkers.each(function(existingMarker) {
      if (existingMarker.priority > marker.priority) {
        this.removeMarkers([ existingMarker ]);
      } else {
        addMarker = false;
      }
    });
    
    if (addMarker) { this.addMarkers([ marker ]); }
  },
  
  addMarkers: function(markers) {
    markers = this.markerSet.extend(markers);
    this.markerManager.addMarkers(markers, this.map.getZoom());
    this.markerManager.refresh();
  },

  removeMarkers: function(markers) {
    markers.each(function(marker) {
      this.markerManager.removeMarker(marker);
    }.bind(this));
  },
  
  nextPromotionMarker: function() {
    return this.markerSet.findNextPromotion(this.map.getBounds());
  },

  switchCategoryImage: function(category, grey) {
    var src = grey
            ? '/images/categories/25x25/' + category.id + '.png'
            : '/images/categories/25x25/' + category.id + '_disabled.png'
            ;

    $('icon_category_' + category.id).src = src;
  },
  
  updateLayoutSetting: function(category, select) {
    if (!currentUser) return;
    
    new Ajax.Request('/layout_setting', {
      method: 'put',
      parameters:
        'authenticity_token=' + encodeURIComponent(authenticityToken) +
        '&category=' + encodeURIComponent(category.key) +
        '&select=' + (select ? 'true' : 'false')
    });
  },
  
  isCategorySelected: function(categoryKey) {
    selected = false;
    
    this.categories.each(function(category) {
      if (category.key == categoryKey) selected = true;
    });
    
    return selected;
  },

  showCategory: function(category) {
    var hidden = this.hiddenMarkerSet.splitByCategory(category.key);
    this.addMarkers(hidden);
    this.categories.push(category);
    this.updateLayoutSetting(category, true);
    this.switchCategoryImage(category, true);
    this.searchCategories();
  },

  hideCategory: function(category) {
    var hidden = this.markerSet.splitByCategory(category.key);
    this.removeMarkers(hidden);
    this.hiddenMarkerSet.extend(hidden);
    this.markerManager.refresh();
    this.categories = this.categories.without(category);
    this.updateLayoutSetting(category, false);
    this.switchCategoryImage(category, false);
  }

});

