/*
* Copyright (c) 2009 Chad Norwood.  All rights reserved.
* http://chadnorwood.com/
*
* You can use this according to version 3 of the GNU General Public License.
* http://www.opensource.org/licenses/gpl-3.0.html
*
* Status: work in progress, code being migrated to jquery style
*/


// google.load()
// http://code.google.com/apis/ajaxlibs/documentation/index.html
//
// - put long descriptions in text box with scroll 
// http://www.communitywalk.com/map/617#0002j7
//
// reverse geocoding (get address)
// http://googlegeodevelopers.blogspot.com/2008/10/geocoding-in-reverse.html
// http://digitalinspiration.com/community/location.html

/*
todo: resolve problem when marker near top is clicked - google chokes
firefox warning: Unresponsive script
http://maps.google.com/intl/en_us/mapfiles/150c/maps2.api/main.js:138
*/


// Global Variables
var  
    mapVersion = "Map Version 2009-4-27",
    jumptxt = " Jump to a City, Address, or Zip",
    urlIconApproved = "http://www.google.com/mapfiles/marker.png",
    urlIconPend = "http://gmaps-samples.googlecode.com/svn/trunk/markers/orange/blank.png",
    urlIconAdd  = "http://gmaps-samples.googlecode.com/svn/trunk/markers/blue/blank.png",
    //urlIconPend = "http://landmarkfinder.com/map/icon_yellow.png",

    // debug statements - changed in cnOnLoad
    debugFirebugCtrl = 1,
    debugFirebugData = 1,
    debugFirebugTimers = 1,
    debugAlert = 0,
    errorAlert = 1, // pop up alert msgs if error state

    // these are class and id names, must match original html
    idRightC = "resultsTab",
    classlist = "classlist",



    start, // pop up alert msgs if error state

    maxMarkers = 500,  // max number of markers on map 
    dMarker = 0,

    // for now we retrieve static json file as our data source
    //jsonURL = '/map/lmf2.js';

    jsonURL = '/map/get.php';
    jsonPendingURL = '/map/get.php?t=1';
var
    addURL= "/map/add.php",

    Map = null,
    geocoder = null,
    redrawing = false,
    startms = 0,
    cZoom = 10,
    cnLocations = {0:{},1:{}},
    cnPending = {}, 
    rawJson = {};
    pendingJson = {0:{},1:{},loaded:false},
    stats = {};
    curFilters = {'curCatg':{}, 'curRating':{}, 'curType':0};
    cnFilters = {};
    numMarkers = 0;
    numPending = 0;
    curShowData = 0;
    dataTypeText = {
      0:"Approved Landmarks Only",
      1:"Pending Landmarks Only",
      2:"Approved and Pending Landmarks" };

    // icon for google maps marker
    iconApproved = new GIcon(G_DEFAULT_ICON, urlIconApproved);
    iconAdd = new GIcon(G_DEFAULT_ICON, urlIconAdd);
    iconPend = new GIcon(G_DEFAULT_ICON, urlIconPend);
/*
    iconbig = new GIcon();
    iconbig.image = "http://www.google.com/mapfiles/marker.png";
    iconbig.shadow = "http://www.google.com/mapfiles/shadow50.png";
    iconbig.iconSize = new GSize(20, 34);
    iconbig.shadowSize = new GSize(37, 34);
    iconbig.iconAnchor = new GPoint(6, 34);
    iconbig.infoWindowAnchor = new GPoint(5, 1);
*/


// use jquery and ajax to retrieve a json file
function getDataJQuery() 
{
      $.getJSON(jsonURL, function(data){
        if (data.count < 1) {
           if (errorAlert) alert("No Data for Map.  Trouble getting from json URL: " + jsonURL);
           return;
        }
        rawJson = data;
        gotData();
      });
}
function clearPendingDataJQuery() 
{
    pendingJson = {0:{},1:{},loaded:false};
    cnRedraw();
}
function getPendingDataJQuery() 
{
      $.getJSON(jsonPendingURL, function(data){
        if (data.count < 1) {
           if (errorAlert) alert("No Data for Map.  Trouble getting from json URL: " + jsonPendingURL);
           return;
        }
        pendingJson = data;
        pendingJson['loaded'] = true;
        stats['pendingJsonLength'] = 0;
        for (var i in pendingJson[1]) {
          stats['pendingJsonLength']++;
        }
        cnRedraw();
      });
}

function gotData() 
{
    if (errorAlert && !('37' in rawJson[0])) {
        // alert("JSON LMF sample category: " + rawJson[0]['37'] +"\n a name from json data:\n"+ rawJson[1]['0']['n']);
        if (errorAlert) alert("JSON is bad, could not find sample category");
    }
    //processJson();

    // initialize stats 
    stats['rawJsonLength'] = 0;
    for (var i in rawJson[1]) {
      stats['rawJsonLength']++;

      continue; // skip normalize data
      //
      // TODO: normalize data (unity temple has errors like spacing: lat="- 87.2342")
      //
      var kk = rawJson[1][i];
      if (kk['lg'].match(/[^\d\.\-]/)) {
        str += kk['n'] +" ("+kk['lg']+") ";
      } 
    }
    //console.log(cnt+" bad data: "+str);

    // initialize filters
    for (var i in rawJson[0]) {
      curFilters['curCatg'][i] = true;
    }
    for (i=1; i<4; i++) {
      curFilters['curRating'][i] = true;
    }
    for (var i in cnFilters) {
      // alert("set "+i+": "+Object.inspect(cnFilters[i]));
      setFilter(i,cnFilters[i]);
    }

    // TODO: update addTab form with latest categories from jsonURL

    // redraw map
    cnRedraw();    
}

