/* Tribalogic extensions to the jmaps system. */

(function($){
  $.jmap.markers = [];
  $.jmap.tooltip = null;

  $.jmap.initCallback = function(el, options)
  {
    var map = $(el);
    map.jmap("displayMarkers");

    if (options.showSearch)
    {
      var display = function(){map.jmap("showSearch"); return false;};
      if (options.showSearchAuto)
      {
        display();
      }
      else
      {
        var search = $('<a href="#">Try our new search interface</a>').click(display);
        map.before(search);
        map.parent().height(map.parent().height()+30);
      }
    }


    /*$(el).jmap("addScreenOverlay",
      {
        imageUrl: "/frontend/default/images/icons/find.png",
        screenXY:[-64,0],
        overlayXY:[0,0],
        size:[64,64]
      },
      function(myoverlay)
      {
        console.log(myoverlay);
        GEvent.addListener($.jmap.GMap2, "click",
          function(overlay, latlng)
          {
            var proj = $.jmap.GMap2.getCurrentMapType().getProjection();
            var point = proj.fromLatLngToPixel(latlng, $.jmap.GMap2.getZoom());
            console.log("Clicked");
            console.log(overlay);
            console.log(point);
            console.log(myoverlay);
          });
      });*/
  };

  $.extend(
    $.jmap.JMarkerDefaults,
    {
      pointToolTip: "",
      pointLink: "",
      pointInfoWindowHtml: "",
      toolTipHoverDomId: "",
      pointIconName: "",
      pointIconSize: [12, 20],
      pointIconShadowName: "http://labs.google.com/ridefinder/images/mm_20_shadow.png",
      pointIconShadowSize: [22, 20],
      pointIconAnchor: [6, 20],
      pointIconInfoWindowAnchor: [5, 1]
    }
  );

  $.jmap.pointerIcons = {};

  $.jmap.createMarker = function(options, callback)
  {
    try
    {
      var options = $.extend({}, $.jmap.JMarkerDefaults, options);
      var markerOptions = {};

      if (typeof options.pointIcon == 'object' && options.pointIcon)
      {
        $.extend(markerOptions, {icon: options.pointIcon});
      }
      else if (options.pointIconName)
      {
        // This cache is a little rubbish seeing as it only keys on the name...
        if (!$.jmap.pointerIcons[options.pointIconName])
        {
          var icon = new GIcon();
          icon.image = options.pointIconName;
          icon.iconSize = new GSize(options.pointIconSize[0], options.pointIconSize[1]);
          if (options.pointIconShadowName)
          {
            icon.shadow = options.pointIconShadowName;
            icon.shadowSize = new GSize(options.pointIconShadowSize[0], options.pointIconShadowSize[1]);
          }
          icon.iconAnchor = new GPoint(options.pointIconAnchor[0], options.pointIconAnchor[1]);
          icon.infoWindowAnchor = new GPoint(options.pointIconInfoWindowAnchor[0], options.pointIconInfoWindowAnchor[1]);
          $.jmap.pointerIcons[options.pointIconName] = icon;
        }
        $.extend(markerOptions, {icon: $.jmap.pointerIcons[options.pointIconName]});
      }

      if (options.pointIsDraggable)
        $.extend(markerOptions, {draggable: options.pointIsDraggable});

      /* Create the marker */
      var marker = new GMarker(new GLatLng(options.pointLatLng[0], options.pointLatLng[1]), markerOptions);

      // Add our own tooltip code.
      if (options.pointToolTip)
      {
        if (!$.jmap.tooltip)
        {
          $.jmap.tooltip = $('<div/>')[0];
          $.jmap.GMap2.getPane(G_MAP_FLOAT_PANE).appendChild($.jmap.tooltip);
        }
        var map = $.jmap.GMap2;
        var tooltip = '<div class="tooltip">' + options.pointToolTip + '</div>';
        GEvent.addListener(marker, "mouseover",
          function()
          {
            $.jmap.tooltip.innerHTML = tooltip;
            var point = map.getCurrentMapType().getProjection().fromLatLngToPixel(map.fromDivPixelToLatLng(new GPoint(0,0),true),map.getZoom());
            var offset = map.getCurrentMapType().getProjection().fromLatLngToPixel(marker.getPoint(),map.getZoom());
            var anchor = marker.getIcon().iconAnchor;
            var width = marker.getIcon().iconSize.width;
            var height = $.jmap.tooltip.clientHeight;
            var pos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(offset.x - point.x - anchor.x + width, offset.y - point.y -anchor.y -height));
            pos.apply($.jmap.tooltip);
            $.jmap.tooltip.style.visibility="visible";
            //marker.openInfoWindowHtml(tooltip, {maxContent: options.pointMaxContent, maxTitle: options.pointMaxTitle});
          }
        );
        GEvent.addListener(marker, "mouseout",
          function()
          {
            $.jmap.tooltip.style.visibility="hidden";
          }
        );
      }

      // Do we link through?
      if (options.pointLink)
      {
        GEvent.addListener(marker, "click",
          function()
          {
            // This implements caching of the movement from the original starting
            // position so we can restore the map.
            startpos = $.cookie("tlSavedStartPos");
            if (startpos)
            {
              cntr = $.jmap.GMap2.getCenter();
              var newval = (cntr.lat() + "," + cntr.lng());
              if (startpos != newval)
              {
                $.cookie("tlSavedExitPos", newval);
              }
            }
            window.location.href=options.pointLink;
          }
        );
      }

      if (options.pointInfoWindowHtml)
        marker.jMapInfoWindow = options.pointInfoWindowHtml;

      if (options.toolTipHoverDomId)
        marker.jMapToolTipHoverDomId = options.toolTipHoverDomId;

      if (callback)
      {
        callback(marker);
      }
    } catch(er) { }
  };

  $.jmap.registerMarker = function(options, callback)
  {
      $.jmap.createMarker(
        options,
        function(marker)
        {
          // Add the marker to the array.
          $.jmap.markers.push(marker);
          if (callback)
          {
            callback(marker);
          }
        }
      );

  };


  $.jmap.getMarker = function(options, callback)
  {
    try
    {
      var options = $.extend({markerId: 0 }, options);
      return $.jmap.markers[options.markerId];
    }
    catch(er) { }
  };


  $.jmap.displayMarkers = function()
  {
    try
    {
      if (1 == $.jmap.markers.length)
      {
        var marker = $.jmap.markers[0];
        $.jmap.GMap2.addOverlay(marker);
        $.jmap.GMap2.setCenter(
          marker.getLatLng(),
          $.jmap.JOptions.mapZoom
        );
        if (marker.jMapInfoWindow) {
          GEvent.addListener(marker, "click",
            function()
            {
              marker.openInfoWindowHtml(marker.jMapInfoWindow);
            }
          );
          // Triggering the event immediately doesn't seem to work
          // and the window is oftent obscured by the map type icons.
          window.setTimeout(
            function()
            {
              GEvent.trigger(marker, "click");
            },
            250
          );
        }
      }
      else if ($.jmap.markers.length > 1)
      {
        // Calculate the bounding box
        var bounds = new GLatLngBounds();

        for (var i=0; i < $.jmap.markers.length; ++i)
        {
          marker = $.jmap.markers[i];
          bounds.extend(marker.getLatLng());
          $.jmap.GMap2.addOverlay(marker);

          if (marker.jMapToolTipHoverDomId)
          {
            /* Need a fresh namespace for the event listeners */
            (
              function()
              {
                var mkr = marker;
                var el = $("#" + mkr.jMapToolTipHoverDomId)[0];
                GEvent.addDomListener(el, "mouseover", function(){GEvent.trigger(mkr, "mouseover");});
                GEvent.addDomListener(el, "mouseout",  function(){GEvent.trigger(mkr, "mouseout");});
              }
            )();
          }
        }

        // Set Center and Zoom
        if ($.jmap.JOptions.mapNoAutoZoom)
        {
          // We need to extend the bounds such that we can recentre safely,
          // knowing that all markers will be kept in the visible area.
          var mb = $.jmap.GMap2.getBounds();
          var bsw = bounds.getSouthWest();
          var bne = bounds.getNorthEast();
          var msw = mb.getSouthWest();
          var mne = mb.getNorthEast();

          // NB this calculation will probably fail if the -180->180 longitude line is crossed.
          var dlng1 = msw.lng() - bsw.lng();
          var dlng2 = bne.lng() - mne.lng();
          if (dlng1 < 0) dlng1 = 0;
          if (dlng2 < 0) dlng2 = 0;
          var dlng = (dlng1 > dlng2 ? dlng1 : dlng2);

          var dlat1 = msw.lat() - bsw.lat();
          var dlat2 = bne.lat() - mne.lat();
          if (dlat1 < 0) dlat1 = 0;
          if (dlat2 < 0) dlat2 = 0;
          var dlat = (dlat1 > dlat2 ? dlat1 : dlat2);

          if (0 != dlng || 0 != dlat)
          {
            bounds.extend(new GLatLng(mne.lat() + dlat, mne.lng() + dlng));
            bounds.extend(new GLatLng(msw.lat() - dlat, msw.lng() - dlng));
          }
        }
        $.jmap.GMap2.setCenter(
          bounds.getCenter(),
          $.jmap.GMap2.getBoundsZoomLevel(bounds)
        );
      }
    }
    catch(er) { }
  };

  $.jmap.setCenter = function(newCenter, callback)
  {
    $.jmap.GMap2.setCenter(newCenter);
    $.jmap.variables.mapCenter = newCenter;
    if (typeof callback == 'function') return callback(center);
  };



  /* Some extra options for the search interface */
  /* TODO: Separate this out from the main options. */
  $.extend(
    $.jmap.JDefaults,
    {
      mapNoAutoZoom: false,
      showSearch: false,
      showSearchAuto: false,
      searchAreaPadding: 7,
      searchAreaBorder: 1,
      searchAreaBackground: "#fff",
      searchAreaBorderStyle: "solid #ccc",
      searchMapPercentage: 65,
      searchAjaxUri: "",
      searchMarkerAjaxArgs: {},
      searchRadarUri: "",
      searchResultsUri: "",
      searchUnit: "km",
      searchRadii: [ 10, 20, 50, 100, 150, 200 ],
      searchInitialRadius: 20,
      searchInitialLocation: "",
      searchRingColor: "0000c0",
      searchBusyIcon: "",
      searchCloseIcon: "",
      searchUseTagCloud: false,
      searchRestrictCountry: true
    }
  );

  $.jmap.showSearch = function(options, callback)
  {
    try
    {
      var options = $.extend({}, $.jmap.JOptions, options);
      var map = $($.jmap.GMap2.getContainer());
      if (!options.showSearchAuto)
      {
        map.jqm({modal: true});
        map.jqmShow();
      }
      var map_orig_width = map.width();

      var search = $('<div class="tlMapSearch" />');
      var offset = map.offset();

      var margin = (map.width() * options.searchMapPercentage / 100);
      var padding = options.searchAreaPadding;
      var border = options.searchAreaBorder;
      search.css("overflow", "auto");
      search.css("position", "absolute");
      search.css("margin-left", margin);
      search.css("padding", padding);
      search.css("border", border + "px " + options.searchAreaBorderStyle);
      search.css("background", options.searchAreaBackground);
      search.width(map_orig_width - margin - ((padding+border) * 2));
      search.height(map.height() - ((padding+border) * 2));
      search.css("left", 0);
      search.css("top", 0);
      map.prepend(search);


      /* Close Icon */
      var close = false;
      if (!options.showSearchAuto && options.searchCloseIcon)
      {
        close = $('<img alt="close" />');
        close.attr("src", options.searchCloseIcon);
        close.css("float", "right");
        search.append(close);
      }

      /* Create a small area for tips/advice */
      var show_tip =
        function(tipText) { /* noop */ };
      var hide_tips =
        function() { /* noop */ };
      if (!options.showSearchAuto)
      {
        var tips = $('<div class="tlMapSearchTips" />');
        tips.css("display", "none");
        tips.css("position", "absolute");
        tips.css("padding", padding);
        tips.css("border", border + "px " + options.searchAreaBorderStyle);
        tips.css("background", options.searchAreaBackground);
        tips.css("opacity", "0.85");
        tips.css("left", 0);
        tips.css("top", map.height());
        tips.width(map_orig_width - ((padding+border) * 2));
        map.prepend(tips);

        tips.append($('<h3>Tips</h3>'));
        var tip = $('<div />');
        tips.append(tip);
        show_tip =
          function(tipText)
          {
            if ("none" == tips.css("display"))
            {
              tip.html(tipText);
              tips.slideDown(500);
            }
            else
            {
              tip.fadeOut(
                200,
                function()
                {
                  tip.html(tipText);
                  tip.fadeIn(200);
                }
              );
            }
          };
        hide_tips =
          function()
          {
            tips.slideUp(1500);
          };
      }

      // For some reason IE prefers having a div here.
      var div = $('<div style="text-align: center"/>');
      search.append(div);

      var tools = $('<div/>');
      div.append(tools);
      tools.append("Look&nbsp;");
      var radius = $('<select/>');
      for (var i = 0; i < options.searchRadii.length; ++i)
      {
        // IE needs to have the value option...
        radius.append(
          $('<option/>')
          .attr("value", options.searchRadii[i])
          .append(options.searchRadii[i] + options.searchUnit)
        );
      }
      tools.append(radius);

      tools.append("&nbsp;from");
      //div.append($('<p style="text-align: center">- OR -</p>'));

      var loctext = 'Enter location...';
      var loc = $('<input type="text" size="20" />');
      if (options.searchInitialLocation)
        loc.attr('value', options.searchInitialLocation);
      else
        loc.attr('value', loctext);

      loc.blur(
        function()
        {
          if (''==loc.val())
            loc.val(loctext);
          hide_tips();
        }
      );
      loc.focus(
        function()
        {
          if (loctext==loc.val())
            loc.val('');
          else
            loc[0].select();

          show_tip(
            "Enter your search terms here. After a short delay your matches will be shown below.<br/>" +
            "When you see a matching location, simply click on the name to recentre the map.<br/>" +
            "You can then choose your desired search radius and display a list of matching results."
          );
        }
      );
      div.append(loc);

      var noresults = $("<div><em>There are no results to display. Try moving your search location or expanding your search radius.</em></div>");
      noresults.css('display', 'none');
      div.append(noresults);
      var button = $('<input type="button" value="View Results"/>');
      button.css('display', 'none');
      div.append(button);


      var busy = $('<div/>');
      if (options.searchBusyIcon)
      {
        var img = $('<img alt="Busy"/>');
        img.attr("src", options.searchBusyIcon);
        busy.append(img);
        busy.css("visibility", "hidden");
        div.append(busy);
      }

      var curloc = $('<p class="tlMapCurLoc">lat/lng: ?/?</p>');
      curloc.css("display", "none"); /* Keep it hidden - useful for debug */
      div.append(curloc);


      var content = $('<div/>');
      search.append(content);

      var named_location = false;
      button.click(
        function()
        {
          var latlng = $.jmap.GMap2.getCenter();
          var uri = options.searchResultsUri;
          uri += ((uri.indexOf('?') >= 0) ? "&" : "?");
          uri += "posrad=" + latlng.lat() + "," + latlng.lng() + "," + radius.val();
          if (named_location)
            uri += "," + escape(named_location);
          window.location = uri;
        }
      );

      var map_events = Array();
      if (close)
      {
        close.click(
          function()
          {
            for (var i=0; i < map_events.length; ++i)
              GEvent.removeListener(map_events[i]);

            $.jmap.GMap2.clearOverlays();
            tips.remove();
            search.remove();
            map.animate(
              { width: map_orig_width },
              1500,
              "swing",
              function()
              {
                map.jqmHide();
                map.css("display", "block");
                $.jmap.GMap2.checkResize();
                $.jmap.GMap2.returnToSavedPosition();
                $.jmap.displayMarkers();
              }
            );
          }
        );
      }


      var bootstrap =
        function()
        {

          show_tip(
            "Please use the map above to target the area you are interested in. You can drag the map to pinpoint exactly where you want to search.<br/>" +
            "When you move the map, nearby locations will be displayed; clicking on a location name will recenter the map for you." +
            (options.searchUseTagCloud ? " Larger/more populated areas will be displayed in bigger type." : "") + "<br/>" +
            "If you want to search by place name, please enter the name above to get a list of potential matches."
          );

          //window.setTimeout(function(){show_tip("You are still a wanker");}, 1500);
          //window.setTimeout(function(){hide_tips();}, 3000);
          /* Put the tools in the right place */
          /*tools.css("position", "absolute");
          tools.css("top", search.innerHeight() - tools.height());
          content.height(tools.offset().top - content.offset().top - 5);*/

          $.jmap.GMap2.checkResize();
          $.jmap.GMap2.returnToSavedPosition();
          var proj = G_NORMAL_MAP.getProjection();
          var circle = false;
          var getMaxZoom =
            function()
            {
              var zoom = $.jmap.GMap2.getZoom();
              var center = $.jmap.GMap2.getCenter();

              km = radius.val();
              // Is this in miles?
              if ('km' != options.searchUnit)
                km *= 1.609344;

              // Avg. Latitude length for 1 deg == 111km
              var rad = new GLatLng(center.lat() + (km/111), center.lng());

              var cpt = proj.fromLatLngToPixel(center, zoom);
              var rpt = proj.fromLatLngToPixel(rad, zoom);

              var bounds = new GLatLngBounds();
              with (Math)
              {
                radval = floor(sqrt(pow((cpt.x-rpt.x),2) + pow((cpt.y-rpt.y),2)));
                var piover180 = (PI/180);
                var ar, x, y, p;
                // 5 steps is enough.
                for (var a = 0; a <= 360; a+= 72)
                {
                  ar = a * piover180;
                  y = cpt.y + radval * sin(ar)
                  x = cpt.x + radval * cos(ar)
                  bounds.extend(proj.fromPixelToLatLng(new GPoint(x, y), zoom));
                }
              }
              return $.jmap.GMap2.getBoundsZoomLevel(bounds);
            }
          var drawcircle =
            function(zoomMode)
            {
              if ("initial" == zoomMode)
              {
                // Here we calculate the zoom level
                var bounds = $.jmap.GMap2.getBounds();
                var sw = bounds.getSouthWest();
                var ne = bounds.getNorthEast();
                var latdelta = Math.abs(sw.lat() - ne.lat()) * 111 / 2;
                // Convert our lat delta into the same units as our drop down.
                if ('km' != options.searchUnit)
                  latdelta /= 1.609344;

                // Find the biggest value of radius that fits!
                var rad = false;
                // We assume assending order
                radius.children('option').each(
                  function()
                  {
                    var val = $(this).val();
                    if (val > latdelta)
                      return;

                    rad = val;
                  }
                );
                // If we get a false value, it means all sizes are smaller
                // therefore we need to zoom out to see our circle.
                if (false === rad)
                {
                  zoomMode = "radius";
                }
                else if ("auto" == options.searchInitialRadius)
                {
                  radius.val(rad);
                }
                else
                {
                  radius.val(options.searchInitialRadius);
                  zoomMode = "radius";
                }
              }
              // Note that we may sometimes zoom to the bounds of our circle
              // and in this case the projection calculations may be a little out.
              // In practice this does not seem to be much of a problem.
              var zoom = $.jmap.GMap2.getZoom();
              var center = $.jmap.GMap2.getCenter();

              km = radius.val();
              // Is this in miles?
              if ('km' != options.searchUnit)
                km *= 1.609344;

              // Avg. Latitude length for 1 deg == 111km
              var rad = new GLatLng(center.lat() + (km/111), center.lng());

              var cpt, rpt;
              if ("radius" == zoomMode)
              {
                $.jmap.GMap2.setZoom(getMaxZoom());
                zoom = $.jmap.GMap2.getZoom();
                cpt = proj.fromLatLngToPixel(center, zoom);
                rpt = proj.fromLatLngToPixel(rad, zoom);
              }
              else
              {
                cpt = proj.fromLatLngToPixel(center, zoom);
                rpt = proj.fromLatLngToPixel(rad, zoom);
              }
              var pxrad = cpt.y - rpt.y;

              if (!circle)
              {
                circle = { "radius": pxrad, "overlay": false };
                var copts = {
                  radius: pxrad,
                  padding: 3,
                  color: options.searchRingColor + "00",
                  updateUrl: options.searchRadarUri
                };
                circle.overlay = new tlJmapCrossHair(copts);
                $.jmap.GMap2.addOverlay(circle.overlay);
              }
              else if (circle.radius != pxrad)
              {
                circle.overlay.setRadius(pxrad);
                circle.radius = pxrad;
              }
            };

          /* A small utilitity function to scale text appropriately */
          var cloudify =
            function(o, val, limit)
            {
              if (!options.searchUseTagCloud)
                return;
              var segment = limit / 4;
              if (val < (segment))
                o.css('font-size', '150%');
              else if (val < (segment * 2))
                o.css('font-size', '120%');
              else if (val < (segment * 3))
                o.css('font-size', '100%');
              else
                o.css('font-size', '90%');
            };
          var update_search =
            function()
            {
              busy.css("visibility", "hidden");
              locval = loc.val();
              if ('' == locval || loctext == locval)
              {
                busy.css("visibility", "hidden");
                return;
              }
              var limit = 25;
              var restrict_country = "";
              if (options.searchRestrictCountry)
              {
                var latlng = $.jmap.GMap2.getCenter();
                restrict_country = "&restrict_country=" + latlng.lat() + "," + latlng.lng();
              }
              $.getJSON(
                options.searchAjaxUri + "?view=name&name=" + escape(loc.val()) + "&limit=" + limit + restrict_country,
                function(data)
                {
                  button.hide();
                  busy.css("visibility", "hidden");
                  show_tip(
                    "Your search results have been updated with a list of matches. Please click on one of the places shown to recentre the map.<br />" +
                    (options.searchUseTagCloud ? "Larger/more populated places are shown in bigger type." : "")
                  );
                  content.text("");
                  content.append($("<h5>Matches:</h5>"));
                  var resultcount = 0;
                  $.each(
                    data.results,
                    function(i,item)
                    {
                      var li = $("<span/>").append(item.name);
                      cloudify(li, item.population_order, data.resultCount);
                      li.click(
                        function()
                        {
                          $.jmap.GMap2.setCenter(new GLatLng(item.latitude, item.longitude));
                          named_location = item.name;
                          curloc.text(item.name);
                          loc.val(item.name);
                        }
                      );
                      if (i > 0)
                        content.append(", ");
                      content.append(li);
                      resultcount++;
                    }
                  );
                  if (!resultcount)
                    content.append($("<p>Sorry, but no matching terms were found. Please try again.</p>"));
                }
              );
            };
          var markers = Array();
          var update_markers =
            function()
            {
              // Clear existing markers.
              for (var i = 0; i<markers.length; ++i)
                $.jmap.GMap2.removeOverlay(markers[i]);
              markers = Array();

              var latlng = $.jmap.GMap2.getCenter();
              var limit = 20;
              var uri = options.searchAjaxUri + "?view=markers&posrad=" + latlng.lat() + "," + latlng.lng() + "," + radius.val() + "&limit=" + limit;
              $.each(options.searchMarkerAjaxArgs,
                function(key, value)
                {
                  uri += "&" + escape(key) + "=" + escape(value);
                }
              );
              $.getJSON(
                uri,
                function(data)
                {
                  if (data.resultCount < 1)
                  {
                    button.hide();
                    noresults.show();
                    return;
                  }
                  noresults.css('display', 'none');
                  if (data.resultCount < 2)
                    button.val('View the only result');
                  else
                    button.val('View the ' + data.resultCount + ' results');
                  button.show();
                  $.each(
                    data.results,
                    function(i,item)
                    {
                      $.jmap.createMarker(
                        item,
                        function(marker)
                        {
                          $.jmap.GMap2.addOverlay(marker);
                          markers.push(marker);
                        }
                      );
                    }
                  );
                }
              );
            };
          var first_nearby = true;
          var update_nearby =
            function()
            {
              var latlng = $.jmap.GMap2.getCenter();
              var limit = 25;
              $.getJSON(
                options.searchAjaxUri + "?view=nearby&posrad=" + latlng.lat() + "," + latlng.lng() + "," + radius.val() + "&limit=" + limit,
                function(data)
                {
                  // This allows the listing markers themselves on first run
                  // but this then has issues on removing them... (i.e. they are not removed!)
                  if (!options.showSearchAuto || !first_nearby)
                    update_markers();
                  busy.css("visibility", "hidden");
                  if (!first_nearby)
                  {
                    show_tip(
                      "The list of nearby locations has been updated. Please click on one of the places shown to recentre the map.<br />" +
                      (options.searchUseTagCloud ? "Larger/more populated places are shown in bigger type. " : "") +
                      "They are ordered by their distance from the centre point of the map."
                    );
                  }
                  first_nearby = false;
                  content.text("");
                  content.append($("<h5>Nearby:</h5>"));
                  var resultcount = 0;
                  $.each(
                    data.results,
                    function(i,item)
                    {
                      var li = $("<span/>").append(item.name);
                      cloudify(li, item.population_order, data.resultCount);
                      li.click(
                        function()
                        {
                          $.jmap.GMap2.setCenter(new GLatLng(item.latitude, item.longitude));
                          named_location = item.name;
                          curloc.text(item.name);
                          loc.val(item.name);
                        }
                      );
                      if (i > 0)
                        content.append(", ");
                      content.append(li);
                      resultcount++;
                    }
                  );
                  if (!resultcount)
                    content.append($("<p>Sorry, but we could not find any nearby locations.</p>"));
                }
              );
            };
          /* Wire things up */
          var timeout = false;
          var dp3 =
            function(num)
            {
              return Math.round(num * 1000) / 1000;
            };
          var update =
            function(mode)
            {
              if ("initial" == mode)
              {
                named_location = loc.val();
                if (loctext == named_location)
                  named_location = false;
              }
              else
              {
                loc.val(loctext);
                named_location = false;
              }

              var center = $.jmap.GMap2.getCenter();
              curloc.text("lat/lng: " + dp3(center.lat()) + "/" + dp3(center.lng()));
              drawcircle(mode);
              if (timeout)
                window.clearTimeout(timeout);
              busy.css("visibility", "visible");
              timeout = window.setTimeout(update_nearby, 500);
            };

          loc.keyup(
            function()
            {
              if (timeout)
                window.clearTimeout(timeout);
              busy.css("visibility", "visible");

              locval = loc.val();
              if ('' == locval || loctext == locval)
                return;
              timeout = window.setTimeout(update_search, 1000);
            }
          );
          update("initial");
          radius.change(
            function()
            {
              var save_name = named_location;
              update("radius");
              if (save_name)
              {
                named_location = save_name;
                curloc.text(named_location);
                loc.val(named_location);
              }
            }
          );
          map_events.push(GEvent.addListener($.jmap.GMap2, "move", update));
          map_events.push(GEvent.addListener($.jmap.GMap2, "moveend", update));
          map_events.push(GEvent.addListener($.jmap.GMap2, "click", function(overlay, point){$.jmap.GMap2.setCenter(point);}));
        };

      if (options.showSearchAuto)
      {
        // There seems to be issues with saving/restoring the position,
        // so we work around it here.
        var cntr = $.jmap.GMap2.getCenter();
        map.width(options.searchMapPercentage + "%");
        $.jmap.GMap2.checkResize();
        $.jmap.GMap2.setCenter(cntr);
        $.jmap.GMap2.savePosition();
        bootstrap();

        // If we have a cookie representing the exit centre point
        // e.g. if a user clicks on a marker,
        // we should restore this assuming that the cookie's
        // original centre point is the same as ours. If it's
        // not the same, we should trash it and go again!
        var saved_pos = $.cookie("tlSavedStartPos");
        cntr = $.jmap.GMap2.getCenter();
        if (saved_pos)
        {
          latlng = saved_pos.split(",");
          if (cntr.lat() == latlng[0] && cntr.lng() == latlng[1])
          {
            // Our old centre position is the same as our new one,
            // so we should scroll to the exit position.
            saved_pos = $.cookie("tlSavedExitPos");
            if (saved_pos)
            {
              latlng = saved_pos.split(",");
              window.setTimeout(
                function()
                {
                  $.jmap.GMap2.panTo(new GLatLng(latlng[0], latlng[1]));
                },
                1000
              );
            }
          }
        }
        $.cookie("tlSavedStartPos", (cntr.lat() + "," + cntr.lng()));
        $.cookie("tlSavedExitPos", null);
      }
      else
      {
        $.jmap.GMap2.clearOverlays();
        $.jmap.GMap2.savePosition();
        map.animate(
          {
            width: options.searchMapPercentage + "%"
          },
          1500,
          'swing',
          bootstrap
        );
      }

    }
    catch(er) { }
  };


})(jQuery);
