/* -----------------------------------------------------------------------------
* g-storelocator.js
* Copyright 2010 wollzelle GmbH (http://wollzelle.com). All rights reserved.
* ------------------------------------------------------------------------------ */

function apiReady() {

/**
 * class GUCCI.StoreLocator < Class
 * 
 * Implements store locator functionality as well as Google Maps API usage
 * 
 **/
 
(function(){
  
  var $ = document.id;
  
  GUCCI.StoreLocator = new Class({
  
    Implements: Options,
    
    BASE_PATH       : '/images/storelocator/',
    MARKER_STORE    : 'map-marker-store-',
    MARKER_LOCATION : 'map-location.gif', 
    STORE_TYPES     : {
                      'boutique'          : 'b',
                      'retailer'          : 'r',
                      'gucci_duty_free'   : 'f',
                      'duty_free_retailer': 'f',
                			'gucci_flagship'    : 'b'
                      },
  
    stores: [],
    countries: [],  
    markers: [],
  	currentLocation: null,
  	storeURL: null,
  	markerSize: 'med',
  
    options: {
      countries                 : [],
      defaultLocation           : { 'lat': 45.4636889, 'lng': 9.1881408, 'country_code': 'IT' }, // Milano as a fallback
      maxMarkersZoomLevel       : 6,
      maxZoomLevel              : 15,
      maxDistance               : 5, // Max distance in km/miles from the location to store to show on map
      lang                      : 'en',
      units                     : '',
      revertPositionOnPopupClose: true
    },
  
    initialize: function(options){
      this.setOptions(options);
      
      var self = this;
      
			// Determine location based on IP of the client. Functionality is provided by Google API.
  		if ($defined(google.loader) && $defined(google.loader.ClientLocation)) {
  		  var clientLoc = google.loader.ClientLocation;
  		  this.options.defaultLocation = { 
          'lat'         : clientLoc.latitude, 
          'lng'         : clientLoc.longitude, 
          'country_code': clientLoc.address.country_code
        };
  	    this.options.units = ['UK', 'US'].indexOf(clientLoc.address.country_code) >= 0 ? 'miles' : 'kms';
			}
			
      this.countries = this.options.countries;
      this.setupMap('map');
      this.geocoder = new google.maps.Geocoder();
  		this.storeURL = $('storelocator_search').get('action'); 
  		this.printLink = $$('.map-print-lnk')[0];
  		
      this.storesRequest = new Request.JSON({
        url: this.storeURL,
        method: 'get',
        secure: false,
        onSuccess: function(responseJSON, responseText){
          this.generateStoresList(responseJSON);
        }.bind(this)
      });
      
      // Init country selector
      this.countriesList = $('country');
      this.countrySelector = new GUCCI.CountrySelector(this.countriesList);
  		
      // Setting event listeners
  		this.printLink.addEvent('click', function(e){
        var queryString = '&zoom=' + this.map.getZoom() + '&center=' + this.map.getCenter().toUrlValue();
        e.target.href = e.target.href + queryString;
      }.bindWithEvent(this));
      
      $('stores_list_container').addEvent('click:relay(a.store-map-lnk)', function(e){
        e.preventDefault();
        var idx = new URI(e.target.href).parsed.fragment - 1;
        var store = this.stores[idx];
        // Create a store marker if it isn't on the map
        if(!$defined(store.marker)) {
          store.marker = this.createMarker(store);
        }
        this.showStoreInfo(store, store.marker);
      }.bindWithEvent(this));
      
      // If the Storelocator is not initialized, prevent user clicks on Submit button (see view)
      function doSubmit(event) {
        event.preventDefault();
        this.searchStoresNearLocation();
      };
      $('storelocator_search').addEvent('submit', doSubmit.bindWithEvent(this));
      $('submit_btn').addEvent('click', doSubmit.bindWithEvent(this));
      
      $$('#storelocator_search .search-filter input').addEvent('change', function(event){
        $$('#storelocator_search .search-filter label').removeClass('active');
        this.getParent('label').addClass('active');
        self.searchStoresNearLocation(self.currentLocation);
      });
        
      google.maps.event.addListener(this.map, 'idle', function() {
        this.refreshMarkers();
      }.bind(this));

      google.maps.event.addListener(this.map, 'zoom_changed', function() {
        if(parseInt(this.map.getZoom()) <= this.options.maxMarkersZoomLevel) {
          this.map.setMapTypeId(google.maps.MapTypeId.TERRAIN);
          if(this.storeInfoWindow) this.storeInfoWindow = this.storeInfoWindow.close();
        } else {
          this.map.setMapTypeId(google.maps.MapTypeId.ROADMAP);
        }
      }.bind(this));
      
      this.countriesList.addEvent('change', function(event){
        var countryCode = this.countriesList[this.countriesList.selectedIndex].get('value');
        var country = this.countries[this.getIdxFromCountryCode(countryCode)];
        this.countrySelector.activateByValue(country.country_code);
        $('address').set('value', country.capital.name).fireEvent('change');
        this.searchStoresNearLocation({
          'lat': country.capital.lat,
          'lng': country.capital.lng,
          'country_code': country.country_code
        });
      }.bindWithEvent(this));
    
      $('map').addEvent('click:relay(.js-close)', function(event){
        event.preventDefault();
        if(this.storeInfoWindow) this.storeInfoWindow = this.storeInfoWindow.close();
        if(this.options.revertPositionOnPopupClose) {
          this.map.setCenter(this.oldMapSettings.center);
          this.map.setZoom(this.oldMapSettings.zoom);
        }
      }.bindWithEvent(this));
      
      // Determining initial user location
      if(navigator.geolocation) {
        // Set the location based on IP address
        this.setInitLocation(this.options.defaultLocation);
        
        // And then try to get it more accurately from Geolocation API
        navigator.geolocation.getCurrentPosition(
          // Success callback
          function(position) {
            this.setInitLocation({
              'lat' : position.coords.latitude, 
              'lng' : position.coords.longitude
            });
          }.bind(this), 
          
          // Failure callback
          function(error) {
            // Get stores for default location determined on server side
            this.setInitLocation(this.options.defaultLocation);
            if(typeof console != 'undefined'){console.error(error.message)};
          }.bind(this)
        );
      // Browser doesn't support Geolocation
      } else {
        // Get stores for default location determined on server side
        this.setInitLocation(this.options.defaultLocation);
      }
    },
    
    /**
     * GUCCI.StoreLocator#setupMap(el)
     * 
     * Initializes Google Maps inside a given element.
     * 
     **/
    setupMap: function(el){
      var el = $(el);
    
      var center = new google.maps.LatLng(this.options.defaultLocation.lat, this.options.defaultLocation.lng);
      var mapOptions = {
        zoom: 12,
  			navigationControl: true,
        navigationControlOptions: {style: google.maps.NavigationControlStyle.SMALL},
  			scaleControl: false,
  			mapTypeControl: false,
        mapTypeId: google.maps.MapTypeId.ROADMAP,
  			scrollwheel: true,
  			disableDoubleClickZoom: false,
  			keyboardShortcuts: false,
  			center: center
  		}		
  		var map = new google.maps.Map(el, mapOptions);
  	  this.map = map;
  	  
  	  this.userLocationMarker = new google.maps.Marker({
        map: this.map,
        icon: new google.maps.MarkerImage(this.BASE_PATH + this.MARKER_LOCATION)
      });
    },
    
    /**
     * GUCCI.StoreLocator#setInitLocation(location)
     * 
     * Performs stores search near the given location and sets form elements with geocoded address.
     * If requested country does not have any stores available, the CountrySelector will be opened.
     * 
     **/
    setInitLocation: function(location){
      this.geocoder.geocode({ 'language': this.options.lang, 'latLng': new google.maps.LatLng(location.lat, location.lng)}, function(results, status){
        
        if (status == google.maps.GeocoderStatus.OK) {
          var result = results[0];
          var country_code = this.getCountryFromComponents(result.address_components).short_name;
          
          // Update default location for futher purposes
          this.options.defaultLocation = {
            'lat': location.lat,
            'lng': location.lng,
            'country_code': country_code
          };

          var idx = this.getIdxFromCountryCode(country_code);

          if(idx < 0) {
            // No stores available for that country. Country selector will be opened.
            this.countrySelector.setValue(this.countrySelector.defaultValue).open();
          } else {
            this.countrySelector.close(); // In case country selector is open
            this.countrySelector.activateByValue(this.countries[idx].country_code);
          }
        
          this.searchStoresNearLocation(this.options.defaultLocation);
        
          $('address').set('value', result.formatted_address).fireEvent('change');
          $('address').focus();
        } else {
          // We didn't get a location from Geocode API nor from server. Country selector will be opened.
          this.countrySelector.open();
        }
      }.bind(this));
    },
    
    /**
     * GUCCI.StoreLocator#searchStoresNearLocation(location)
     * 
     * Performs search around given location. Takes form values as parameters.
     * 
     **/
    searchStoresNearLocation: function(location){
      // $$('#storelocator_search .search-filter label').removeClass('active');
      var storeType = this.getStoreType();
      // If location is not defined, we will geocode it from address
  		if (!$defined(location)) {
        this.getLocationFromAddress($('address').get('value'), 
          function(location){
            this.getStoresForLocation(location, storeType);
          }.bind(this),
        
          function(status){
            // Geocoder retuned error
            $('address').focus();
            switch(status) {
              case 'INVALID_REQUEST': // The request was empty
                // Get stores for default location determined on server side
                this.setInitLocation(this.options.defaultLocation);
                break;
                
              case 'ZERO_RESULTS': // The request returned zero results
                this.storesRequest.cancel().send('empty=true');
                break;
              
              default:
                this.storesRequest.cancel().send('empty=true');
                break;
            }
          }.bind(this)
        );
  		} else {
  			// we already have a valid location
        this.getStoresForLocation(location, storeType);
  		}
    },
    
    getStoresForLocation: function(location, type){
      if(this.storeInfoWindow) this.storeInfoWindow = this.storeInfoWindow.close();
      
      var point = new google.maps.LatLng(location.lat, location.lng);
      this.userLocationMarker.setPosition(point);

      var queryString = 'lat='+location.lat+'&lng='+location.lng+"&country_code="+location.country_code+'&store_type='+type+'&units='+this.options.units;
      this.storesRequest.cancel().send(queryString);
    
      var printURI = new URI(this.printLink.href).set('query', queryString);
      this.printLink.set('href', printURI.toString());

      this.currentLocation = location;
      /*
        TODO Add check if the country has been really changed
      */
      this.countrySelector.activateByValue(location.country_code);
    },
  
    /**
     * GUCCI.StoreLocator#generateStoresList(json)
     * 
     * Updates list with results and set the custom scroller.
     * 
     **/
    generateStoresList: function(json){
      var html = new StringBuffer(),
          nearestStores = [], // We'll collect nearst stores here.
          center = this.userLocationMarker.getPosition();
      
      // Clear active stores and markers;
      this.removeMarkers();
      this.stores = [];
      
      if(json.stores.length > 0) { // Stores found. Render html.
        html.append('<ul class="stores-list">');
        var idx = 0;
        
        // Generate HTML for stores
        json.stores.each(function(store){
          var geoStr      = new StringBuffer(),
              distStr     = new StringBuffer(),
              eventStr    = new StringBuffer(),
              classNames  = new StringBuffer(),
              linkAttrs   = new StringBuffer();
              
          if(store.accurate){
            idx++;
            geoStr.append('<div class="geo"><span class="latitude">').append(store.lat).append('</span>, <span class="longitude">').append(store.lng).append('</span></div>');
            
            distStr.append('<p class="store-distance">').append(store.formattedDistance).append('</p>');
            
            classNames.append(' valid-location');
            
            linkAttrs.append('href="#').append(idx).append('" class="store-map-lnk"');
            
            // Collect stores for futher use
            var d = store.distance;
            if(d <= this.options.maxDistance) nearestStores.push(store); // Collect store if it's closer than maxDistance (in km)
            this.stores.push(store); // Put store in stores array
            
          } else {
            linkAttrs.append('href="').append(json.terms.empty_url).append('" class="store-infopos-lnk js-link" rel="dropdown:open"');
          }
          
          if(store.event) {
            eventStr.append('<a href="').append(store.event_link).append('" title="').append(json.terms.event_link_title).append('" class="js-link" rel="dropdown:open">')
            .append(json.terms.event_link_title).append('</a>');
          }
          
          classNames.append(' store-type-');
          classNames.append(store.type);
          
          html.append('<li class="stores-list-store').append(classNames.toString()).append('">').append('<div class="store-vcard vcard">').append('<h3 class="org fn"><a ')
              .append(linkAttrs.toString()).append('" title="">').append(store.name).append('</a></h3>').append('<p class="addr-block">').append(store.address)
              .append('<span class="tel"><span class="value">').append(store.phone).append('</span></span></p>').append(geoStr.toString()).append('</div>')
              .append('<div class="store-meta">').append('<p class="store-type"><em>').append(store.type).append('</em></p>').append(distStr.toString()).append('</div>').append('</li>');

        }, this);

        html.append('</ul>');
        
        // Set map to display nearest stores
        if(this.stores.length > 0) {
          var zoomBounds = new google.maps.LatLngBounds(center);

          // Always include first store.
          zoomBounds.extend(new google.maps.LatLng(this.stores[0].lat, this.stores[0].lng));

          // But also include stores within given radius (in km)
          nearestStores.each(function(store){
            zoomBounds.extend(new google.maps.LatLng(store.lat, store.lng))
          });

          // Zoom map to nearest stores
          this.map.fitBounds(zoomBounds);

          // If bounds are too small, set maximum zoom level. 
          if(parseInt(this.map.getZoom()) > this.options.maxZoomLevel) this.map.setZoom(this.options.maxZoomLevel);
          
          // Generate or activate markers inside visible area.
          this.refreshMarkers();
        } else {
          // Zoom out to include active country.
          this.map.setZoom(5);
          this.map.setCenter(center);
        }
        
      } else { // No stores found. Proceed with empty message
        if(this.getStoreType().length > 0) {
          // If storeType is defined, we'll try to reset and search for all stores firstly
          var radio = $$('#storelocator_search .search-filter input')[0];
          radio.checked = true;
          radio.fireEvent('change');
          this.getStoresForLocation(this.currentLocation);
        } else {
          // Generate blank message
          html.append('<div class="stores-empty"><p>').append(json.terms.not_found_text).append('</p><p><a href="#country_choices" class="js-link" rel="overlay:open">').append(json.terms.not_found_link).append('</a></p></div>');
          // Zoom out to include active country.
          this.map.setZoom(5);
          this.map.setCenter(center);
        }
      }
      
      // Inject HTML into DOM
      $('stores_list').innerHTML = html.toString();
      // Fire event to update custom scroller
      $('stores_list_container').fireEvent('scroller:refresh');
      // Store this viewport setting for later use.
      this.oldMapSettings = { zoom: this.map.getZoom(), center: this.map.getCenter() };
    },
    
    /**
     * GUCCI.StoreLocator#refreshMarkers()
     * 
     * Performs adding or refreshing of markers for both stores and countries. 
     * 
     **/
    refreshMarkers: function(){
      bounds = this.map.getBounds();
      if(!$defined(bounds)) return;
      
      var zoomLevel = parseInt(this.map.getZoom());
      if(zoomLevel >= 14) 
        this.markerSize = 'big';
      else if(zoomLevel >= 11 && zoomLevel < 14) 
        this.markerSize = 'med';
      else
        this.markerSize = 'small';
      
      this.stores.each(function(store){
        // Show only overlays inside the visible bounds
        if($defined(store.marker)) {
          if( bounds.contains(store.marker.getPosition()) && (this.options.maxMarkersZoomLevel < zoomLevel) ) { 
            // Adjust icon depending on zoom level
            var newIconUrl = this.BASE_PATH + this.MARKER_STORE + this.STORE_TYPES[store.type.replace(/\s/g, '_')] + '-' + this.markerSize + '.png';
            if(store.marker.getIcon() != newIconUrl) store.marker.setIcon(newIconUrl);
            // Inject the marker into DOM
            if(!store.marker.getMap()) store.marker.setMap(this.map);
          } else {
            // Remove marker from DOM
            if(store.marker.getMap()) store.marker.setMap(null);
          }
        } else {
          var location = new google.maps.LatLng(store.lat, store.lng);
          if( bounds.contains(location) && (this.options.maxMarkersZoomLevel < zoomLevel) ) {
            var marker = this.createMarker(store);

            google.maps.event.addListener(marker, 'click', function() {
              this.showStoreInfo(store, marker);
            }.bind(this));

            // Adding marker to array
            this.markers.push(marker);
            store.marker = marker;
          }
        }
      }, this);
      
      this.countries.each(function(country){
        // Show only overlays inside the visible bounds
        if($defined(country.overlay)) {
          if( bounds.contains(country.overlay.getPosition()) && (this.options.maxMarkersZoomLevel >= zoomLevel) ) {
            // Inject overlay into DOM
            country.overlay.add();
          } else {
            // Remove overlay from DOM
            country.overlay.remove();
          }
        } else {
          var location = new google.maps.LatLng(country.location.lat, country.location.lng);
          if( bounds.contains(location) && (this.options.maxMarkersZoomLevel >= zoomLevel) ) {
            var overlayEl = new Element('a', {
              'href': country.country_code,
              'text': country.stores_amount,
              'events': {
                'click': function(e){
                  e.preventDefault();
                  this.countrySelector.activateByValue(country.country_code);
                  $('address').set('value', country.capital.name).fireEvent('change');
                  location = {
                    'lat': country.capital.lat,
                    'lng': country.capital.lng,
                    'country_code': country.country_code
                  };
                  this.searchStoresNearLocation(location);
                }.bindWithEvent(this)
              }
            });
            country.overlay = new CustomOverlay(location, this.map, overlayEl, 'map-stores-amount-overlay');
          }
        }
      }, this);
    },
    
    /**
     * GUCCI.StoreLocator#getLocationFromAddress(address, success, failure)
     * 
     * Performs a geocoder request. 
     * If it's succesfull, the success callback will be called. 
     * If geocoder get an error, failure callback will be called.
     * 
     **/
    getLocationFromAddress: function(address, success, failure){
      this.geocoder.geocode({ 'address': address }, function(results, status){
        if (status == google.maps.GeocoderStatus.OK) {
          var result = results[0];
          var country = this.getCountryFromComponents(result.address_components);
          var location = { 
            'lat'           : result.geometry.location.lat(), 
            'lng'           : result.geometry.location.lng(),
            'country_code'  : country.short_name
          };
          if($defined(success) && $type(success) == 'function') success(location);
        } else {
          if(typeof console != 'undefined'){console.error("Geocode was not successful for the following reason: " + status)};
          if($defined(failure) && $type(failure) == 'function') failure(status);
        }
      }.bind(this))

    },
    
    /**
     * GUCCI.StoreLocator#getStoreType() -> String
     * 
     * Returns a string ov value representing a selected store type input
     * 
     **/
    getStoreType: function(){
      return $$('#storelocator_search .search-filter input[checked]')[0].get('value');
    },
    
    /**
     * GUCCI.StoreLocator#getCountryFromComponents(components) -> Hash
     * 
     * Returns a hash with a country information.
     * 
     **/
    getCountryFromComponents: function(components){
      return components.map(function(component){
        return component.types[0] == 'country' ? component : null;
      }).clean()[0];
    },
    
    /**
     * GUCCI.StoreLocator#getIdxFromCountryCode(code) -> Number
     * 
     * Returns a position (index) in the countries Array for the given country code. 
     * 
     **/
    getIdxFromCountryCode: function(code){
      return this.countries.map(function(country){
        return country.country_code;
      }).indexOf(code);
    },

    /**
     * GUCCI.StoreLocator#createMarker(store) -> google.maps.Marker
     * 
     * Creates a store marker for a given store object. Attaches to the Google maps.
     * 
     **/
    createMarker: function(store){
      return new google.maps.Marker({
        position: new google.maps.LatLng(store.lat, store.lng), 
        map: this.map, 
        title: store.name.clean(),
        icon: new google.maps.MarkerImage(this.BASE_PATH + this.MARKER_STORE + this.STORE_TYPES[store.type.replace(/\s/g, '_')] + '-' + this.markerSize + '.png')
      });
    },    
    
    /**
     * GUCCI.StoreLocator#removeMarkers()
     * 
     * Removes all markers from DOM. Also removes all associated with them event listeners.
     * 
     **/
    removeMarkers: function(){
      var l = this.markers.length;
      for(var i = 0; i < l; i++) {
        var mrk = this.markers.pop();
        google.maps.event.clearInstanceListeners(mrk);
        mrk.setMap(null);
        mrk = null;
      }
    },
    
    /**
     * GUCCI.StoreLocator#showStoreInfo(store, marker)
     * 
     * Displays a custom info overlay window on the map with store information for a given store and associated with a given marker.
     * 
     **/
    showStoreInfo: function(store, marker){
      if($defined(this.storeInfoWindow) && $defined(store.infoWindow) && store.infoWindow == this.storeInfoWindow) return; // Overlay already exist
    
      if(store.infoWindow != this.storeInfoWindow) 
        if(this.storeInfoWindow) this.storeInfoWindow = this.storeInfoWindow.close();
    
      this.map.setCenter(marker.getPosition());
      this.map.setZoom(15);
      
      var html = new StringBuffer();
      html.append('<div class="b-arr"><b class="a1"></b><b class="a2"></b><b class="a3"></b><b class="a4"></b><b class="a5"></b><b class="a6"></b></div> <div class="store-info-window-wrap"><h3 class="store-name">');
      html.append(store.name);
      html.append('</h3><p class="store-addr">');
      html.append(store.address);
      html.append('<span class="tel"><span class="value">');
      html.append(store.phone);
      html.append('</span></span></p>');
      if($defined(store.additional) && store.additional.length > 0) {
        html.append('<p class="store_additional">');
        html.append(store.additional);
        html.append('</p>');
      }
      html.append('<a href="#" class="js-close">Close</a></div></div>');
      
      this.storeInfoWindow = store.infoWindow = new CustomOverlay(marker.getPosition(), this.map, html.toString(), 'store-info-window');
    }
  });
  
})();

/**
 * class CustomOverlay < google.maps
 * 
 * Implements custom overlays for Google Maps to display country badges with store amount on it. 
 * 
 **/
var CustomOverlay = new Class({
  
  Extends: google.maps.OverlayView,
  
  initialize: function(location, map, content, className){
    this.location_ = location;
    this.content_ = content;
    this.map_ = map;
    this.className_ = className;

    this.div_ = null;

    // Explicitly call setMap() on this overlay
    this.setMap(map);
  },
  
  onAdd: function(){
    var div = new Element('div', {
      'class': this.className_
    });

    this.div_ = div;

    var panes = this.getPanes();
    panes.floatPane.appendChild(div);
  },
  
  draw: function(){
    var overlayProjection = this.getProjection();
    var location = overlayProjection.fromLatLngToDivPixel(this.location_);

    var div = this.div_;
    div.style.left = location.x + 'px';
    div.style.top = location.y + 'px';
    
    if(typeof this.content_ === 'string')
      div.innerHTML = this.content_;
    else
      this.content_.inject(div); 
  },
  
  onRemove: function(){
    this.div_.parentNode.removeChild(this.div_);
    this.div_ = null;
  },
  
  hide: function(){
    if (this.div_) {
      this.div_.style.visibility = "hidden";
    }
  },
  
  show: function(){
    if (this.div_) {
      this.div_.style.visibility = "visible";
    }
  },
  
  toggle: function(){
    if (this.div_) {
      if (this.div_.style.visibility == "hidden") {
        this.show();
      } else {
        this.hide();
      }
    }
  },
  
  add: function(){
    if(!this.getMap()) this.setMap(this.map_);
  },
  
  remove: function(){
    if(this.getMap()) this.setMap(null);
  },
  
  toggleDOM: function(){
    if (this.getMap()) {
      this.setMap(null);
    } else {
      this.setMap(this.map_);
    }
  },
  
  close: function(){
    if (this.getMap())
      this.setMap(null);
    return null;
  },
  
  getPosition: function(){
    return this.location_;
  }
});

// Init store locator
window.storeLocator = new GUCCI.StoreLocator(storelocator_options);
};