function cnOnLoad(glat,glng,gZoomLevel,gMapType,filters,tb,sd) 
{
    // todo: firebug lite and fireunit
    // http://mattsnider.com/test/debugging-firebug-console-emulator/
    // http://ejohn.org/blog/fireunit/
    if (!(window.console && window.console.firebug)) {
      if (debugAlert) alert("cnOnLoad() no console, no log");
      debugFirebugCtrl = 0;
      debugFirebugData = 0;
      debugFirebugTimers = 0;
    }
    if (debugFirebugCtrl) console.log("cnOnLoad(%s,%s,%s,%s,%o) called", glat,glng,gZoomLevel,gMapType,filters);
    if (debugFirebugCtrl) console.log("cnLocations: %o", cnLocations);
    startms = new Date().getTime();

    // http://docs.jquery.com/UI/Tabs
    $("#tab-container").tabs();
    $("#tab-container").tabs().tabs('select', tb);

    curShowData = sd;

    if (!GBrowserIsCompatible()) 
    {
       document.getElementById("MapID").innerHTML = "Unfortunately your browser doesn't support Google Maps.<br /> To check browser compatibility visit the following <a href=\"http://local.google.com/support/bin/answer.py?answer=16532&topic=1499\">link</a>.";
       return;
    }
    map = new GMap2(document.getElementById("MapID"));
    //map.addControl(new GLargeMapControl()); // add zoom control with slider
    //map.addControl(new GMapTypeControl());  // add supported type control (map, satellite, hybrid) 
    map.setUIToDefault();
    // Center the map to the default location and set map type
    map.setCenter(new GLatLng(glat,glng), gZoomLevel, map.getMapTypes()[gMapType]);

    geocoder = new GClientGeocoder();

    GEvent.addListener(map, 'moveend', function(){mapMoved();});

    // set filters string to be processed after data is fetched
    cnFilters = filters;

    // setup jQuery 
    jqInit();

    MapLogo(map);
    MapJumpBox(map);

    // Add locations (async data call)
    getDataJQuery();    
    //getDataPrototype();    
}

function jqInit() 
{
  // from jquery.ui-1.6rc2.6/demos/real-world/splitpane/index.html
  if (debugFirebugCtrl) console.log("jqInit() called");

  $("#xxxx"+idRightC).resizable({
      handles: 'w',
      minWidth: 10,
      maxWidth: 400
  });
    //alert("w: "+$("#MapID").width() + ", h: "+$("#MapID").height());
    tabsInit();
}

function tabsInit() 
{
    prepareAdd();
    $("#aboutTab").html('  <a href="http://chadnorwood.com/about/" title="Read about Chad">Chad is Crafty</a>'+
                  "<br>"+ mapVersion+"<p><div id='debugConsole'><div>");
    $("#addTab ol li a:link").css({ "text-decoration": "underline"});
}

// Function is called to redraw map
function cnRedraw()
{
  if (debugFirebugCtrl) console.log(" . . . cnRedraw() called");
  redrawing=true;

  if ((curShowData !=0) && (!pendingJson['loaded'])) {
      if (debugFirebugCtrl) console.log(" . . . cnRedraw() triggering getPendingDataJQuery()");
      getPendingDataJQuery(); // trigger redraw with new point added
  }
  /* 
  cnLt = map.getCenter().lat();  // number
  cnLg = map.getCenter().lng();  // number
  //cnLtSW = cnGLatLngBounds.getSouthWest.lng();  // number
  cnLtSW = map.getBounds().getSouthWest().lat();
  cnLgSW = map.getBounds().getSouthWest().lng();
  cnLtNE = map.getBounds().getNorthEast().lat();
  cnLgNE = map.getBounds().getNorthEast().lng();
  str = '';
  str += " Loaded "+ cnLocations.length +" markers\n";
  str += " center: "+ cnLt +", "+ cnLg +"\n";
  str += "SW: "+ cnLtSW +", "+ cnLgSW +"\n";
  str += "NE: "+ cnLtNE +", "+ cnLgNE +"\n";
  //alert(str);
  */
  $("#MapStats").html("<span style='color:black; background:yellow;'> .. Updating Map .. </span>");

  var t1='total redraw';
  var t2='updateMarkers()';
  if (debugFirebugTimers) console.time(t1);
  if (debugFirebugTimers) console.time(t2);
  if (updateMarkers()) {
    if (debugFirebugTimers) console.timeEnd(t2);
    //updateList();
    //updateTabs();
    if (debugFirebugCtrl) console.log("cnRedraw() updated markers and tabs");
  } else {
    if (debugFirebugTimers) console.timeEnd(t2);
    if (debugFirebugCtrl) console.log("cnRedraw() updated markers (no changes)");
  }
  updateTabs(); // TODO: only update open tabs
  updateLink();
  if (debugFirebugTimers) console.timeEnd(t1);
  redrawing=false;
}

