/**
 *  == base ==
 *  The main section
**/

/** section: base
 * Mapeed
 **/

// Namespace
if (typeof Mapeed == 'undefined') {
  Mapeed = {};
}

/**
 * Mapeed.AddressChooser
**/
Mapeed.AddressChooser = {};

// Default options for AddressChooser Widget

Mapeed.AddressChooser.DefaultOptions = { map:             'map',
                                         lat:             'lat',
                                         lng:             'lng',
                                         street:          'street',
                                         zip:             'zip',
                                         city:            'city',
                                         state:           'state',
                                         country:         'country',
                                         spinner:          false,
                                         icon:             null,
                                         auto:             true,
                                         delay:            300,
                                         showAddressOnMap: true,
                                         markerDraggable:  true,
                                         mapProxy:         Mapeed.Proxy.GoogleMap,
                                         onInitialized:    function() {} };
// Internal options keys for input field
Mapeed.AddressChooser.AddressKeys  = ['street', 'city', 'state', 'country', 'zip'],

/** section: base
 * class Mapeed.AddressChooser.Widget
 *
   * Class to add "AddressChooser" behavior to a form. HTML markup must be present in DOM (nothing is generated by this object).
 * Supported input fields are: input[type=text], select and textarea
 *
 * It creates a Map object depending on mapping system on *map* DOM element. When map is ready *onInitialized* id called. In this callback map
 * can be customized.
 *
 * There are also 2 events fired by this object:
 *
 *  **suggests:started**: When a request is send to mapping system to get placemarks suggests from current address.
 *  **suggests:found**: When a response is received by mapping system.
 *
 *  Default options are (required fields are in bold):
 *  <table class='options'>
 *    <tr>
 *      <th>Name</th>
 *      <th>Default value</th>
 *      <th>Description</th>
 *    </tr>
 *    <tr>
 *      <td>**map**</td>
 *      <td>map</td>
 *      <td>DOM id of map element</td>
 *    </tr>
 *    <tr>
 *      <td>**lat**</td>
 *      <td>lat</td>
 *      <td>DOM id of lat field (required)</td>
 *    </tr>
 *    <tr>
 *      <td>**lng**</td>
 *      <td>lng</td>
 *      <td>DOM id of lng field (required)</td>
 *    </tr>
 *    <tr>
 *      <td>street</td>
 *      <td>street</td>
 *      <td>DOM id of street field,can be use as unique address field</td>
 *    </tr>
 *    <tr>
 *      <td>city</td>
 *      <td>city</td>
 *      <td>DOM id of city field</td>
 *    </tr>
 *    <tr>
 *      <td>zip</td>
 *      <td>zip</td>
 *      <td>DOM id of zip field</td>
 *    </tr>
 *    <tr>
 *      <td>state</td>
 *      <td>state</td>
 *      <td>DOM id of state field</td>
 *    </tr>
 *    <tr>
 *      <td>country</td>
 *      <td>country</td>
 *      <td>DOM id of country field</td>
 *    </tr>
 *    <tr>
 *      <td>spinner</td>
 *      <td>false</td>
 *      <td>DOM id of a spinner element shown when a suggest search starts and hidden when a response is received</td>
 *    </tr>
 *    <tr>
 *      <td>icon</td>
 *      <td>null</td>
 *      <td>Icon object (depending on map system) to override default icon</td>
 *    </tr>
 *    <tr>
 *      <td>auto</td>
 *      <td>true</td>
 *      <td>Update map while typing</td>
 *    </tr>
 *    <tr>
 *      <td>delay</td>
 *      <td>300</td>
 *      <td>Delay in milliseconds before after typing address before searching for placemarks</td>
 *    </tr>
 *    <tr>
 *      <td>showAddressOnMap</td>
 *      <td>true</td>
 *      <td>Display current selected address in info window</td>
 *    </tr>
 *    <tr>
 *      <td>markerDraggable</td>
 *      <td>true</td>
 *      <td>Make marker on map draggable to move its position if mapping system has this feature</td>
 *    </tr>
 *    <tr>
 *      <td>mapProxy</td>
 *      <td>Mapeed.Proxy.GoogleMap</td>
 *      <td>Map proxy object. This allows to change mapping system. see Map.Proxy.GoogleMap to get information on how to create your own proxy.</td>
 *    </tr>
 *    <tr>
 *      <td>onInitialized</td>
 *      <td>empty function</td>
 *      <td>Callback called when the widget is ready (when map is ready to use).</td>
 *    </tr>
 *  </table>
 *
 **/

