function getLatLng(address, callback) {
  var geocoder = new GClientGeocoder();
  
  geocoder.getLatLng(
    address,
    callback
  );
}

var bv_console = {
  out:null,
  counter:0,
  
  makeOutputArea : function () {
    var ol = document.createElement('ol');
    document.getElementsByTagName('body')[0].appendChild(ol);
    ol.style.position = "absolute";
    ol.style.top = "0";
    ol.style.left = "0";
    ol.style.zIndex = "10000";
    ol.style.width ="350px";
    ol.style.height = "150px";
    ol.style.overflow = 'auto';
    ol.style.backgroundColor = "#ddb";
    return ol;
  },
  makeOutputRow : function (msg) {
    var li = document.createElement('li');
    li.style.width = "3000px";
    li.style.fontSize = "10px";
    li.style.fontFamily = "monospace";
    li.style.color = "#555";
    li.style.backgroundColor = (this.counter++ %2) ? "#333" : "#444";
    li.style.border = "1px solid #000";
    li.style.borderColor = "#666 #ddb #111 #bb9";
    li.style.overflow = 'visible';
    var span = document.createElement('span');
    span.innerHTML = msg;
    span.style.color = "#cfc";
    span.style.padding = "2px 5px";
    li.appendChild(span);
    return li;
  },
  dumpObject : function (objObj) {
    var arrRtn = [];
    for (var strProp in objObj) {
      arrRtn.push(strProp+":'"+objObj[strProp]+"'");
    }
    return "{"+ arrRtn.join(', ') +"}";
  },
  
  log : function (mixValue) {
    if (! this.out) this.out = this.makeOutputArea();
    var strMsg = '';
    strMsg += (typeof mixValue == 'string') ? mixValue : this.dumpObject(mixValue);
    strMsg += "\n";
    this.out.appendChild(this.makeOutputRow(strMsg));
  },
  
  complete:true
};
var bvt = (new Date()).getTime();
var bvd = function (msg,obj) {
  if (false) { // change to 'true' to debug w/Firebug or replacement console created above
    (window.console && console.log) || (window.console = bv_console);
    var dt = (new Date()).getTime() - bvt;
    if (typeof msg != 'object') console.log(dt +": "+ msg);
    else {
      obj = msg;
      msg = '';
    }
    if (obj) {
      console.log(dt +":"+ msg);
      console.log(obj);
    }
  }
}


// http://code.google.com/apis/maps/documentation/