// called whenever map is moved .. zooms or is dragged
function mapMoved() 
{
    ems = elapsedMs();
    if (!map.getInfoWindow().isHidden()) {
        if (debugFirebugCtrl) console.log(ems+"mapMoved(), infowindow is open, not redrawing.");
        updateLink();
        return;
    }
    if (redrawing) {
        if (debugFirebugCtrl) console.log(ems+"mapMoved(), redrawing already in process.");
        return;
    }
    if (debugFirebugCtrl) console.log(ems+"mapMoved(), redrawing ...");
    cnRedraw();

      //console.log("sleeping for 2secs");
      //sleep(2000);
      //console.log("woke up !!");
}

// returns true if changes, false otherwise
function updateMarkers() 
{
    if (debugFirebugCtrl) console.log("updateMarkers() called ..");

    //
    // First remove all markers outside of map
    //
    str ='';
    newLocations = {0:{},1:{}},
    numNewLocations = 0;
    clearStats();

    ltSW = map.getBounds().getSouthWest().lat();
    lgSW = map.getBounds().getSouthWest().lng();
    ltNE = map.getBounds().getNorthEast().lat();
    lgNE = map.getBounds().getNorthEast().lng();

    if (debugFirebugTimers) {
        tjson = "checking all existing markers";
        console.time(tjson);
    }
    // cnLocations[0] contains approved landmarks
    for (var i in cnLocations[0]) {
      if (i.search(/^\d+$/) != 0) continue; // skip 'length'
      var kk = rawJson[1][i];
      if ( (curShowData != 1) 
            && cnContainsLatLng(kk['lt'], kk['lg'], ltSW,lgSW, ltNE,lgNE )
            && curFilters['curRating'][kk['r']] 
            && curFilters['curCatg'][kk['c']]) {
           stats['catg'][kk['c']]++;
           stats['rating'][kk['r']]++;
           newLocations[0][i] = cnLocations[0][i];
           numNewLocations++;
      } else {
        map.removeOverlay(cnLocations[0][i][0]);
      }
    }

    // cnLocations[1] contains pending landmarks, only shown during add process
    for (var i in cnLocations[1]) {
      if (i.search(/^\d+$/) != 0) continue; // skip 'length'
      var kk = pendingJson[1][i];
      if ( kk && (curShowData != 0) 
            && cnContainsLatLng(kk['lt'], kk['lg'], ltSW,lgSW, ltNE,lgNE )
            && curFilters['curRating'][kk['r']] 
            && curFilters['curCatg'][kk['c']]) {
           stats['catg'][kk['c']]++;
           stats['rating'][kk['r']]++;
           newLocations[1][i] = cnLocations[1][i];
           numNewLocations++;
      } else {
        map.removeOverlay(cnLocations[1][i][0]);
      }
    }
    if (debugFirebugTimers) console.timeEnd(tjson);

    removed = numMarkers - numNewLocations;
    cnLocations[0] = newLocations[0];
    cnLocations[1] = newLocations[1];
    numMarkers = numNewLocations;
    unchanged = numMarkers;
    newLocations = {0:{},1:{}},
    numNewLocations = 0;

    //
    // Second: scan json for acceptable items (coordinates inside map, match filters)
    //
    cnt = 0;
    for (var i in rawJson[1]) cnt++;

    if (debugFirebugTimers) {
        tjson = "checking "+cnt+" rawJson points";
        console.time(tjson);
    }
    if (curShowData != 1) { 
    for (var i in rawJson[1]) {
      try {
          if (i in cnLocations[0])  continue; // already added
          //if (cnLocations[0][i])  continue; // already added
      } catch (e) { }

      var kk = rawJson[1][i];
      //if (map.getBounds().containsLatLng(new GLatLng( kk['lt'], kk['lg'] ))) 
      //str = "checking ("+kk['lt']+", "+kk['lg']+"), inside\n NE("+ltNE+", "+lgNE+")\n SW("+ltSW+", "+lgSW+")";
      if (cnContainsLatLng(kk['lt'], kk['lg'], ltSW,lgSW, ltNE,lgNE )
            && curFilters['curRating'][kk['r']] 
            && curFilters['curCatg'][kk['c']]) {
           stats['catg'][kk['c']]++;
           stats['rating'][kk['r']]++;
           newLocations[0][i] = i; // we temporarily use newLocations this way, we addMarker() below
           numNewLocations++;
      } else {
         //alert(str + "\nOUTSIDE");
      }
    }
    } // curShowData

    if (curShowData != 0) { 
    for (var i in pendingJson[1]) {
      if (i in cnLocations[1])  continue; // already added

      var kk = pendingJson[1][i];
      if (cnContainsLatLng(kk['lt'], kk['lg'], ltSW,lgSW, ltNE,lgNE )
            && curFilters['curRating'][kk['r']] 
            && curFilters['curCatg'][kk['c']]) {
           stats['catg'][kk['c']]++;
           stats['rating'][kk['r']]++;
           newLocations[1][i] = i; // we temporarily use newLocations this way, we addMarker() below
           numNewLocations++;
      } else {
         //alert(str + "\nOUTSIDE");
      }
    }
    } // curShowData

    if (debugFirebugTimers) console.timeEnd(tjson);

    //
    // Third: create new markers using google calls
    //
    if (unchanged + numNewLocations > maxMarkers) {
      if (debugFirebugData) console.log(elapsedMs()+"only adding "+(maxMarkers-unchanged)
         + " of "+numNewLocations+" markers (max="+maxMarkers+")");
    } else {
      if (debugFirebugData) console.log(elapsedMs()+"about to add "+numNewLocations+" new markers ");
    }
    if (debugFirebugTimers) {
        tjson = "adding "+numNewLocations+" new markers to map";
        console.time(tjson);
    }
    str = '';
    mm = unchanged+1;
    added = 0;
    for (var i in newLocations[0]) {
        if (i.search(/^\d+$/) != 0) continue; // skip 'length'

        if (mm++ > maxMarkers) { 
          if (debugFirebugData) console.log("adding no more markers to map - Hit max ("+maxMarkers+") out of "+cnt);
          return (removed || added);
        }
        //if (debugFirebugData) console.log(elapsedMs()+"adding new marker "+ i);
        cnLocations[0][i] = addMarker(i,0);
        numMarkers++;
        added++;
        str += ' '+ i;
    }
    for (var i in newLocations[1]) {
        if (i.search(/^\d+$/) != 0) continue; // skip 'length'

        if (mm++ > maxMarkers) { 
          if (debugFirebugData) console.log("adding no more markers to map - Hit max ("+maxMarkers+") out of "+cnt);
          return (removed || added);
        }
        cnLocations[1][i] = addMarker(i,1);
        numMarkers++;
        added++;
        str += ' '+ i;
    }
    if (debugFirebugData) console.log("added new markers: "+ str);
    if (debugFirebugTimers) console.timeEnd(tjson);

    //console.log("cnLocations str was: %s", str);
    if (debugFirebugData) console.log("updateMarkers() %s removed, %s added, %s unchanged on map", removed, added,unchanged);

    return (removed || added);
}

