// this file uses the YUI Doc standard for documentation
// more information and a parser can be found at:
// http://developer.yahoo.com/yui/yuidoc/

// create namespace if it doesn't exist yet
if (typeof window.WLCP == "undefined") window.WLCP = new Object();

/**
 * The Map module provides a binding for the GoogleMaps to show geotagged assets
 * and to tag them
 * @module Map
 */

/**
 * Represents a map and provides methods to add markers and to configure the
 * representation
 *
 * @class Map
 * @namespace WLCP
 * @constructor
 * @param domId {String} Dom ID of the map container
 * @param options {Object} Associative Array with configuration options
 */
WLCP.Map = function(domId, options) {

  /////////////////////////////////////////////////////////////////////////
  //
  // Private properties
  //
  /////////////////////////////////////////////////////////////////////////

  /**
   * The Dom ID of the map container
   *
   * @property _domId
   * @type String
   * @private
   */
  this._domId = domId;

  /**
   * Associative array of configuration options
   *
   * @property _options
   * @type Object
   * @private
   */
  this._options = options || {};

  /**
   * Pool of GMarkers
   *
   * @property _markers
   * @type Gmarker[]
   * @private
   */
  this._markers = [];

  /**
   * Pool of draggable markers, indexed by ident
   *
   * @property _draggableMarkers
   * @type Object
   * @private
   */
  this._draggableMarkers = {};

  /**
   * Flag, true if map is already renderd, false otherwise
   *
   * @property _rendered
   * @type boolean
   * @private
   */
  this._rendered = false;

  /**
   * Refernce to the GMap
   *
   * @property _map
   * @type GMap2
   * @private
   */
  this._map = null;

  /**
   * Refernce to the Geocoder
   *
   * @property _geocoder
   * @type GClientGeocoder
   * @private
   */
  this._geocoder = new GClientGeocoder();

  /**
   * Reference to the Clusterer
   *
   * @property _clusterer
   * @type Clusterer
   * @private
   */
  this._clusterer = null;
}


/////////////////////////////////////////////////////////////////////////
//
// Private static constants
//
/////////////////////////////////////////////////////////////////////////

/**
 * Maximum visible markers default value
 *
 * @property MAX_VISIBLE_MARKERS
 * @type Number
 * @static
 * @final
 * @default 100
 */
WLCP.Map.MAX_VISIBLE_MARKERS = 100;

/**
 * Minimum markers per cluster
 *
 * @property MIN_MARKERS_PER_CLUSTER
 * @type Number
 * @static
 * @final
 * @default 1
 */
WLCP.Map.MIN_MARKERS_PER_CLUSTER = 1;


/**
 * Maximum number on lines in an info box
 *
 * @property MAX_LINES_PER_INFO_BOX
 * @type Number
 * @static
 * @final
 * @default 3
 */
WLCP.Map.MAX_LINES_PER_INFO_BOX = 3;

/**
 * Error message shown if browser is not compatible to Google Maps
 *
 * @property ERROR_NOT_COMPATIBLE
 * @type String
 * @static
 * @final
 * @default Google Maps doesn't work in your browser.
 */
WLCP.Map.ERROR_NOT_COMPATIBLE = "Google Maps doesn't work in your brower.";

/**
 * Message shown, when position was sent successfully
 *
 * @property SUCCESS_SEND_DRAGGABLE_POSITION
 * @type String
 * @static
 * @final
 * @default Position sent.
 */
WLCP.Map.SUCCESS_SEND_DRAGGABLE_POSITION = "Position sent.";

/**
 * Error shown, when position sending failed
 *
 * @property ERROR_SEND_DRAGGABLE_POSITION
 * @type String
 * @static
 * @final
 * @default Position could not be sent.
 */
WLCP.Map.ERROR_SEND_DRAGGABLE_POSITION = "Position could not be sent.";

/////////////////////////////////////////////////////////////////////////
//
// Public methods
//
/////////////////////////////////////////////////////////////////////////