var envelepMap = {
  
  blnInitialized : false,
  blnClickable : true,
  blnSizeIcons : true,
  nodeMap : null,
  intMaxVisiblePerSet : 250,
  currentBounds : {},
  objMapSettings : {
      scale : 2,
      center : [22,0],
      centerOn : null,
      autoOpen : null
    },
  bitActive : 0,
  objSets : {},
  objSetsLength : 0,
  objAllMarkers : {},
  arrSetPriority : [
      'me',
      'friends_on',
      'friends_off',
      'members_on',
      'members_off',
      'members_active',
      'visitors',
      'events',
      'start',
      'clubs',
      'verified',
      'featured'
    ],
  arrAllowedSets : [],
  objMarkerOptions  : {
      'me'             : {clickable:true,  title:'Me'},
      'friends_on'     : {clickable:true,  title:' (online)'},
      'friends_off'    : {clickable:true,  title:' (offline)'},
      'members_on'     : {clickable:true,  title:' (online)'},
      'members_off'     : {clickable:true,  title:' (offline)'},
      'members_active' : {clickable:true,  title:' (active)'},
      'visitors'       : {clickable:false, title:'Recent Visitor'},
      'events'         : {clickable:true},
      'start'          : {clickable:true,  title:'Start Location'},
      'clubs'          : {clickable:true},
      'verified'       : {clickable:true},
      'featured'       : {clickable:true}
    },
  objMarkerIcons : {},




  init : function () {
    if (GBrowserIsCompatible()) {
      var bMiniMap = false;
      var bMapNoControls = false;
      var bClickable = true;
      var bSizeIcons = true;

      var mapDiv = document.getElementById("mapView");
      
      if (!mapDiv) {
        mapDiv = document.getElementById("mapViewMini");
        bMiniMap = true;
      }
      
      if (!mapDiv) {
        mapDiv = document.getElementById("mapViewUpgrade");
        bMapNoControls = true;
        bClickable = false;
        //bSizeIcons = false;
      }
      
      if (!mapDiv) {
        return;
      }

      envelepMap.nodeMap = new GMap2(mapDiv);
      if (!bMapNoControls) {
        if (!bMiniMap) {
          envelepMap.nodeMap.addControl(new GLargeMapControl());
          envelepMap.nodeMap.addControl(new GMapTypeControl());
          envelepMap.nodeMap.addControl(new GScaleControl());
        } else {
          envelepMap.nodeMap.addControl(new GSmallMapControl());
        }
      }
      envelepMap.setMap();
      
      envelepMap.blnInitialized = true;
      envelepMap.blnClickable = bClickable;
      envelepMap.blnSizeIcons = bSizeIcons;
  
      envelepMap.panToCenter();
      if (envelepMap.objMapSettings.autoOpen && envelepMap.objAllMarkers[envelepMap.objMapSettings.autoOpen]) {
        // use setTimeout to make the action asynchronous, after the queued js has executed
        setTimeout('envelepMap.makeInfoWindow(envelepMap.objAllMarkers["'+ envelepMap.objMapSettings.autoOpen +'"])();', 1);
      }
      
      GEvent.addListener(envelepMap.nodeMap, 'zoomend', envelepMap.manageView);
      GEvent.addListener(envelepMap.nodeMap, 'moveend', envelepMap.manageView);
      
      if (window.checkActiveSets) {
        envelepMap.fnOnRegister = window.checkActiveSets;
      } else {
        envelepMap.fnOnRegister = envelepMap.activateAll;
      }
    } else {
      // TODO: something for incompatible browsers?
    }
  },



/// Google map api interaction
  setMap : function () {
    if (envelepMap.nodeMap) {
      if (envelepMap.objMapSettings.sw && envelepMap.objMapSettings.ne) {
        envelepMap.objMapSettings.boundsObj = new GLatLngBounds(
            new GLatLng(envelepMap.objMapSettings.sw[0],envelepMap.objMapSettings.sw[1]),
            new GLatLng(envelepMap.objMapSettings.ne[0],envelepMap.objMapSettings.ne[1])
          );
        envelepMap.nodeMap.setCenter(
            envelepMap.objMapSettings.boundsObj.getCenter(),
            Math.min(14,envelepMap.nodeMap.getBoundsZoomLevel(envelepMap.objMapSettings.boundsObj))
          );
      } else {
        envelepMap.nodeMap.setCenter(new GLatLng(envelepMap.objMapSettings.center[0],envelepMap.objMapSettings.center[1]), envelepMap.objMapSettings.scale);
      }
      envelepMap.currentBounds = envelepMap.nodeMap.getBounds();
    }
  },

  getIcon : function (strType) { // different sets demand different-looking pegs
    if (! envelepMap.objMarkerIcons._baseline) {
      var icon = new GIcon();
      icon.image = "/images/icons/peg_me.png";
      icon.shadow = "/images/icons/peg_shadow.png";
      icon.iconSize = new GSize(15, 26);
      icon.shadowSize = new GSize(30, 18);
      icon.iconAnchor = new GPoint(6, 20);
      icon.infoWindowAnchor = new GPoint(5, 1);
      envelepMap.objMarkerIcons._baseline = icon;
    }
    if (! envelepMap.objMarkerIcons[strType]) {
      envelepMap.objMarkerIcons[strType] = new GIcon(envelepMap.objMarkerIcons._baseline);
      envelepMap.objMarkerIcons[strType].image = "/images/icons/peg_"+ strType +".png";
    }
    if (strType == 'me' && ! envelepMap.objMarkerIcons.me.all_fixed_up) {
      envelepMap.objMarkerIcons.me.iconSize = new GSize(20, 34);
      envelepMap.objMarkerIcons.me.shadowSize = new GSize(36, 24);
      envelepMap.objMarkerIcons.me.iconAnchor = new GPoint(6, 25);
      envelepMap.objMarkerIcons.me.all_fixed_up = true;
    }
    if (strType == 'start' && ! envelepMap.objMarkerIcons.start.all_fixed_up) {
      envelepMap.objMarkerIcons.start.iconSize = new GSize(20, 34);
      envelepMap.objMarkerIcons.start.shadowSize = new GSize(36, 24);
      envelepMap.objMarkerIcons.start.iconAnchor = new GPoint(6, 25);
      envelepMap.objMarkerIcons.start.all_fixed_up = true;
    }
    if (strType == 'visitors' && ! envelepMap.objMarkerIcons.visitors.all_fixed_up) {
      envelepMap.objMarkerIcons.visitors.iconSize = new GSize(15, 26);
      envelepMap.objMarkerIcons.visitors.shadowSize = new GSize(22, 13);
      envelepMap.objMarkerIcons.visitors.iconAnchor = new GPoint(6, 20);
      envelepMap.objMarkerIcons.visitors.all_fixed_up = true;
    }
    if (strType == 'clubs' && ! envelepMap.objMarkerIcons.clubs.all_fixed_up) {
      if (envelepMap.blnSizeIcons) {
        envelepMap.objMarkerIcons.clubs.iconSize = new GSize(15, 26);
        envelepMap.objMarkerIcons.clubs.shadowSize = new GSize(27, 15);
        envelepMap.objMarkerIcons.clubs.iconAnchor = new GPoint(6, 20);
        envelepMap.objMarkerIcons.clubs.all_fixed_up = true;
      }
    }
    if (strType == 'verified' && ! envelepMap.objMarkerIcons.verified.all_fixed_up) {
      if (envelepMap.blnSizeIcons) {
        envelepMap.objMarkerIcons.verified.iconSize = new GSize(20, 34);
        envelepMap.objMarkerIcons.verified.shadowSize = new GSize(30, 18);
        envelepMap.objMarkerIcons.verified.iconAnchor = new GPoint(6, 20);
        envelepMap.objMarkerIcons.verified.all_fixed_up = true;
      }
    }
    if (strType == 'featured' && ! envelepMap.objMarkerIcons.featured.all_fixed_up) {
      if (envelepMap.blnSizeIcons) {
        envelepMap.objMarkerIcons.featured.iconSize = new GSize(20, 34);
        envelepMap.objMarkerIcons.featured.shadowSize = new GSize(33, 21);
        envelepMap.objMarkerIcons.featured.iconAnchor = new GPoint(6, 20);
        envelepMap.objMarkerIcons.featured.all_fixed_up = true;
      }
    }
    
    return envelepMap.objMarkerIcons[strType];
  },

  panToCenter : function (lat, lng) {
    if (
        lat && typeof lat == 'number'
      &&
        lng && typeof lng == 'number'
    ) {
      envelepMap.objMapSettings.centerOn = [lat, lng];
    }
    if (
        envelepMap.blnInitialized
      &&
        envelepMap.objMapSettings.centerOn
      &&
        envelepMap.objMapSettings.centerOn.length
      &&
        envelepMap.objMapSettings.centerOn.length == 2
    ) {
      if (
          typeof envelepMap.objMapSettings.centerOn[0] == 'number'
        &&
          typeof envelepMap.objMapSettings.centerOn[1] == 'number'
      ) {
        envelepMap.nodeMap.panTo(new GLatLng(envelepMap.objMapSettings.centerOn[0], envelepMap.objMapSettings.centerOn[1]));
      }
      envelepMap.objMapSettings.centerOn = null;
    }
  },




/// Functions that import data into the map

  adjustMapSettings : function (newSettings) {
    for (var Name in newSettings) {
      envelepMap.objMapSettings[Name] = newSettings[Name];
    }
    envelepMap.setMap();
    envelepMap.panToCenter();
  },

  allowRegistry : function (strMarkerSetName) {
    if (strMarkerSetName) envelepMap.arrAllowedSets.push(strMarkerSetName);
    else envelepMap.arrAllowedSets = envelepMap.arrSetPriority;
  },

  registerMarkerSets : function (objMarkerSets) {
    if ( ! envelepMap.blnInitialized) {
      setTimeout(
        function (objMarkerSets) {
          return function () { envelepMap.registerMarkerSets(objMarkerSets); };
        }(objMarkerSets),
        100);
    } else {
      if (envelepMap.arrAllowedSets.length == 0) envelepMap.allowRegistry();
      for (var strSetName in objMarkerSets) {
        if ((','+envelepMap.arrAllowedSets.toString()+',').indexOf(','+strSetName+',') != -1) {
          if (envelepMap.objSets[strSetName]) {
            envelepMap.objSets[strSetName].update(objMarkerSets[strSetName]);
          } else {
            envelepMap.objSets[strSetName] = new markerSet(this, strSetName, envelepMap.objSetsLength, objMarkerSets[strSetName]);
            envelepMap.objSetsLength++;
          }
        }
      }
      if (envelepMap.fnOnRegister) envelepMap.fnOnRegister();
    }
  },

  manageView : function () {
    var objNuBounds = envelepMap.nodeMap.getBounds();
    if (envelepMap.currentBounds.toString() != objNuBounds.toString()) {
      for (var strSetName in envelepMap.objSets) {
        if (envelepMap.objSets[strSetName]) envelepMap.objSets[strSetName].reView();
      }
    }
    envelepMap.currentBounds = objNuBounds;
  },
  activateAll : function () {
    for (var strSetName in envelepMap.objSets) {
      if(envelepMap.objSets[strSetName]) envelepMap.objSets[strSetName].activate();
    }
  },
  activateSet : function (strSetName) {
    if (envelepMap.objSets[strSetName]) envelepMap.objSets[strSetName].activate();
  },
  deactivateSet : function (strSetName) {
    if (envelepMap.objSets[strSetName]) envelepMap.objSets[strSetName].deactivate();
  },
  


  complete : true
}




