//
// EinsteinDirectory core javascript
//
// Author:  Brian Smith
// Date:  August 2007
//

EI.namespace("Directory");

EI.Directory = function(mapElement, options) {

  var _options = options || {}

  var map = new EI.Directory.Map(mapElement, _options.centerLatLng, _options.radius);
  var self = this;
  
  // Loads the map and runs any necessary initialization. Should be set as a window (or document) load event.
  //
  this.load = function() {
    self.beforeLoad();
    
    if(map) {
      map.load();
    }
    
    self.afterLoad();
  };
  
  // Unloads the map and runs any necessary teardown code. Should be set as a window (or document) unload event.
  //
  this.unload = function() {
    self.beforeUnload();
    
    if(map) {
      map.unload();
    }
    
    self.beforeUnload();
  };
   
  // Add an entity
  //
  // options: 
  //  newEntity - JSON object of an entity
  //  newEntityHtml - HTML representation of entity
  //  newEntityLinkClass - HTML class for links to this entity
  //
  this.addEntity = function(newEntity, options) {
    var options = options || {}
    
    self.beforeAddEntity(newEntity, options);
        
    if(map) {
      map.addEntity([newEntity.location.latitude, newEntity.location.longitude], options);
    }
    
    self.afterAddEntity(newEntity, options);
  };
  
  // Remove all entities
  //
  //
  this.clearEntities = function() {
    self.beforeClearEntities();
    
    if(map) {
      map.clearEntities();
    }
    
    self.afterClearEntities();
  };
  
  // Set the center and radius of the search.
  //
  // newLatLng needs to be a two-element array with the 
  // latitude and longitude of strings.
  //
  // newRadius needs to be an integer (for radius of search in miles)
  //
  this.setCenter = function(newLatLng, newRadius) {
    self.beforeSetCenter(newLatLng, newRadius);
    
    if(map) {
      map.moveTo(newLatLng, newRadius);
    }
    
    self.afterSetCenter(newLatLng, newRadius);
  };
  
}
// Before and after callbacks
//
// You can overwrite any of these callbacks to allow for custom
// functionality.
//
// Example:
//
//  EI.Directory.prototype.afterLoad = function() { alert("Loaded!"); };
//
EI.Directory.prototype.afterUpdate = function() {};
EI.Directory.prototype.beforeUpdate = function() {};
EI.Directory.prototype.afterAddEntity = function(newEntity, newEntityHtml, newEntityLinkClass) {};
EI.Directory.prototype.beforeAddEntity = function(newEntity, newEntityHtml, newEntityLinkClass) {};
EI.Directory.prototype.afterLoad = function() {};
EI.Directory.prototype.beforeLoad = function() {};
EI.Directory.prototype.afterUnload = function() {};
EI.Directory.prototype.beforeUnload = function() {};
EI.Directory.prototype.afterClearEntities = function() {};
EI.Directory.prototype.beforeClearEntities = function() {};
EI.Directory.prototype.afterSetCenter = function(newLatLng, newRadius) {};
EI.Directory.prototype.beforeSetCenter = function(newLatLng, newRadius) {};