// returns true if lat,lng is inside southwest and northeast coords lat1,lng1 and lat2,lng2}
function cnContainsLatLng(lat,lng, latS,lngW, latN,lngE ) 
{
       // need to handle north pole and south pole better

       //alert("checking ("+lat+", "+lng+"), inside\n NE("+latN+", "+lngE+")\n SW("+latS+", "+lngW+")");
       if (lat < latS) return false;
       if (lat > latN) return false;
       if (lng < lngW) return false;
       if (lng > lngE) return false;
       return true;
 }

// creates google marker and event listener and returns array obj 
function addMarker (i,t) 
{
    if (t==0) {
        if (!(i in rawJson[1])) {
          if (debugFirebugData) console.log("bad addMarker ("+i+",0)");
          return;
        }
        var kk = rawJson[1][i];
    } else { 
        if (!(i in pendingJson[1])) {
          if (debugFirebugData) console.log("bad addMarker ("+i+",1)");
          return;
        }
        var kk = pendingJson[1][i];
    }
    // validate lat and lng 
    point = new GLatLng( kk['lt'], kk['lg'] );

    //str = i+" id("+kk['id']+"): "+kk['n']+"\n"+ kk['lt'] +", "+ kk['lg'] ; 
    //alert(str);
    footer = '<div id="IWZoom"><a href="javascript:void(0)" onclick="ZoomTo('+i+','+t+')">Zoom To</a></div>'; 
    desc = "Star(s): "+ kk['r'] +" &nbsp; Category: "+ rawJson[0][kk['c']] + "<p>\n" + kk['d'];
    infoHTML = "<div class=\"IW\"><h1>"+ kk['n'] +"<\/h1><div id=\"IWContent\">"+ desc +"</div>"+footer+"</div>"; 

    if (t==1) {
      markerIcon = iconPend;
    } else {
      markerIcon = iconApproved;
    }
    marker = new GMarker(point, {icon:markerIcon});
    GEvent.addListener(marker, "click", function() {
      highlightItem(i,t);
    });
    map.addOverlay(marker);
             
    return new Array(marker, kk['n'],infoHTML); // create new cnLocations object
}

function dropMarker() 
{
    if (dMarker) removeDraggableMarker();

    $('#addFormMsg').html('');
    map.closeInfoWindow();
    dMarker = new GMarker(map.getCenter(), {draggable: true, icon: iconAdd});

    GEvent.addListener(dMarker, "dragstart", function() {
      map.closeInfoWindow();
    });
    GEvent.addListener(dMarker, "dragend", function() {
      dropMarkerIW();
    });
    map.addOverlay(dMarker);

    dropMarkerIW();
}