/// Marker Set object:
var markerSet = function (objParent, strName, intIdx, arrSet) {
    this.map = objParent;
    this.name = strName;
    this.idx = Math.pow(2,intIdx); // powers of 2, for use in bitwise comparison
    this.icon = this.map.getIcon(this.name);
    this.type = /^(events|clubs|start|featured|verified)$/i.test(this.name) ? 'place':'user';
    this.active = false;
    this.update(arrSet);
    this.arrSet = arrSet;
    return this;
  };
markerSet.prototype.setImage = function (strImage) {
  this.markers = [];
  for (
      var ii=0, objMarker={};
      objMarker=this.arrSet[ii];
      ii++
  ) {
    if (objMarker && objMarker.peg) {
      objMarker.peg.setImage(strImage);
    }
  }
}
markerSet.prototype.update = function (arrSet) {
  this.markers = [];
  for (
      var ii=0, objMarker={};
      objMarker=arrSet[ii];
      ii++
  ) {
    if ( ! objMarker.id || ! objMarker.lat || ! objMarker.lng) continue;
    if (this.map.objAllMarkers[objMarker.id]) {
      objMarker = this.map.objAllMarkers[objMarker.id];
    } else {
      this.map.objAllMarkers[objMarker.id] = objMarker;
    }

    // make a peg if there isn't one...
    // or replace the previous peg if it was a lower priority set
    if (
        ! objMarker.peg
      ||
        (
            objMarker.set
          &&
            envelepMap.arrSetPriority.toString().indexOf(objMarker.set) > envelepMap.arrSetPriority.toString().indexOf(this.name)
        )
    ) {
      objMarker.set = this.name;
      objMarker.sets || (objMarker.sets = 0);
      if (objMarker.card) objMarker.card.tpl = this.type;
      if (envelepMap.blnClickable) {
        var objOptions = {
            icon:this.icon,
            clickable:this.map.objMarkerOptions[this.name].clickable
          }
      } else {
        var objOptions = {
            icon:this.icon,
            clickable:false
          }
      }
      if (this.map.objMarkerOptions[this.name].title) {
        objOptions.title = this.map.objMarkerOptions[this.name].title.replace(/^ \(/, objMarker.card.name +' (');
      } else if (objMarker.id) {
        objOptions.title = objMarker.card.name;
      }
      objMarker.peg = new GMarker(
          new GLatLng(objMarker.lat,objMarker.lng),
          objOptions
        );
      objMarker.peg.titleName = objMarker.id;
      GEvent.addListener(objMarker.peg, "click", this.makeInfoWindow(objMarker));
    }
    // see "bitActive" comment under set.deactivate
    objMarker.sets = objMarker.sets | this.idx;
    this.markers.push(objMarker);
  }
};
markerSet.prototype.reView = function () {
  if (this.active) {
    this.activate(true);
  } else {
    this.deactivate(true);
  }
};
markerSet.prototype.activate = function (blnForce) {
  if ( ! this.active || blnForce) {
    this.map.bitActive = this.map.bitActive | this.idx; // add this set to the bitwise Active count
    this.active = true;
    for (var xx=0, ii=0, peg=null; ii<this.map.intMaxVisiblePerSet && (marker = this.markers[xx]); xx++) {
      if ( ! marker.envelepMapPlaced ) {
        this.map.nodeMap.addOverlay(marker.peg);
        marker.envelepMapPlaced = true;
      }
      if (this.map.currentBounds.contains(marker.peg.getPoint())) {
        marker.peg.show();
        ii++;
      } else {
        marker.peg.hide();
      }
    }
  }
};
markerSet.prototype.deactivate = function (blnForce) {
  if (this.active || blnForce) {
    this.map.bitActive = (this.map.bitActive | this.idx) ^ this.idx; // remove this set to the bitwise Active count
      // NOTE: you *need* to bitwise OR the idx first, otherwise if it's not there then the XOR will *add* it instead of removing it
    this.active = false;
    for (var xx=0, peg=null; marker = this.markers[xx]; xx++) {
      /*
        Each marker has a bitwise value based on which sets it is part of;
        since it can be in more than one set, we only hide it if the peg
        is not in any active set.
        Bitwise AND is only true if there is overlap between the currently
        active sets, and the sets this peg is part of.
        Treat "me" special: it can be turned off even when it's member_on too.
      */
      if ( ! (this.map.bitActive & marker.sets) || this.name == 'me') {
        marker.peg.hide();
      }
    }
  }
};
markerSet.prototype.makeInfoWindow = function (objMarker) {
  if (objMarker.card) {
    var HTML = false;
    switch (objMarker.card.tpl) {
      case 'user' :
        HTML = '<table width="210" cellspacing="5"><tr valign="top" align="center">';
        if (objMarker.card.avt) {
          HTML += '<td><img src="'+ envelepMap.objMapSettings.UserAvatarDirUrl + objMarker.card.avt +'"';
          if (envelepMap.objMapSettings.UserAvatarWidth)  HTML += ' width="'+  envelepMap.objMapSettings.UserAvatarWidth  +'"';
          if (envelepMap.objMapSettings.UserAvatarHeight) HTML += ' height="'+ envelepMap.objMapSettings.UserAvatarHeight +'"';
          HTML += '/></td>';
        }
        HTML += '<td><a href="'+ envelepMap.objMapSettings.UserUrlPrefix + objMarker.id +'">'+ objMarker.id +'</a><br />';
        if (objMarker.card.name)  HTML += objMarker.card.name +'<br />';
        if (objMarker.card.loc)   HTML += objMarker.card.loc +' &nbsp;';
        if (objMarker.card.ctry)  HTML += objMarker.card.ctry.replace(/\s+/,'&nbsp;') +'&nbsp; <br />';
        if (objMarker.card.ccode) HTML += '<img src="'+ envelepMap.objMapSettings.CountryFlagDirUrl + objMarker.card.ccode +'.gif" />';
        HTML += '</td></tr></table>';
        break;

      case 'place' :
        HTML = '<table width="210" cellspacing="5"><tr valign="top" align="center">';
        if (objMarker.card.avt) {
          HTML += '<td><img src="'+ envelepMap.objMapSettings.PlaceAvatarDirUrl + objMarker.card.avt +'"';
          if (envelepMap.objMapSettings.PlaceAvatarWidth)  HTML += ' width="'+  envelepMap.objMapSettings.PlaceAvatarWidth  +'"';
          if (envelepMap.objMapSettings.PlaceAvatarHeight) HTML += ' height="'+ envelepMap.objMapSettings.PlaceAvatarHeight +'"';
          HTML += '/></td>';
        }
        HTML += '<td>';
        if (objMarker.card.url) {
          HTML += '<strong><a href="'+ envelepMap.objMapSettings.PlaceUrlPrefix + objMarker.card.url +'">'+ objMarker.card.name +'</a></strong><br />';
        } else {
          HTML += objMarker.id + '<br />';
        }
        if (objMarker.card.loc) {
          HTML += objMarker.card.loc +'<br />';
        }
        if (objMarker.card.start)  HTML += 'Start: ' + objMarker.card.start  +'<br />';
        if (objMarker.card.end)  HTML += 'End: ' + objMarker.card.end  +'<br />';
        if (objMarker.card.adr1)  HTML += objMarker.card.adr1  +'<br />';
        if (objMarker.card.adr2)  HTML += objMarker.card.adr2  +'<br />';
        if (objMarker.card.city)  HTML += objMarker.card.city  +', ';
        if (objMarker.card.state) HTML += objMarker.card.state +' &nbsp; ';
        if (objMarker.card.zip)   HTML += objMarker.card.zip;
        if (objMarker.card.city || objMarker.card.state || objMarker.card.zip) HTML += '<br />';
        if (objMarker.card.ctry)  HTML += objMarker.card.ctry.replace(/\s+/,'&nbsp;') +'<br />';
        if (objMarker.card.ccode) HTML += '<img src="'+ envelepMap.objMapSettings.CountryFlagDirUrl + objMarker.card.ccode +'.gif" />';
        //if (objMarker.card.dist)  HTML += objMarker.card.dist +' miles<br />';
        if (objMarker.card.rate)  HTML += objMarker.card.rate;
        
        HTML += '</td></tr></table>';
        break;
    }
    if (HTML) {
      return function () {
        objMarker.peg.openInfoWindowHtml(HTML);
      }
    }
  }
  return function () {};
}; // END: makeInfoWindow




if (window.GMap2) window.onload = function () { setTimeout(envelepMap.init, 100); };