// The Map constructor creates a new map.
//
// Requires: Prototype 1.5+
//
// Parameters:
//
//  mapElement:     The element (or string id of an element) to use for displaying the map,
//                  most likely a div element.
//  centerLatLng:   GLatLng object
//  radius:         The minimum radius (in miles) that the map should cover.
// 
EI.Directory.Map = function (mapElement, centerLatLng, radius) {
  
  this.defaultCenter = new GLatLng(39.9, -98.5);
  this.defaultRadius = 1400;
  
  var _mapElement = $(mapElement);
  var _centerLatLng = centerLatLng || this.defaultCenter;
  var _radius = radius || this.defaultRadius;
  
  var _map = null;
  var _entityMarkers = new Array;
  
  var self = this;

  function centerLat() {
    return parseFloat(_centerLatLng.lat());
  }
  
  function centerLng() {
    return parseFloat(_centerLatLng.lng());
  }

  function radiusToZoom() {
    degLatInMiles =  69.170323428;
    degLngInMiles = degLatInMiles * Math.cos(centerLat() * 0.0174532925199433);
        
    diffDegLat = _radius / degLatInMiles;
    diffDegLng = _radius / degLngInMiles;
        
    swBound = new GLatLng(centerLat() - diffDegLat, centerLng() - diffDegLng);
    neBound = new GLatLng(centerLat() + diffDegLat, centerLng() + diffDegLng);

    return _map.getBoundsZoomLevel(new GLatLngBounds(swBound, neBound));
  }
  
  function loadEntityMarkers() {
    for( var i = 0; i < _entityMarkers.length; i++) {
      _map.addOverlay( _entityMarkers[i] );
    }
  }
  
  function addLinkInfoEvents(entityIndex, linkClass) {
    linkElements = $$('a.' + linkClass);
    for(var i = 0; i < linkElements.length; i++) {
      
    }
  }
  
  
  this.load = function() {
    if (GBrowserIsCompatible()) {
      _map = new GMap2(_mapElement);
      _map.setUI({
        controls: { largemapcontrol3d: true, maptypecontrol: true, scalecontrol: true },
        maptypes: { normal: true },
        zoom:     { scrollwheel: true, doubleclick: true},
        keyboard: true
      });
      _map.setCenter(new GLatLng(centerLat(), centerLng()), radiusToZoom());

      loadEntityMarkers();
      return true;      
    }
    return false;
  };
  
  this.unload = function() {
    if (GBrowserIsCompatible()) {
      GUnload();
      return true;
    }
    return false;
  };
  
  this.addEntity = function(markerLatLng, options) {

    var options = options || {};
    var markerOptions = {};

    var icon = new GIcon(options.icon || G_DEFAULT_ICON);
    markerOptions.icon = icon;

    var newPoint = new GLatLng(markerLatLng[0], markerLatLng[1]);
    var newMarker = new GMarker(newPoint, markerOptions);

    // Attach behavior to update custom infoWindow element with entityHTML
    if (options.customInfoWindowElement) {

      GEvent.addListener(newMarker, 'click', function() {
        $(options.customInfoWindowElement).update(options.entityHTML);

        if (typeof Effect != 'undefined') {
          new Effect.Highlight(options.customInfoWindowElement, { 
            keepBackgroundImage: true, 
            startcolor: '#cfe8ff',
            endcolor: '#ffffff',
            restorecolor: true
          });
        }
      });

    // Otherwise, default behavior
    } else {
      newMarker.bindInfoWindowHtml(options.entityHTML);
    }

    GEvent.addListener(newMarker, 'mouseover', function() {
      newMarker.setImage(icon.imageOver);
    });

    GEvent.addListener(newMarker, 'mouseout', function() {
      newMarker.setImage(icon.image)
    });

    // Attach click event trigger to classed links
    entityLinks = $$('a.' + options.entityLinkClass);
    for(var i = 0; i < entityLinks.length; i++) {
      Element.observe(entityLinks[i], "click", function() { GEvent.trigger(newMarker, "click"); });
    }

    _entityMarkers.push(newMarker);

    if( _map ) {
      _map.addOverlay(newMarker);
    }
    
    return true;
  };
  
  this.clearEntities = function () {
    var currentMarker;
    
    while(currentMarker = _entityMarkers.pop()) {
      _map.removeOverlay(currentMarker);
    }
    return true;
  };
  
  this.moveTo = function(newLatLng, newRadius) {
    _radius = newRadius;
    _centerLatLng = newLatLng;
    
    _map.setZoom(radiusToZoom());
    _map.panTo( new GLatLng(centerLat(), centerLng()));
    
    return true;
  };

};

// GoogleGeocoder core JavaScript
// 
// Author:  Andrew Waer
// Date:    February 2008
//
// The GoogleGeocoder constructor returns an object for Google geocoding service
// 
// Requires: EI.Geolocation,  Google Maps API
//

EI.namespace('GoogleGeocoder');

