gogoGoogleMap = 
{
  
  geocode_address_limiter: '',
  
  auto_attach_picker: function(form, inline) 
  {
    // Search the document input elements for something named Latitude or Longitude
    // When found one, search for the matching other half
    // Look for any applicable Country, City, Street fields
    //  (to use for geocoding)
    // When we have everything attach a button to the second co-ordinate field (Longitude Probably)
    // which opens the picker window with the current Lat, Lon and Address, and callback for 
    // saving the values
    
    if(!form)
    {
      var forms = document.getElementsByTagName('form');
      for(var x = 0; x < forms.length; x++)
      {
        this.auto_attach_picker(forms[x], inline);
      }      
      return;
    }
    
    // Find input elements
    var ielms = form.getElementsByTagName('input');    
    var telms = form.getElementsByTagName('textarea');
    var inputs = [];
    
    for(var i = 0; i < ielms.length; i++)
    {
      inputs[inputs.length]=ielms[i];
    }
    
    for(var i = 0; i < telms.length; i++)
    {
      inputs[inputs.length]=telms[i];
    }
    
    //alert(inputs.length);
    // Look for lat or lon    
    for(var x = 0; x < inputs.length; x++)
    {
      var fields = { Latitude: null, Longitude: null, Address: null, Street: null, City: null, Suburb: null, Country: null, PostCode: null };
      var prefix = null;
      var suffix = null;
      var attachTo = null;
      
      if(inputs[x].name.match(/^(.*)(Latitude|Longitude)(.*)$/))
      {
        prefix = RegExp.$1;                
        suffix = RegExp.$3;        
        var thisIs = RegExp.$2;
        fields[thisIs] = inputs[x];
                
        // And find the matching
        var lookingFor = prefix + (thisIs === 'Latitude' ? 'Longitude' : 'Latitude') + suffix; 
        for(var i = x+1; i < inputs.length; i++)
        {
          if(inputs[i].name == lookingFor)
          {
            fields[(thisIs === 'Latitude' ? 'Longitude' : 'Latitude')] = inputs[i];
            attachTo = inputs[i];
            break;
          }
        }
        
        if(fields.Longitude && fields.Latitude)
        {          
          // Got both, try and find street city and country data
          // note region doesn't work so well because Google gets confused on like
          // "South Island" as a region
          for(var i = 0; i < inputs.length; i++)
          {
            if(fields.Country == null && inputs[i].name === (prefix + 'Country' + suffix))
            {
              fields.Country = inputs[i];
            }
            else if(fields.Region == null && inputs[i].name === (prefix + 'Region' + suffix))
            {
              fields.Region = inputs[i];
            }
            else if(fields.Province == null && inputs[i].name === (prefix + 'Province' + suffix))
            {
              fields.Province = inputs[i];
            }
            else if(fields.City == null && inputs[i].name === (prefix + 'City' + suffix))
            {
              fields.City = inputs[i];
            }            
            else if(fields.Suburb == null && inputs[i].name === (prefix + 'Suburb' + suffix))
            {
              fields.Suburb = inputs[i];
            }                        
            else if (
              fields.PostCode == null &&
              (
                    inputs[i].name === (prefix + 'PostCode' + suffix)
                ||  inputs[i].name === (prefix + 'PostalCode' + suffix)
                ||  inputs[i].name === (prefix + 'ZipCode' + suffix)
              )
            )
            {              
              fields.PostCode = inputs[i];
            }
            else if (
              fields.Street == null &&
              (
                    inputs[i].name === (prefix + 'Street' + suffix)
                ||  inputs[i].name === (prefix + 'Address' + suffix)
                ||  inputs[i].name === (prefix + 'Street1' + suffix)
              )
            )
            {              
              fields.Street = inputs[i];
            }
            else
            {
           //                  alert(prefix + ' ' + suffix + ' ' + inputs[i].name);
            }
          }

          if(inline)
          {
            if(typeof inline == 'boolean')
            {
              var mapHolder = document.createElement('div');
              mapHolder.className = 'googleMap clearfloat';
              mapHolder.style.border = '1px solid red';
              mapHolder.style.display = 'block';
              mapHolder.style.cssFloat = 'none';
              mapHolder.style.clear    = 'both';
              mapHolder.style.height   = '480px';
              mapHolder.style.width    = '100%';
              attachTo.parentNode.appendChild(mapHolder);
              inline = mapHolder;
            }
            this.inline_picker(inline,fields);
          }
          else
          {
            this.attach_picker(attachTo, fields);
          }
          
        }
      }
    }    
    
  },
  
  attach_picker: function(attachTo, fields)
  {
    fields.button_callback = function(Latitude, Longitude)
    {
      this.Latitude.value  = Latitude;
      this.Longitude.value = Longitude;
    }
    
    var afi = this._active_fields.length;
    this._active_fields[this._active_fields.length] = fields;    
    
    var button = document.createElement('input');
    button.type = 'button';
    button.value = 'Map';
    button.gogoGoogleMapFieldIndex = afi;
    
    button.onclick             = function()
      {
        var fields = gogoGoogleMap._active_fields[this.gogoGoogleMapFieldIndex];
        
        // Build an address string
        var address = [ ];
        if(fields.Street.value)
        {
          address[address.length] = fields.Street.value;
        }
        
        if(fields.City.value)
        {
          address[address.length] = fields.City.value;  
        }
        
        if(fields.Country.value)
        {
          address[address.length] = fields.Country.value;
        }
        
        address = address.join(', ');
           
        var url = '/__classpath/gogo/gogoGoogleMap/picker.html?Callback=gogoGoogleMap._active_fields[' + this.gogoGoogleMapFieldIndex + '].button_callback&Address=' + encodeURIComponent(address) + '&Latitude=' + encodeURIComponent(fields.Latitude.value) + '&Longitude=' + encodeURIComponent(fields.Longitude.value);
        
        var win = window.open(url, 'MapWindow', 'width=570,height=540');
        win.focus();
        
      }
      
      attachTo.parentNode.insertBefore(button, attachTo.nextSibling);
  },
  
  inline_picker: function(parentElement, fields)
  {
    var map;
    var selectedMarker;
    var mapHandler = this;
    var zoom       = 1; 
    
    function picker_marker(point)
    {
      if(!point) return false;
      
      if (selectedMarker) 
      {
        map.removeOverlay(selectedMarker);
      }
      selectedMarker = new GMarker(point, {clickable:true,draggable:true}); 
      map.addOverlay(selectedMarker);
      update_point();      
      GEvent.addListener(selectedMarker, "dragend", function() {
        update_point();
      });
    }
    
    function update_point()
    {
      if(!selectedMarker) return false;
      
      var point = selectedMarker.getPoint();
      var lat = Math.round(point.lat() * 100000) / 100000;
      var lng = Math.round(point.lng() * 100000) / 100000;
      fields.Latitude.value  = lat;
      fields.Longitude.value = lng;
      return true;      
    }

    function update_geocode()
    {
      var address = [ ];
      
      zoom = 1;
      
      if(fields.Street && fields.Street.value)
      {
        zoom = Math.max(16,zoom);
        address[address.length] = fields.Street.value;
      }
      else if(fields.Address && fields.Address.value)
      {
        zoom = Math.max(16,zoom);
        address[address.length] = fields.Street.value;
      }
      
      if(fields.City && fields.City.value)
      {
        if(fields.Suburb && fields.Suburb.value)          
        {
          zoom = Math.max(14,zoom);
          address[address.length] = fields.Suburb.value;
        }    
        
        zoom = Math.max(12,zoom);
        address[address.length] = fields.City.value; 
          
      }
      else if(fields.Suburb && fields.Suburb.value)          
      {
        zoom = Math.max(14,zoom);
        address[address.length] = fields.Suburb.value;
      }  
      else 
      {
        if(fields.Province && fields.Province.value)
        {
          zoom = Math.max(8,zoom);
          address[address.length] = fields.Province.value;  
        }
         
        if(fields.Region && fields.Region.value)
        {
          zoom = Math.max(6,zoom);
          address[address.length] = fields.Region.value;  
        }
      }
      
      if(fields.Country && fields.Country.value)
      {
        zoom = Math.max(5,zoom);
        address[address.length] = fields.Country.value;
      }            
      
      if(mapHandler.geocode_address_limiter)
      {
        address[address.length] = mapHandler.geocode_address_limiter;
      }
      
      address = address.join(' ');
      
      
      // alert(address);
      
      mapHandler.geocode_placemark(address,
                         function(placemark) 
                         { 
                           if(placemark == null) return false;
                           placemark = mapHandler.collapse_placemark(placemark);                                                                                 
                           point = placemark.Point;
                                                                                                            
                           if(fields.Street && fields.Street.value) 
                           {
                             if(fields.PostCode && placemark.PostalCodeNumber)
                             {                            
                               fields.PostCode.value = placemark.PostalCodeNumber;
                             }                             
                             picker_marker(point); 
                           }
                           else
                           {
                              if (selectedMarker) 
                              {
                                map.removeOverlay(selectedMarker);
                              }
                              selectedMarker = null;
                           }
                           map.setCenter(point, zoom); }
                        );
      
    }
    
    if (GBrowserIsCompatible()) 
    {      
      fields.Longitude.gogoGoogleMap =
      fields.Latitude.gogoGoogleMap =
      parentElement.gogoGoogleMap =
      map = new GMap2(parentElement);
          
      map.addControl(new GLargeMapControl());
      map.addControl(new GMapTypeControl());
      
      if(fields.Latitude.value && fields.Longitude.value)
      {
        zoom = 16;
        var point = new GLatLng(fields.Latitude.value, fields.Longitude.value);
        map.setCenter(point, zoom); //-43.531065, 172.636564), 14);                
        picker_marker(point);        
      }
      else
      {
        zoom = 1;
        map.setCenter(new GLatLng(-43.531065, 172.636564), zoom);            
        update_geocode();
      }
      
      GEvent.addListener(map, "dblclick", function(marker, point) 
      { 
        picker_marker(point);
        update_point();            
      });
       
      fields.Longitude.onchange =
      fields.Latitude.onchange  = function()
      {
        var point = new GLatLng(fields.Latitude.value, fields.Longitude.value);
        map.setCenter(point, zoom); //-43.531065, 172.636564), 14);                
        picker_marker(point);        
      }
      
      var changers = ['Address', 'Street','City','Suburb','Province','Region','Country'];
      for(var i = 0; i < changers.length; i++)
      {
        if(fields[changers[i]]) fields[changers[i]].onchange = function() { update_geocode(); }
      }
      
      var recodeButton = document.createElement('input');
      recodeButton.value = 'Auto-Locate Address';
      recodeButton.onclick = function() { update_geocode(); }
      recodeButton.type = 'button';
      parentElement.parentNode.insertBefore(recodeButton, parentElement);
    }
  },
  
  /** Geocodes an address and hits the callback with a GLatLng or null */
  
  geocode: function(address, point_callback)
  {
    this.geocode_placemark(address, function(placemark) 
                      { 
                        if(placemark == null) return null;                        
                        point = placemark.Point;
                        point_callback( new GLatLng(point[1],point[0]) );
                      });    
  },
  
  /** Geocodes an address and returns the first placemark or null
   *       
   *      {
   *        "address": "1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA",
   *        "AddressDetails": {
   *          "Country": {
   *            "CountryNameCode": "US",
   *            "AdministrativeArea": {
   *              "AdministrativeAreaName": "CA",
   *              "SubAdministrativeArea": {
   *                "SubAdministrativeAreaName": "Santa Clara",
   *                "Locality": {
   *                  "LocalityName": "Mountain View",
   *                  "Thoroughfare": {
   *                    "ThoroughfareName": "1600 Amphitheatre Pkwy"
   *                  },
   *                  "PostalCode": {
   *                    "PostalCodeNumber": "94043"
   *                  }
   *                }
   *              }
   *            }
   *          },
   *          "Accuracy": 8
   *        },
   *        "Point": {
   *          "coordinates": [-122.083739, 37.423021, 0]
   *        }
   *      }
   *
   */
   
  geocode_placemark: function(address, placemark_callback)
  {
    this.geocode_full(address, function(locations) 
                      { 
                        if(!locations) return null;        
                        if(locations.Status.code != 200) return null;
                        placemark_callback( locations.Placemark[0] );
                      });    
  },
   
  /** Geocodes an address and returns the full locations return from Google.
   *
   *  {
   *    "name": "1600 Amphitheatre Parkway, Mountain View, CA, USA",
   *    "Status": {
   *      "code": 200,
   *      "request": "geocode"
   *    },
   *    "Placemark": [
   *     // See geocode_placemark
   *    ]
   *  }
   */
   
  geocode_full:function(address, full_callback)
  {
    if(this._geocode_cache[address])
    {
      full_callback(this._geocode_cache[address]);      
    }
    else
    {
      var mapper = this;
      var geocoder = new GClientGeocoder();
      geocoder.getLocations
      (
        address,
        function(locations) { mapper._geocode_cache[address] = locations; full_callback(locations); }      
      );
    }
  },
    
  /** Given a placemark object collapse to something like
   *
   *      {
   *         "address": "1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA",
   *          "CountryNameCode": "US",
   *          "AdministrativeAreaName": "CA",
   *          "SubAdministrativeAreaName": "Santa Clara",
   *          "LocalityName": "Mountain View",   
   *          "ThoroughfareName": "1600 Amphitheatre Pkwy"     
   *          "PostalCodeNumber": "94043"    
   *          "Accuracy": 8 
   *          "coordinates": [-122.083739, 37.423021, 0]
   *          "Point": new GLatLng(x,y)   
   *      }
   */
   
  collapse_placemark: function(placemark)
  {
    function collapse_object(o)
    { 
      for(var i in o)
      {
        if(typeof o[i] == 'object')
        {
          var s = collapse_object(o[i]);
          for(var j in s)
          {
            if(typeof j != 'string') continue;
            if(typeof s[j] == 'string' || typeof s[j] == 'number')
            {
              o[j] = s[j];
            }
          }      
        }
      }
      
      return o;
    }
    
    if(typeof placemark.clone == 'undefined')
    {
      function clone (deep) {
        var objectClone = new this.constructor();
        for (var property in this)
          if (!deep)
            objectClone[property] = this[property];
          else if (typeof this[property] == 'object')
            objectClone[property] = this[property].clone(deep);
          else
            objectClone[property] = this[property];
        return objectClone;
      }
      Object.prototype.clone = clone;
    }
    var newPlacemark = placemark.clone(true);
        
    newPlacemark.Point = new GLatLng(placemark.Point.coordinates[1],placemark.Point.coordinates[0]);

    return collapse_object(newPlacemark);
  },
  
  
  draw_map_with_marks:function(container, markers)
  {
    if(!GBrowserIsCompatible()) return false;
    
    var map = null;
    var bounds = null;

    map = new GMap2(container);
    map.addControl(new GLargeMapControl());
    map.addControl(new GMapTypeControl());
    map.setCenter(new  GLatLng(10, 10)); 
    
    for(var x = 0; x < markers.length; x++)
    {
      var ll = new GLatLng(markers[x].lat, markers[x].lon);
      if(!bounds)
      {
        bounds = new GLatLngBounds(ll,ll);
      }
      else
      {
        bounds.extend(ll);
      }
      
      // Each marker is {lat: , lon: , description: 'HTML'}
      var marker = new GMarker(ll);
      map.addOverlay(marker);
      if(markers[x].description)
      {
        if(marker.bindInfoWindowHtml)
        {
          marker.bindInfoWindowHtml(markers[x].description);
        }
      }
    }
    
    if(bounds)
    {
      map.setCenter(bounds.getCenter());
      map.setZoom(map.getBoundsZoomLevel(bounds));
    }
   
    
    return map;
  },
  
  _geocode_cache: { },
  _active_fields: [ ]
}