function dropMarkerIW()
{
    cntr = dMarker.getLatLng();
    lng = cntr.lng().toString().replace(/(\.\d\d\d\d\d\d)\d+/,"$1");
    lat = cntr.lat().toString().replace(/(\.\d\d\d\d\d\d)\d+/,"$1");
    $("#addTabLat").html(lat);
    $("#addTabLatH").val(lat);
    $("#addTabLng").html(lng);
    $("#addTabLngH").val(lng);
    if ($("#name").val().length > 0) $("#addForm").valid();

    zoom = '<div id="IWZoom"><a href="javascript:void(0)" onclick="ZoomTo(0,2)">Zoom To</a></div>'; 
    infoHTML = "<div class='IW'><h1>Adding New Landmark<\/h1><div id='IWContent'>Lat: "+lat+"<br>Long: "+lng+"<br>"+zoom+"</div><div id='IWaddr'><p>.<p></div>"; 
    dMarker.openInfoWindowHtml(infoHTML);

    geocoder.getLocations(cntr, function(addresses) {
      if(addresses.Status.code != 200) {
        $("#debugConsole").append("reverse geocoder failed to find an address for " + cntr.toUrlValue());
      } else { 
        var result = addresses.Placemark[0];
        $("#IWaddr").html("<p>Approximately: "+result.address+"<p>");
      }
    });
}
function removeDraggableMarker() 
{
    // remove listeners?
    map.removeOverlay(dMarker);
    dMarker=0;
}

function clearStats() 
{
    stats['catg'] = {};
    for (var i in rawJson[0]) {
        stats['catg'][i] = 0;
    }
    stats['rating'] = {};
    for (var i in curFilters['curRating']) {
        stats['rating'][i] = 0;
    }
}

// need to improve filters
function isLimited(fltr) 
{
      for (var i in curFilters[fltr]) {
        if (!curFilters[fltr][i]) return true; 
      }
      return false;
}

function setFilter (fltr,key) 
{
      for (var i in curFilters[fltr]) {
        if (key == 'all') {
          curFilters[fltr][i] = true;
        } else {
          curFilters[fltr][i] = false;
        }
      }
      if (key == 'all') return;
      curFilters[fltr][key] = true;
}

// returns key that is true
function getFilter (fltr) 
{
      if (!isLimited(fltr)) return 'all';
      for (var i in curFilters[fltr]) {
        if (curFilters[fltr][i]) return i;
      }
      return 'all';
}

function getMapType(oMap)
{
  mm = oMap.getMapTypes().length;
  for (nn = 0; nn < mm; nn++){
    if (oMap.getMapTypes()[nn] == oMap.getCurrentMapType())
      return nn;
  }
  return -1;
}

function updateLink() 
{
      // create link
      clink = '/map/?z='+ map.getZoom();
      clink += '&lat='+ map.getCenter().lat().toString().replace(/(\.\d\d\d\d\d\d)\d+/,"$1");
      clink += '&lng='+ map.getCenter().lng().toString().replace(/(\.\d\d\d\d\d\d)\d+/,"$1");
      clink += '&m='+ getMapType(map);         // number
      fltrs = {'curRating':'r', 'curCatg':'c'};
      for (f in fltrs) {
        if (isLimited(f)) {
          clink += '&'+ fltrs[f] +'='+ getFilter(f);
        }
      }
      if (curShowData == 0) {
          totalMarkers = stats['rawJsonLength'];
      } else if (curShowData == 1) {
          totalMarkers = stats['pendingJsonLength'];
      } else if (curShowData == 2) {
          totalMarkers = stats['rawJsonLength'] + stats['pendingJsonLength'];
      }
      clink += '&tb='+ $('#tab-container').tabs().tabs('option', 'selected'); // => 0
      if (curShowData != 0) clink += '&sd='+ curShowData;

      summaryHtml = "Showing "+ numMarkers +" of "+ totalMarkers;
      //summaryHtml += " (<a href='javascript:void(0)' onclick='cnRedraw()'>redraw</a>)";
      summaryHtml += " &nbsp; (<a href='"+clink +"' title='Link for this specific page'>Link</a>)<br>\n";
      //document.getElementById("MapStats").innerHTML = summaryHtml;
      $("#MapStats").html(summaryHtml);
}

function updateFiltersTab() 
{
    newHtml = getFiltersHTML();
    //newHtml += "<p>\nCenter: "+ map.getCenter().lat() +", "+ map.getCenter().lng();
    $("#filtersTab").html(newHtml);
    updateDataFilters();   
}