EI.GoogleGeocoder = function() {

  var self            = this;
  var _geocoder       = new GClientGeocoder();

  // Query Google geocoder with address string. Delegate geolocation response to onComplete.
  // 
  this.findAddress = function(address, onComplete) {
    _geocoder.getLocations(address, function(response) {
      var loc = parseResponse(response);
      if (typeof(onComplete) == 'function') 
        onComplete(loc);
    });
  };

  // Response parsed as xAL (eXtensible Address Language). 
  // TODO: (refactor) this is a bit of a mess, due to the unpredictable node hierarchy
  // 
  function parseResponse(response) {

    if (!response || response.Status.code!=200) return false;

    var place       = response.Placemark[0];
    var loc         = {};
    var area        = null;
    var locality    = null;

    if (place) {

      loc.latitude    = place.Point.coordinates[1];
      loc.longitude   = place.Point.coordinates[0];
      loc.accuracy    = accuracyToRadius(place.AddressDetails.Accuracy);

      if (place.AddressDetails.Country) {

        loc.countryCode = place.AddressDetails.Country.CountryNameCode;

        if (place.AddressDetails.Country.AdministrativeArea) {

          // For some reason, Google adds '.' to state names, for example C.A. for California
          if (place.AddressDetails.Country.AdministrativeArea.AdministrativeAreaName)
            loc.state = place.AddressDetails.Country.AdministrativeArea.AdministrativeAreaName.replace(/\./g, '');

          // Assign the proprerty node from which we will pull Locality information
          area = place.AddressDetails.Country.AdministrativeArea.SubAdministrativeArea ||
                 place.AddressDetails.Country.AdministrativeArea;

          if (area) {
            if (area.Locality) {
              // Ignore DependentLocalityName (borrough/community) and just use name of city
              loc.city = area.Locality.DependentLocality ?
                area.SubAdministrativeAreaName :
                area.Locality.LocalityName;

              if (area.Locality.PostalCode)
                loc.postalCode = area.Locality.PostalCode.PostalCodeNumber;

              if (area.Locality.Thoroughfare)
                loc.streetAddress = area.Locality.Thoroughfare.ThoroughfareName;
            }
          }
        }
      }
    }
    return new EI.Geolocation(loc);
  }

  // Translates Google geocoding accuracy value to radius
  // 
  function accuracyToRadius(accuracy) {
    var accuracy = parseFloat(accuracy);
    var defaultRadius = 1500;
    var radius = {
      0: defaultRadius,  // Unknown
      1: 400,            // Country
      2: 200,            // Region (state/province)
      3: 10,             // Sub-region (county/municipality) <-- buggy, 3 and 4 are sometimes reversed
      4: 10,             // Town (city/village)
      5: 5,              // Post code
      6: 2,              // Street
      7: 1,              // Intersection
      8: 1               // Address
    };
    return radius[accuracy] || defaultRadius;
  }

};

//
// GeoListingsMap core javascript
//
// Author:  Andrew Waer
// Date:    February 2008
//

EI.namespace('GeoListingsMap');