WLCP.Map.prototype = {
  /**
   * Add marker from associative array
   *
   * @method addMarker
   * @param markerInfo {Object} Description of the marker to add
   * @return {GMarker} GMarker Object
   */
  addMarker: function(markerInfo) {
    var point = new GLatLng(parseFloat(markerInfo.latitude),
                            parseFloat(markerInfo.longitude));
    var marker = new GMarker(point);

    // add info window
    GEvent.addListener(marker, "click", function() {
        marker.openInfoWindowHtml('<div>' + markerInfo.info + '</div>');
    });

    // push marker in pool
    this._markers.push(marker);

    // add marker to map, if map is already rendered
    if (this._rendered) {
      this._clusterer.AddMarker(marker, "test");
    }
    return marker;
  },

  /**
   * Add markers from an Array, using addMarker.
   *
   * @method addMarkers
   * @param markers {Object[]} Array of marker descriptions
   */
  addMarkers: function(markers) {
    for (var i=0; i < markers.size(); i++) {
      this.addMarker(markers[i]);
    }
    return true;
  },

  /**
   * Add a draggable marker
   *
   * @method addDraggableMarker
   * @param location {GLatLng|String} Position or Address of the location
   * @param ident {String} identifier to get position of this marker later
   */
  addDraggableMarker: function(location, ident) {
    // define call backback for geocoding
    var self = this; // save this in self for callback
    var callback = function(point) {
      // default to 0,0 if position is empty
      if (!point.lat() || !point.lng()) {
        point = new GLatLng(0, 0);
      }
      var marker = new GMarker(point, {draggable: true});
      self._draggableMarkers[ident] = marker;
      self._markers.push(marker);
    }

    // use geocoding, if location is a string
    if (typeof location == "string") {
      this._geocoder.getLatLng(location, callback);
    }
    // otherwise use the position given
    else {
      var point = new GLatLng(parseFloat(location.latitude),
                              parseFloat(location.longitude));

      callback(point);
    }
  },

  /**
   * Send position of draggable marker via AJAX to defined url
   *
   * @param sendDraggablePosition
   * @param ident {String} Identifier to get position of this marker later
   * @param url {String} URL to which the reqeust is sent
   * @param options {Object} Prototype AJAX options.
   *                         Parameters will be overridden with position.
   *                         Default callbacks with alert calls are used
   *                         if onSuccess & onFailure are not set
   */
  sendDraggablePosition: function(ident, url, options) {
    options = options || {};
    var marker = this._draggableMarkers[ident];
    var point = marker.getLatLng();

    // register default callbacks
    var self = this;
    options.onSuccess = options.onSuccess || function() {
        alert(self._options.successSendDraggablePosition ||
              WLCP.Map.SUCCESS_SEND_DRAGGABLE_POSITION);
    };
    options.onFailure = options.onFailure || function() {
        alert(self._options.errorSendDraggablePosition ||
              WLCP.Map.ERROR_SEND_DRAGGABLE_POSITION);
    };

    options.parameters = {lat: point.lat(), lng: point.lng()};
    new Ajax.Request(url, options); // NEEDS PROTOTYPE
  },

  /**
   * Renders the map in the map container
   *
   * @method render
   * @return {boolean} true on success, false otherwise
   */
  render: function() {
    if (!GBrowserIsCompatible()) {
      alert(this._options.errorNotCompatible || WLCP.Map.ERROR_NOT_COMPATIBLE);
      return false;
    }
    var container = document.getElementById(this._domId);
    if (typeof container == "undefined" || container == null) {
      return false;
    }

    // set up map and clusterer
    this._map = new GMap2(container);
    this._clusterer = new Clusterer(this._map);
    this._applyConfig();

    // add markers
    for (var i=0; i < this._markers.size(); i++) {
      this._clusterer.AddMarker(this._markers[i], "test");
    }

    // focus map
    this.showAllMarkers();

    this._rendered = true;
    return true;
  },

  /**
   * Finds a zoom level and a center for the map at which all markers can be seen
   * and applies zoom level and center to the map
   *
   * @method showAllMarkers
   */
  showAllMarkers: function() {
    if (this._markers.size() == 0) return false;

    var bounds = new GLatLngBounds();
    for (var i=0; i < this._markers.size(); i++) {
      bounds.extend(this._markers[i].getLatLng());
    }

    var center = bounds.getCenter();
    var zoom   = this._map.getBoundsZoomLevel(bounds);

    this._map.setCenter(center, zoom);
  },
                  

  /////////////////////////////////////////////////////////////////////////
  //
  // Protected methods
  //
  /////////////////////////////////////////////////////////////////////////

  /**
   * Applies configuration to map and clusterer
   *
   * @method _applyConfig
   * @protected
   */
  _applyConfig: function() {
    // maps controls
    var controls = this._options.mapControls || [];
    for (var i=0; i < controls.size(); i++) {
      var Control = eval(controls[i]);
      this._map.addControl(new Control());
    }

    // clusterer settings
    this._clusterer.SetMaxVisibleMarkers(
      this._options.maxVisibleMarkers || WLCP.Map.MAX_VISIBLE_MARKERS
    );
    this._clusterer.SetMinMarkersPerCluster(
      this._options.minMarkersPerCluster || WLCP.Map.MIN_MARKERS_PER_CLUSTER
    );
    this._clusterer.SetMaxLinesPerInfoBox(
      this._options.maxLinesPerInfoBox || WLCP.Map.MAX_LINES_PER_INFO_BOX
    );
  }
}


/////////////////////////////////////////////////////////////////////////
//
// Static private Properties
//
/////////////////////////////////////////////////////////////////////////

/**
 * Pool of map objects
 *
 * @property _maps
 * @static
 * @private
 */
WLCP.Map._maps = new Array();


/////////////////////////////////////////////////////////////////////////
//
// Static Methods
//
/////////////////////////////////////////////////////////////////////////

/**
 * Creates new map objects, attache it to the pool
 * and registers the render method for exeuction at window load
 *
 * @method create
 * @static
 * @param domId {String} The Dom ID of the map container
 * @param options {Object} Associative array of configuration options
 */
WLCP.Map.create = function(domId, options) {
  // set up map object
  var map = new WLCP.Map(domId, options);

  // push map object in the pool
  WLCP.Map._maps.push({domId: domId, 'map': map});

  // render map on load
  if (document.loaded) { // NEEDS PROTOTYPE >1.6
    map.render();
  }
  else {
    Event.observe(window, 'load', // NEEDS PROTOTYPE
      function() { map.render(); }
    );
  }
  return map;
}

/**
 * Get map object from the object pool
 *
 * @method get
 * @static
 * @param domId {String} the Dom ID of the map container
 */
WLCP.Map.get = function(domId) {
  var map;
  for (key in WLCP.Map._maps) {
    map = WLCP.Map._maps[key];
    if (map.domId == domId) return map.map;
  }
  return null;
}