function getFiltersHTML() 
{
      //newHtml = "Stats: <p>";
      newHtml = "<p>";
      ratingTotal = 0;
      catgTotal = 0;
      rHtml = '';
      for (var i in stats['rating']) {
        if ((!isLimited('curRating')) || (stats['rating'][i] > 0)) {
          rHtml += '<a href="javascript:void(0)" onclick="setFilter(\'curRating\', \''+i+'\'); cnRedraw()"';
          rHtml += ' title="Redraw showing only '+ i +' stars">';
          rHtml += "["+ stats['rating'][i] + "] "+ i + " stars</a>  &nbsp;\n";
          ratingTotal += stats['rating'][i]; 
      } }
      if (isLimited('curRating')) {
        newHtml += "<p>Showing only: "+rHtml+"<br>\n";
        newHtml += '<a href="javascript:void(0)" onclick="setFilter(\'curRating\', \'all\'); cnRedraw()"';
        newHtml += ' title="Redraw showing All Ratings">';
        newHtml += "Show All Ratings on Map</a>\n";
      } else {
        newHtml += "<p>Showing All Ratings on Map.<br>Show only:\n<br>"+rHtml+"<br>\n";
      }
      rHtml = '';
      for (var i in stats['catg']) {
        if (stats['catg'][i] > 0) {
          rHtml += '<br><a href="javascript:void(0)" onclick="setFilter(\'curCatg\', \''+i+'\'); cnRedraw()"';
          rHtml += ' title="Redraw showing only category: '+ rawJson[0][i] +'">';
          rHtml += " ["+ stats['catg'][i] + "] "+ rawJson[0][i] +"</a>  &nbsp;\n";
          catgTotal += stats['catg'][i]; 
      } }
      if (isLimited('curCatg')) {
        newHtml += "<p>Showing only: "+rHtml+"<br>\n";
        newHtml += '<a href="javascript:void(0)" onclick="setFilter(\'curCatg\', \'all\'); cnRedraw()"';
        newHtml += ' title="Redraw showing All Categories">';
        newHtml += "Show All Categories on Map</a>\n";
        //newHtml += "Show All Categories on Map</a>\n<br>Showing only "+rHtml+"<br>\n";
      } else {
        newHtml += "<p>Showing All Categories on Map\n<br>Show only:"+rHtml+"<br>\n";
      }
      //newHtml += "<br><a href='"+clink +"'>link directly to map</a> (created after <a href='javascript:void(0)' onclick='cnRedraw()'>redraw</a>)<br>\n";

/*
      newHtml += "<ul>";
      newHtml += "<li><a href='#'><img src="+ urlIconApproved+"> Approved Landmarks Only</a><li>";
      newHtml += "<li><a href='#'><img src="+ urlIconPend+"> Pending Landmarks Only</a><li>";
      newHtml += "<li><a href='#'><img src="+ urlIconApproved+"> <img src="+ urlIconPend+"> Approved and Pending Landmarks</a><li>";
      newHtml += "</ul>";
*/
      curFilters['curCatg'][i] = true;
      newHtml += "\n<p>";
      for (i in dataTypeText) {
          newHtml += "<a href='javascript:void(0)' id='showData"+i+"' onclick='curShowData="+i+"; cnRedraw();'>"+dataTypeText[i]+"</a><br>";
          //newHtml += "<a href='#' class='ui-button ui-state-default ui-corner-all'>Approved Landmarks Only</a><br>";
      }
      return newHtml;
}

function updateDataFilters()
{

    $("#showData"+curShowData).prepend("Showing ");
//        .addClass("ui-button ui-state-active ui-corner-all");

    $("#showData0").append(" ["+stats['rawJsonLength']+"]");
    $("#showData1").append(" ["+stats['pendingJsonLength']+"]");
    $("#showData2").append(" ["+(stats['rawJsonLength']+stats['pendingJsonLength'])+"]");

  //  $("#showData"+i).click(function () { dummy=0; });
    for (i=0;i<3;i++) {
        if (i==curShowData) continue; 
        $("#showData"+i).click(function () { curShowData=i; cnRedraw(); });
        $("#showData"+i).prepend("Show: ");
    }
}

// getFilteredHTML() is displayed above results so you know they are filtered
function getFilteredHTML() 
{
    newHtml = '';
    if (isLimited('curRating')) {
      for (var i in stats['rating']) {
        if (stats['rating'][i] > 0) {
          newHtml = "Showing only "+i+" stars ";
      } }
      newHtml += '<a href="javascript:void(0)" onclick="setFilter(\'curRating\', \'all\'); cnRedraw()"';
      newHtml += ' title="Redraw showing All Ratings">'; 
      newHtml += "[X]</a>";
    }
    if (isLimited('curCatg')) {
      for (var i in stats['catg']) {
        if (stats['catg'][i] > 0) {
          newHtml += '<br>Showing only '+ rawJson[0][i];
      } }
      newHtml += ' <a href="javascript:void(0)" onclick="setFilter(\'curCatg\', \'all\'); cnRedraw()"';
      newHtml += ' title="Redraw showing All Categories">';
      newHtml += "[X]</a>";
      //newHtml += "Remove Filter</a>)";
    }
    if (curShowData!=0) {
      newHtml += "<br>Showing "+dataTypeText[curShowData]+" <a href='javascript:void(0)' onclick='curShowData=0; cnRedraw();'";
      newHtml += ' title="Redraw Showing '+ dataTypeText[0] +'">[X]</a><br>';
    }
    return newHtml;
}

function elapsedMs() 
{
    ems = new Date().getTime() - startms;
    ems +=  "ms ";
    return ems;
}


function sleeper(msDelay)
{
    var start = new Date().getTime();
    while (new Date().getTime() < start + msDelay);
}


// Zoom map to. Used from InfoWindow
function ZoomTo(num,type) 
{
  var currentZoom = map.getZoom();
  newZoom = (currentZoom >= 19) ? 19 : (currentZoom + 1);
  if (type==2) {
    //newZoom = (newZoom < 15) ? 15 : newZoom;
    map.setCenter(dMarker.getLatLng(), newZoom);
  } else {
    map.setCenter(cnLocations[type][num][0].getLatLng(), newZoom);
  }
  updateLink();
}