// The GeoListingsMap constructor initializes map with geo-targeted listings
// 
// Requires: EI.Listings
// 
// Parameters:
// 
//  mapElement:     The element (or string id of an element) to use for displaying the map,
//                  most likely a div element.
//  options
//    address:                  Client address for geocoding
//    customInfoWindowElement:  (optional) Element (or string id of element) for updating listing infoWindow HTML
//    loadingIndicatorElement:  (optional) Element (or string id of element) assigned 'loading' class during geocoding
//
EI.GeoListingsMap = function(mapElement, options) {

  var self = this;

  var _options             = options || {};
  var _defaultSearchRadius = 100;
  var _defaultZoom         = 10;
  var _defaultLatLng       = new GLatLng(39.9, -98.5);
  var _geolocation         = EI.Listings.getLocation();
  var _geocoder            = new EI.GoogleGeocoder();
  var _mapElement          = mapElement;
  var _systemMessageDiv    = document.createElement('div');
  var _callback            = _options.callback;

  var _icons               = [
    // Priority
    {
      image:             _options.imagePath + '/geo_listings_marker_priority.png',
      imageOver:         _options.imagePath + '/geo_listings_marker_priority_over.png',
      shadow:            _options.imagePath + '/geo_listings_marker_priority_shadow.png',
      iconSize:          new GSize(22, 39),  // FIXME: tightly-coupled to GSize & GPoint. Push off to EI.Directory.Map?
      shadowSize:        new GSize(42, 39),
      iconAnchor:        new GPoint(11, 39),
      infoWindowAnchor:  new GPoint(10, 2)
    },
    // Featured
    {
      image:             _options.imagePath + '/geo_listings_marker_featured.png',
      imageOver:         _options.imagePath + '/geo_listings_marker_featured_over.png',
      shadow:            _options.imagePath + '/geo_listings_marker_featured_shadow.png',
      iconSize:          new GSize(20, 34),
      shadowSize:        new GSize(38, 34),
      iconAnchor:        new GPoint(9, 34),
      infoWindowAnchor:  new GPoint(9, 2)
    },
    // Profiled (Basic)
    {
      image:             _options.imagePath + '/geo_listings_marker_featured.png',
      imageOver:         _options.imagePath + '/geo_listings_marker_featured_over.png',
      shadow:            _options.imagePath + '/geo_listings_marker_featured_shadow.png',
      iconSize:          new GSize(20, 34),
      shadowSize:        new GSize(38, 34),
      iconAnchor:        new GPoint(9, 34),
      infoWindowAnchor:  new GPoint(9, 2)
    }
  ];

  this.directory     = null;

  // Geocodes free-text address and updates geolocation.
  // On completion, updates listings.
  // 
  this.updateLocation = function(address) {

    if (!/^\s*$/.test(address)) {

      $(options.loadingIndicatorElement).addClassName('loading');

      _geocoder.findAddress(address, function(geoLoc) { 

        if (geoLoc && geoLoc.isSuccess()) {

          // Update geolocation & search for new listings set
          self.setGeolocation(geoLoc);
          EI.Listings.findListings(_callback);

          // Update map with new geolocation and listings if accuracy is better than state-level
          if (geoLoc.accuracy < geoLoc.maxAccuracy) {
            self.directory.setCenter(new GLatLng(geoLoc.latitude, geoLoc.longitude), geoLoc.accuracy);
            self.directory.clearEntities();

          // Otherwise, display notice
          } else {
            displaySystemMessage('Please narrow your search');
          }
        } else {
          displaySystemMessage('Unable to find location');
        }

        $(options.loadingIndicatorElement).removeClassName('loading');
      });
    }
  };

  // Add entity to directory map.  Pass along matching icon object & options.
  // 
  this.displayListing = function(listing, options) {
    options.icon = _icons[(listing.listingType-1)];
    if (_options.customInfoWindowElement)
      options.customInfoWindowElement = _options.customInfoWindowElement;
    self.directory.addEntity(listing, options);
  };

  // Sets _geolocation property
  // 
  this.setGeolocation = function(geolocation) {
    _geolocation.updateLocation(geolocation);
  };

  // Initialize map, either at geocoded location or default (U.S.)
  // 
  function setupDirectoryMap() {

    var radius = _geolocation && _geolocation.isSuccess() ? _geolocation.accuracy : 1500;
    var latLng = _geolocation && _geolocation.isSuccess() ? new GLatLng(_geolocation.latitude, _geolocation.longitude) : _defaultLatLng;

    self.directory = new EI.Directory(mapElement, { centerLatLng: latLng, radius: radius });

    if (self.directory) {
      Event.observe(window, 'unload', self.directory.unload);
      self.directory.load();
    }
    return false;
  }

  // Updates and displays system message overlay element
  // 
  function displaySystemMessage(message) {
    var systemMessageDiv = _systemMessageDiv || document.createElement('div');
    systemMessageDiv.setAttribute('id', 'geo_listings_message');

    $(systemMessageDiv).update('<p>' + message +'</p>');

    $(systemMessageDiv).setStyle({
      textAlign:          'center',
      position:           'absolute',
      width:              '100%',
      top:                '40%',
      zIndex:             99999
    });

    $(systemMessageDiv).down('p').setStyle({
      display:            'block',
      width:              '165px',
      border:             '3px solid #cc0000',
      padding:            '7px',
      margin:             '0 auto',
      textAlign:          'center',
      backgroundColor:    '#ffffff'
    });

    // Append system message and set timeout to remove
    $(mapElement).appendChild(systemMessageDiv);
    setTimeout(function(){ $(mapElement).removeChild($(systemMessageDiv)); }, 3000);
  }

  // Initialize map; constructor should be called on dom ready
  // Use cookie geolocation if exists, otherwise IP detection or use address if provided
  // 
  var initialize = function() {

    if (_options.address) {
      _geocoder.findAddress(_options.address, function(geoLoc){
        self.setGeolocation(geoLoc);
        setupDirectoryMap();
      });
    } else {
      setupDirectoryMap();
    }
  }();

};