(function($) {
  $.fn.autoGeocoder = function() {
    var DEFAULT_LOCATION = new GLatLng(34, 0);
    var DEFAULT_ZOOM     = 1;
    var GEOCODE_DELAY    = 500; // milliseconds
    var lastZoom         = 15;

    var geocoder = new GClientGeocoder();

    return $(this).each(function(index, element) {
      element = $(element);
      element.after('<div class="auto-geocoder-map" />');

      var mapElement = element.next();
      var map        = new GMap2(mapElement[0]);
      var timer      = null;

      map.setCenter(DEFAULT_LOCATION, DEFAULT_ZOOM);
      map.disableDragging();

      element.keyup(function() {
        var address = $(this).val();

        if (timer) {
          clearTimeout(timer);
        }

        if ($.trim(address) == '') {
          map.clearOverlays();
          map.setCenter(DEFAULT_LOCATION, DEFAULT_ZOOM);

          return;
        } else {
          map.setZoom(lastZoom);
        }

        timer = setTimeout(function() {
          element.trigger('auto-geocode');
        }, GEOCODE_DELAY);
      }).bind('auto-geocode', function() {
        var address = $(this).val();

        geocoder.getLocations(address, function(response) {
          map.clearOverlays();

          if (response) {
            mapElement.find('.not-found, .multiple-addresses').remove();

            if (response.Status.code == 200) {
              // Eliminate redundant placemarks
              var placemarks = []
              for (var i = 0, l = response.Placemark.length; i < l; i++) {
                var placemark = response.Placemark[i];

                if (placemark) {
                  if ((placemark.address == address) && response.Placemark.size() > 1) {
                    continue;
                  }

                  var duplicate = false;
                  placemarks.each(function(existingPlacemark) {
                    if (existingPlacemark.address == placemark.address) { duplicate = true; }
                  });

                  if (!duplicate) {
                    placemarks.push(placemark);
                  }
                }
              }

              if (placemarks.length == 1) {
                var place = placemarks[0];
                var point = new GLatLng(place.Point.coordinates[1],
                                        place.Point.coordinates[0]);
                var marker = new GMarker(point);

                map.panTo(point);
                map.addOverlay(marker);
                map.addControl(new GSmallZoomControl3D());
                var exData = place.ExtendedData;
                var bounds = exData ? exData.LatLonBox : undefined;
                if (bounds) {
                  var llBounds = new GLatLngBounds(
                                new GLatLng(bounds.south, bounds.west),
                                new GLatLng(bounds.north, bounds.east));
                  lastZoom = map.getBoundsZoomLevel(llBounds);
                  map.setZoom(lastZoom);
                }
              } else {
                mapElement
                  .prepend(' \
                    <div class="multiple-addresses"> \
                      <p>Multiple locations found, please select one:</p> \
                      <div> \
                      </div> \
                    </div> \
                  ');

                var ordinal = 1;
                for (var i = 0, l = placemarks.length; i < l; i++) {
                  var itemClass = "";
                  if (i > 4) {
                    itemClass = "overflow";
                  }

                  mapElement
                    .find('.multiple-addresses div')
                    .append('<p class="' + itemClass + '" title="' + placemarks[i].address + '"> ' + ordinal.toString() + '. <span>' +
                      placemarks[i].address +
                    '</span></p>');

                  ordinal++;
                }

                if (placemarks.length > 5) {
                    mapElement
                      .find('.multiple-addresses div')
                      .after('<a class="toggle-more-addresses" href="#">+ More suggestions</a>');

                    mapElement
                      .find('.multiple-addresses div')
                      .after('<a class="toggle-fewer-addresses" style="display: none;" href="#">- Fewer suggestions</a>');

                    mapElement.find('a.toggle-more-addresses').click(function() {
                        mapElement.find('a.toggle-more-addresses').hide();
                        mapElement.find('a.toggle-fewer-addresses').show();
                        mapElement.find('p.overflow').toggle();
                        mapElement.find('p.overflow').css("display", "block");
                        // mapElement.css("overflow-y", "scroll");
                        return false;
                    });

                    mapElement.find('a.toggle-fewer-addresses').click(function() {
                        mapElement.find('a.toggle-more-addresses').show();
                        mapElement.find('a.toggle-fewer-addresses').hide();
                        mapElement.find('p.overflow').css("display", "none");
                        // mapElement.css("overflow-y", "hidden");
                        // mapElement[0].scrollTop = 0;
                        return false;
                    });
                }

                mapElement.find('.multiple-addresses span').click(function() {
                  element.val($(this).text()).trigger('auto-geocode');
                });
              }
            } else if (response.Status.code == 602) {
              mapElement
                .prepend(' \
                  <div class="not-found"> \
                    Unable to identify your location.<br /> \
                    Try being less specific. \
                  </div> \
                ');
            }
          }
        });
      });
    });
  };
})(jQuery);