// Called when user clicks on map marker or clicks on name in results tab
// Opens info window for marker and highights name in results tab
function highlightItem(i,t) 
{
    ems = elapsedMs();
    //$("#rightC").append(mhtml);
    if (debugFirebugCtrl) console.log(ems+ "highlightItem(%s) begins", i);

    // remove any previous highlights
    $("#"+idRightC+" a").removeClass("highlight2"); 
    //$("td a").removeClass("highlight2"); 
//    each(function(){
 //     $(this).removeClass("highlight"); 
  //  });

    // highlight item in table where href=i
    //$("#"+idRightC+" a[href*=("+i+")]").parent().addClass("highlight2"); 
    $("#"+idRightC+" a[href*=("+i+","+t+")]").addClass("highlight2"); 

    // open info window for item
    cnLocations[t][i][0].openInfoWindowHtml(cnLocations[t][i][2]);

    map.panTo(cnLocations[t][i][0].getLatLng());
}

function updateTabs() 
{
    // todo: for speed, only update tab that is open
    // 
    // http://docs.jquery.com/UI/Tabs
    // $tabs = $("#tab-container").tabs();

    if (debugFirebugTimers) {
      t2 = 'updateTabs()';
      console.time(t2);
    }
    updateResultsTab();
    updateFiltersTab();
    updateAddTab();
    updateAboutTab();
    if (debugFirebugTimers) console.timeEnd(t2);
}

function updateAddTab() 
{
  //$("#addTablon").html('43');
}

function updateAboutTab() 
{
  getDebugStats();
}

function getDebugStats() 
{
    cntr = map.getCenter(); 
    $("#debugConsole").html("<p>\nCenter: "+cntr.lat()+", "+cntr.lng()+"<br>");

    geocoder.getLocations(cntr, function(addresses) {
      if(addresses.Status.code != 200) {
        $("#debugConsole").append("reverse geocoder failed to find an address for " + cntr.toUrlValue());
      } else { 
        var result = addresses.Placemark[0];
        $("#debugConsole").append(result.address);
        if (jumptxt == '') SetJumpBoxText(result.address);
      }
    });
}