/** section: base
*  new Mapeed.AddressChooser.Widget([options])
*  - options (Hash): override default options.
*
*  Creates a new Mapeed.AddressChooser widget to add map based behavior to a regular address form.
*
**/
Mapeed.AddressChooser.Widget = function(options) {
  // Internal: Gets event to listen for an element. INPUT, TEXTAREA, and SELECT are allowed
  function eventForElement(element) {
    return element.tagName == 'INPUT' || element.tagName == 'TEXTAREA' ? 'keyup' : 'change';
  }
  
  function extend(destination, source) {
    for (var property in source)
      destination[property] = source[property];
    return destination;
  }
  
  function updateMap(event, delay) {
    // Do not handle keys like arrows, escape... just accept delete/backspace
    if (event) {
      var key = event.keyCode;
      if (event.charCode || (key >0 && key < 47 && key != 8 && key != 46)) return;
    }

    // Needs to wait before updating the map
    if (delay) {
      var self = this;
      // Clear existing timer
      if (this.timeout) clearTimeout(this.timeout);

      // Starts a new timer
      this.timeout = setTimeout(function() {updateMap.call(self)}, delay);
    }
    else {
      this.updateMap();
    }
  }
  
  // Internal: init callback when map is ready
  function init() {
    var options  = this.options,
        allKeys  = Mapeed.AddressChooser.AddressKeys.concat('lat', 'lng', 'spinner');
    
    // Get html elements for read/write values
    for (var i = allKeys.length-1; i>=0; --i){
      var k = allKeys[i];
      this[k] =  document.getElementById(options[k]);
    }
    
    // Check lat/lng required fields
    var map = document.getElementById(this.options.map);
    if (!this.lat || !this.lng || !map) {
      throw 'lat/lng/map are required options and valid DOM elements.'
    }
    
    // Connect event listener for auto mode
    if (options.auto) {
      var callback    = function(event) {updateMap.call(this, event, this.options.delay)},
          addressKeys = Mapeed.AddressChooser.AddressKeys;
      for (var i = addressKeys.length-1; i>=0; --i) {
        var k = addressKeys[i];
        if (this[k]) this.mapProxy.addEventListener(this[k], eventForElement(this[k]), this, callback);
      }
    }
    this.options.onInitialized(this);
  }

  // Apply default options
  this.options = extend({}, Mapeed.AddressChooser.DefaultOptions);
  extend(this.options, options);

  this.placemarks = [];

  // Initialize proxy with init callback
  this.element  = document.getElementById(this.options.map);

  this.mapProxy = new this.options.mapProxy(this.element, init, this);
};


// Instance methods
Mapeed.AddressChooser.Widget.prototype = (function() {  
  // Internal: Gets value of an element. INPUT, TEXTAREA, and SELECT are allowed
  function valueForElement(element) {
    if (element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') {
      return element.value;
    } 
    else {
      return element.options[element.selectedIndex].value;
    }
  }
  
  /** section: base
   *  Mapeed.AddressChooser.Widget#updateMap() -> undefined
   *  
   *  fires addresschooser:suggests:started, addresschooser:suggests:found
   *  
   *  Removes a handler that was installed using addEventListener
   **/
  function updateMap(event, delay) {
    // OL
    $('#map').show();
    if (this.spinner) this.spinner.style.display = 'block';
    
    // Fires addresschooser:suggests:started
    this.mapProxy.trigger(this.element, 'addresschooser:suggests:started');
    // Ask map proxy for getting placemarks
    this.mapProxy.getPlacemarks(this.getCurrentAddress(), _placemarksReceived, this);
  }
  
  /** section: base
   *  Mapeed.AddressChooser.Widget#initMap([showAddress = false, zoom = 5]) -> undefined
   *  - zoom (Integer): map zoom (default 5)
   *  
   *  Initiliazes map with current form values. Use lat/lng values if defined, else get current address from input fields.
   *  If address is empty then center map on user location.
   **/
  function initMap(zoom) {
    if (this.lat.value && this.lng.value) {
      this.mapProxy.showMarker(this.lat.value, this.lng.value, zoom || 5, 
                               this.options.showAddressOnMap ? this.getCurrentAddress().join('<br/>') : false , _markerDragEnd, this)
      // OL
      $('#map').show();
    }
    else {
      var address = this.getCurrentAddress();
      if (address.length == 0) {
        this.centerOnClientLocation(zoom || 5);
        // OL
        $('#map').hide();
      } else {
        this.updateMap();
      }
    }
  }
  
  /** section: base
   *  Mapeed.AddressChooser.Widget#showPlacemark(index) -> undefined
   *  - index (Integer): index of suggested placemark, must be valid
   *  
   *  Displays suggested placemark on the map.
   **/
  function showPlacemark(index) {
    if (this.placemarks && index< this.placemarks.length) {
      var placemark = this.placemarks[index];
      if (this.options.markerDraggable) {
        this.mapProxy.showPlacemark(placemark, this.options.showAddressOnMap, _markerDragEnd, this);
      }
      else {
        this.mapProxy.showPlacemark(placemark, this.options.showAddressOnMap);
      }
      this.lat.value = this.mapProxy.getLat(placemark);
      this.lng.value = this.mapProxy.getLng(placemark);
    }
  }
      
  /** section: base
   *  Mapeed.AddressChooser.Widget#getCurrentAddress() -> String
   *  
   *  Returns current address from input field values
   **/
  function getCurrentAddress() {
    var address     = [], 
        addressKeys = Mapeed.AddressChooser.AddressKeys;

    for (var i = addressKeys.length-1; i>=0; --i) {
      var k = addressKeys[i];
      if (this[k]) {
        var value = valueForElement(this[k]);
        // Strip string
        value = value.replace(/^\s+/, '').replace(/\s+$/, '');
        if (value.length > 0) address.unshift(value);
      }
    }
    return address;
  }

  /** section: base
   *  Mapeed.AddressChooser.Widget#getMapProxy() -> Mapeed.Proxy
   *  
   *  Returns current map proxy
   **/
  function getMapProxy() {
    return this.mapProxy;
  }
  
  // Internal: Callback when placemarks are found
  function _placemarksReceived(placemarks) {
    this.placemarks = placemarks;
    if (placemarks) {
      this.showPlacemark(0);      
    }
    else {
      this.lat.value = '';
      this.lng.value = '';
      
      this.mapProxy.hidePlacemark();
    }
    if (this.spinner) this.spinner.style.display = 'none';
    
    // Fires addresschooser:suggests:found
    this.mapProxy.trigger(this.element, 'addresschooser:suggests:found', placemarks);
  }
  
  // Internal: Delegates method to map proxy
  function _delegateToMapProxy(method) {
    return function() {
      var args = arguments;
      return this.mapProxy[method].apply(this.mapProxy, arguments)
    }
  }
  
  // Internal: markerDragEnd callback. Called by map proxy when marker has been moved, update hidden lat/lng fields
  function _markerDragEnd(lat, lng) {
    this.lat.value = lat;
    this.lng.value = lng;
  }
    
  /** section: base
   *  Mapeed.AddressChooser.Widget#getCity(placemark) -> String
   *  - placemark (Object): Placemark object depending on mapping system.
   *  
   *  Returns city name of a placemark if exists else returns empty string
   **/
  

  /** section: base
   *  Mapeed.AddressChooser.Widget#getCountry(placemark) -> String
   *  
   *  Returns country name of a placemark if exists else returns empty string
   **/

  /** section: base
   *  Mapeed.AddressChooser.Widget#getZIP(placemark) -> String
   *  
   *  Returns zip code (postal code) of a placemark if exists else returns empty string
   **/
   
   /** section: base
    *  Mapeed.AddressChooser.Widget#getStreet(placemark) -> String
    *  
    *  Returns street name of a placemark if exists else returns empty string
    **/
   
   /** section: base
    *  Mapeed.AddressChooser.Widget#getAddress(placemark) -> String
    *  
    *  Returns full address of a placemark if exists else returns empty string
    **/

   /** section: base
    *  Mapeed.AddressChooser.Widget#getMap() -> Map object depending on mapping system
    *  
    *  Returns map object used by mapping system
    **/
  
   /** section: base
    *  Mapeed.AddressChooser.Widget#setIcon(icon) -> undefined
    *  - icon (Object): icon object depending on mapping system
    *  
    *  Sets marker icon to override default icon (depending on mapping system)
    **/
  
   /** section: base
    *  Mapeed.AddressChooser.Widget#getMap() -> Map object depending on mapping system
    *  
    *  Returns map object used by mapping system
    **/
 
    /** section: base
     *  Mapeed.AddressChooser.Widget#addEventListener() -> Event Listener depending on mapping system.
     *  
     *  Returns a handle that can be used to eventually deregister the handler.
     **/
   function addEventListener(eventName, callback) {
     return this.mapProxy.addEventListener(this.element, 'addresschooser:' + eventName, this, callback)
   }    
  
   /** section: base
     *  Mapeed.AddressChooser.Widget#removeEventListener(handle) -> undefined
     *  - handle (Object): handle returns by addEventListener.
     *  
     *  Removes an handler that has been created by addEventListener.
     **/
  
  // Publish public API
  return {
    initMap:                initMap,
    updateMap:              updateMap,
    showPlacemark:          showPlacemark,
                           
    setIcon:                _delegateToMapProxy('setIcon'),
    
    centerOnClientLocation: _delegateToMapProxy('centerOnClientLocation'),

    // Getters
    getMapProxy:            getMapProxy,
    getCurrentAddress:      getCurrentAddress,
    getMap:                 _delegateToMapProxy('getMap'),
    getCity:                _delegateToMapProxy('getCity'),
    getCountry:             _delegateToMapProxy('getCountry'),
    getZIP:                 _delegateToMapProxy('getZIP'),
    getStreet:              _delegateToMapProxy('getStreet'),
    getAddress:             _delegateToMapProxy('getAddress'),
    
    // Events
    addEventListener:       addEventListener,
    removeEventListener:    _delegateToMapProxy('removeEventListener')
  }
})();