function updateResultsTab() 
{
  idTable = idRightC + 'Table';
 
  filterHTML = getFilteredHTML();
  str='';
  $("#"+idRightC).html(filterHTML+"<table id='"+idTable+"' class='tablesorter'><thead><tr><th title='Rating'><nobr>R &nbsp; &nbsp;</nobr> </th><th>Name</th><th>Category</th></tr></thead><tbody>\n");
  for (j=0; j<2; j++) {
  for (var i in cnLocations[j]) {
    if (i.search(/^\d+$/) != 0) continue; // skip 'length'
    str += i+' ';

    var kk = (j==0) ? rawJson[1][i] : pendingJson[1][i];

    //str = i+" id("+kk['id']+"): "+kk['n']+"\n"+ kk['lt'] +", "+ kk['lg'] ; 

    //rowHTML = "<tr><td>"+kk['r']+"</td><td class='highlight2>";
    rowHTML = "<tr><td>"+kk['r']+"</td><td>";

    //rowHTML += kk['n']+"</td><td>";
    rowHTML += '<a href="javascript:highlightItem('+i+','+j+')">'+kk['n']+"</a></td><td>";

    //rowHTML += kk['c']+"</td></tr>\n";
    rowHTML += rawJson[0][kk['c']].replace(/\//g, ", ") +"</td></tr>\n";
    $("#"+idRightC +" table").append(rowHTML);
  }
  }

/*  could not get this too work
  // http://www.webdesignerwall.com/tutorials/jquery-tutorials-for-designers/  (6. Entire block clickable)
  $("."+ classlist+" li").click(function(){
    console.log("clicked !!!");
    return false;
  });
    console.log("clicked %o ", $(this).find("a").attr("href"));
    i=$(this).find("a").attr("href"); 
    highlightItem(i);
  });
*/
  if (debugFirebugData) console.log("updateResultsTab() added "+str);

  // requires jquery.tablesorter.js
  $("#"+idTable).tablesorter(); 

  // highlight columns or rows:
  // http://p.sohei.org/stuff/jquery/tablehover/demo/demo.html#
}


function JumpToAddress(address) {

    // if its a street address, we want to zoom in more than if city or country
    // assuming street address if address contains a comma
    if (address.search(/,/) == -1) {cZoom=11;} else {cZoom=16;}

    map.closeInfoWindow();
    geocoder.getLatLng( address, function(point) {
        if (!point) {
          //alert(address + " not found");
          SetJumpBoxText("NOT FOUND: "+ address);
        } else {
          jumptxt = '';
          map.setCenter(point, cZoom);
          //SetJumpBoxText("Jumped to: "+ address);  // set by reverse lookup

          //var marker = new GMarker(point);
          //map.addOverlay(marker);
          //marker.openInfoWindowHtml(address);
        }
    });
}

function SetJumpBoxText(txt) {
  // focus on something else so when user clicks on jumpBox again, it will clear
  $("#LogoInfo").focus;
  jumptxt = txt;
  $("#jumpBox").val(txt);
}

function MapJumpBox(oMap)
{ 
    /* Insert Search Box on Map */
    var info=document.createElement('div');
    //info.id='SearchBox';
    info.style.position='absolute';
    info.style.right='7px';
    info.style.top='30px';
    //info.style.backgroundColor='transparent';
    info.style.zIndex=25500;
 		//info.innerHTML='<input id="gotoBox" type="text" size=35 onkeypress="handleKeyPress(event,\'#goToAddress\')" value ="'+jumptxt+'" onfocus="if (this.value == \''+jumptxt+'\') {this.value = \'\';}" onblur="if (this.value == \'\') {this.value = \''+jumptxt+'\';}" />';
 		info.innerHTML='<form action="#" onsubmit="JumpToAddress(this.address.value); return false">'+
'<input id="jumpBox" type="text" size=35 name="address" value ="'+jumptxt+'" onfocus="if (this.value == \''+jumptxt+'\') {this.value = \'\';}" onblur="if (this.value == \'\') {this.value = \''+jumptxt+'\';}" />' + '</form>';

    oMap.getContainer().appendChild(info);
}

function MapLogo(oMap)
{ 
    /* Insert Logo on Map */
    var info=document.createElement('div');
    info.id='LogoInfo';
    info.style.position='absolute';
    info.style.right='4px';
    info.style.bottom='20px';
    info.style.backgroundColor='transparent';
    info.style.zIndex=25500;
 		info.innerHTML='<a href="http://chadnorwood.com/" title="Powered by Craft Beer"><img src="http://chadnorwood.com/beermug.ico" style="border:0; margin: 2px;"/></a>';

    oMap.getContainer().appendChild(info);
}

function prepareAdd()
{
    // http://docs.jquery.com/Plugins/Validation
    // http://www.ferdychristant.com/blog//articles/DOMM-7LZJN7
    $("#addForm").validate({ debug:true, onkeyup:false, 
      rules: {
        name: { required:true, minlength:5, maxlength:99, validChars:true },
        longitude: { required:true },
        email: { required:true, minlength:5, maxlength:99, validChars:true }
      }, 
      messages: {
        longitude: { required: "Must Drop a Marker" },
        name: { 
          required: "Name is required.",
          minlength: jQuery.format("Name must be at least {0} characters in length."),
          maxlength: jQuery.format("Name can not exceed {0} characters in length."),
          validChars: "Please supply valid characters only." }
      },
      submitHandler: function(form) {
        if (debugFirebugCtrl) console.log("submitHandler: %o", form);
        //$(form).ajaxSubmit();
        submitAdd();
      }
    });

    $.validator.addMethod('validChars', function (value) {
        //if (debugFirebugCtrl) console.log("validChars("+value+")");
        // unwanted characters
        var iChars = "!#$%^&*()=-[]\\\;/{}|:<>?";
        for (var i = 0; i < value.length; i++) {
            if (iChars.indexOf(value.charAt(i)) != -1) {
                return false;
            }
        }
        return true;
    }, '');

    //$('#asubmit').click(function () { return submitAdd(); });
        
   if (debugFirebugCtrl) console.log("prepareAdd() done");
}

function resetAddForm()
{
    //$('#addForm').each(function(){  this.val(''); });
    $(':input','#addForm').not(':button, :submit, :reset, :hidden')
        .val('').removeAttr('checked').removeAttr('selected');

    removeDraggableMarker();
    $("#addTabLat").html(''); 
    $("#addTabLng").html(''); 
}
function submitAdd()
{ 
    dataStr = "name="+ $("input#name").val(); 
    dataStr += "&email="+ $("#addTabEmail").val(); 
    dataStr += "&lat="+ $("#addTabLat").html(); 
    dataStr += "&lng="+ $("#addTabLng").html(); 
    dataStr += "&rating="+ $("#addTabRating").val(); 
    dataStr += "&category="+ $("#addTabCategory").val(); 
    dataStr += "&description="+ $("#addTabDescription").val(); 

    $('#addFormMsg').html("<h2>.. submitting location ..</h2>");
            
    if (debugFirebugCtrl) console.log("add form submit: %s",dataStr);

    $.getJSON( addURL+"?"+dataStr, function(data){
        if (data.count < 1) {
            $('#addFormMsg').html("<h2>Error: Could not connect, try again later.</h2>");
           return;
        }
        if (data[0] == 'error') {
            $('#addFormMsg').html("<h2>"+data[1]+"</h2>");
           return;
        }
        if (data[0] == 'success') {
            resetAddForm();
            $('#addFormMsg').html("<h2>Location Submitted Successfully.</h2>");
            curShowData=2;
            getPendingDataJQuery(); // trigger redraw with new point added
        } else {
            $('#addFormMsg').html("<h2>Error: Could not add, try again later.</h2>");
        }
    });

/*
    $.ajax({  
        type: "GET",  
        url: addURL,
        data: dataStr,  
        success: function(msg) {  
            $('#addFormMsg').html("<h2>"+msg+"</h2>");
            if (debugFirebugCtrl) console.log("add form response:%o", msg);
            .append("<p>We will be in touch soon.</p>")  
            .hide()  
            .fadeIn(1500, function() {  
                $('#message').append("<img id='checkmark' src='images/check.png' />");  
            });  
            return false;  
        }  
    });  
*/
    return false;  
}


