function toddEncodeJSON(data)
{
  switch(typeof data)
  {
    case 'boolean':
      return data ? 'true' : 'false';
    case 'object':
      if(data==null)
        return'null';

      if(data instanceof Array)
      {
        var retval='[';
        for (var i=0;i<data.length;++i)
          retval+=(i>0?',':'')+toddEncodeJSON(data[i]);
        return retval+']';
      }

      var retval='{';
      for (cellname in data) //ADDME safari 1.3 fallback for hasownproperty?!
        if(!data.hasOwnProperty || data.hasOwnProperty(cellname))
          retval += (retval.length>1?',':'') + '"' + cellname + '":' + toddEncodeJSON(data[cellname]);
      return retval+'}';
    case 'number':
      return data.toString();
    case 'string':
      if (/["\\\x00-\x1f]/.test(data))
      {
          return '"' + data.replace(/[\x00-\x1f\\"]/g, function (a)
          {
              var c = a.charCodeAt();
              return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
          }) + '"';
      }
      return '"' + data + '"';
    default:
      return alert("toddEncodeJSON: How to encode " + typeof data + "?");
  }
}


///////////////////////////////////////////////////////////////////////////////
// Map management

var map;
var infowindow;
var lastbounds;
var xhr;

function LoadMap(center)
{
  if (window.XMLHttpRequest)
    xhr = new XMLHttpRequest();
  else if (window.ActiveXObject)
    xhr = new ActiveXObject("Microsoft.XMLHTTP");
  xhr.onreadystatechange = XHR_StateChanged;

  map = toddGM_Initialize("map_canvas", { maptype: "map"
                                        , center: center
                                        , zoom: 12
                                        , moveable: true
                                        , iconsize: 48
                                        , icons: [ { name: "vw"
                                                   , icon: "/design/images/maps/vw.png"
//                                                   , shadow: "pin_shadow.48x48.png"
                                                   , anchor_x: 21
                                                   , anchor_y: 21
                                                   , label_x: 21
                                                   , label_y: 21
                                                   , popup_x: 19
                                                   , popup_y: 7
                                                   }
                                                 ]
                                        , OnInitialized: MapInitialized
                                        , OnMoveEnd: XHR_UpdateOverlays
                                        , OnZoomEnd: XHR_UpdateOverlays
                                        , OpenInfoWindow: OpenInfoWindow
                                        , CloseInfoWindow: CloseInfoWindow
                                        });
}

function UnloadMap()
{
  toddGM_DeInit(map);
}

function MapInitialized()
{
  map.map.addControl(new vw_ZoomControl());
  map.map.addControl(new vw_FullscreenControl());
  infowindow = new vw_InfoWindow();
  map.map.addOverlay(infowindow);

  XHR_UpdateOverlays();
}

function XHR_UpdateOverlays(force)
{
  var bounds = toddGM_BoundsToString(map.GetMapIconBounds());
  if (bounds == lastbounds && !force)
    return;

  lastbounds = bounds;

  var request = { type: "updateoverlays"
                , bounds: bounds
                , zoom: map.map.getZoom()
                };

  xhr.onreadystatechange = function(){};
  xhr.abort();
  xhr.open("POST", "map.shtml", true);
  xhr.onreadystatechange = XHR_StateChanged;
  xhr.send(toddEncodeJSON(request));
}

function XHR_StateChanged()
{
  if (xhr.readyState == 4 && xhr.status == 200)
  {
    var response = eval('(' + xhr.responseText + ')');
    if (response.success)
      map.UpdateAllOverlays(response.overlays);
  }
}

function OpenInfoWindow(overlay)
{
  if (overlay.infohtml)
  {
    infowindow.Open(overlay);
  }
}

function CloseInfoWindow()
{
  infowindow.Close();
}


///////////////////////////////////////////////////////////////////////////////
// Info window

function vw_InfoWindow(overlay)
{
  this.edges = new Object();
  this.overlay = overlay;
}
vw_InfoWindow.prototype = new google.maps.Overlay();

vw_InfoWindow.prototype.initialize = function vw_InfoWindow_initialize(map)
{
  this.map = map;

  this.node = document.createElement("div");
  this.node.className = "toddInfoWindow";

  // All elements are absolute positioned, so in order to avoid having to use z-indices, first add the content div and then
  // position the borders on top of it
  this.contentnode = document.createElement("div");
  this.contentnode.className = "toddInfoWindow-contents";

  // The actual content holder
  this.contents = document.createElement("span");
  this.contents.className = "toddExtFontSettings";
  this.contents.appendChild(document.createTextNode("\xA0"));
  this.contentnode.appendChild(this.contents);

  this.node.appendChild(this.contentnode);

  // Top border
  this.edges.nw = document.createElement("div");
  this.edges.nw.className = "toddInfoWindow-nw";
  this.edges.nw.appendChild(document.createTextNode("\xA0"));
  this.node.appendChild(this.edges.nw);
  this.edges.n = document.createElement("div");
  this.edges.n.className = "toddInfoWindow-n";
  this.edges.n.appendChild(document.createTextNode("\xA0"));
  this.node.appendChild(this.edges.n);
  this.edges.ne = document.createElement("div");
  this.edges.ne.className = "toddInfoWindow-ne";
  this.edges.ne.appendChild(document.createTextNode("\xA0"));
  this.node.appendChild(this.edges.ne);

  // Left and right borders
  this.edges.w = document.createElement("div");
  this.edges.w.className = "toddInfoWindow-w";
  this.edges.w.appendChild(document.createTextNode("\xA0"));
  this.node.appendChild(this.edges.w);
  this.edges.e = document.createElement("div");
  this.edges.e.className = "toddInfoWindow-e";
  this.edges.e.appendChild(document.createTextNode("\xA0"));
  this.node.appendChild(this.edges.e);

  // Bottom border
  this.edges.sw = document.createElement("div");
  this.edges.sw.className = "toddInfoWindow-sw";
  this.edges.sw.appendChild(document.createTextNode("\xA0"));
  this.node.appendChild(this.edges.sw);
  this.edges.s = document.createElement("div");
  this.edges.s.className = "toddInfoWindow-s";
  this.edges.s.appendChild(document.createTextNode("\xA0"));
  this.node.appendChild(this.edges.s);
  this.edges.se = document.createElement("div");
  this.edges.se.className = "toddInfoWindow-se";
  this.edges.se.appendChild(document.createTextNode("\xA0"));
  this.node.appendChild(this.edges.se);

  // Info window tail
  this.edges.tail = document.createElement("div");
  this.edges.tail.className = "toddInfoWindow-tail";
  this.edges.tail.appendChild(document.createTextNode("\xA0"));
  this.node.appendChild(this.edges.tail);

  if (this.overlay)
    this.Open(this.overlay);
}

vw_InfoWindow.prototype.redraw = function vw_InfoWindow_redraw(force)
{
  if (this.overlay)
  {
    // Reposition the info window
    var iconanchor = this.overlay.marker.getIcon().iconAnchor;
    var infoanchor = this.overlay.marker.getIcon().infoWindowAnchor;

    // Calculate position relative to icon and overlay anchors
    var pos = this.map.fromLatLngToDivPixel(this.overlay.marker.getPoint());
    pos.y = pos.y - this.contentheight - 22/*padding + tail height*/ - iconanchor.y + infoanchor.y;
    pos.x = pos.x - 27/*tail left*/ - iconanchor.x + infoanchor.x;

    // Set position
    this.node.style.top = pos.y + "px";
    this.node.style.left = pos.x + "px";

    if (force)
    {
      // Get position relative to map container
      pos = this.map.fromLatLngToContainerPixel(this.overlay.marker.getPoint());
      pos.y = pos.y - this.contentheight - 22/*padding + tail height*/ - iconanchor.y + infoanchor.y;
      pos.x = pos.x - 27/*tail left*/ - iconanchor.x + infoanchor.x;

      // Move map to get info window in view
      var mapsize = this.map.getSize();
      var movex = 0;
      var movey = 0;
      if (pos.x < 6)
        movex = 6 - pos.x;
      else if (pos.x > mapsize.width - this.contentwidth - 17)
        movex = mapsize.width - this.contentwidth - 17 - pos.x;
      // Leave some room above the info window for the button controls
      if (pos.y < 40)
        movey = 40 - pos.y;
      else if (pos.y > mapsize.height - 53)
        movey = mapsize.height - 53 - pos.y;

      if (movex != 0 || movey != 0)
        this.map.panBy({ width: movex, height: movey });
    }
  }
}

vw_InfoWindow.prototype.remove = function vw_InfoWindow_remove()
{
  // Clean up
  google.maps.Event.clearListeners(this.node);
  if (this.node.parentNode)
    this.node.parentNode.removeChild(this.node);
  this.overlay = null;
}

vw_InfoWindow.prototype.copy = function vw_InfoWindow_copy()
{
  return new vw_InfoWindow(this.overlay);
};

// Open the info window for a given overlay.
vw_InfoWindow.prototype.Open = function vw_InfoWindow_Open(overlay)
{
  this.overlay = overlay;

  this.map.getPane(G_MAP_FLOAT_PANE).appendChild(this.node);

  var self = this;
  google.maps.Event.addDomListener(this.node, "mousedown", function(e) { return self.OnClick(e, false); });
  google.maps.Event.addDomListener(this.node, "dblclick", function(e) { return self.OnClick(e, false); });
  google.maps.Event.addDomListener(this.node, "click", function(e) { return self.OnClick(e, true); });

  this.OverlayUpdated();
};

// Call this function if the infohtml value of an overlay has been changed. This will update the contents and reposition
// the info window if necessary.
vw_InfoWindow.prototype.OverlayUpdated = function vw_InfoWindow_OverlayUpdated()
{
  if (this.overlay)
  {
    this.contents.innerHTML = this.overlay.infohtml;
    this.Relayout();
  }
};

vw_InfoWindow.prototype.Relayout = function vw_InfoWindow_Relayout()
{
  // Reset height before calculating new height
  this.contentnode.style.height = "";
  this.contentwidth = this.contentnode.offsetWidth;
  this.contentheight = this.contentnode.offsetHeight;
  if (this.contentheight != 34) // minimum height
  {
    this.contentheight = 34;
    this.contentnode.style.height = this.contentheight + "px";
  }

  // Set border sizes
  this.edges.n.style.width = this.contentwidth + "px";
  this.edges.s.style.width = this.contentwidth + "px";
  this.edges.w.style.height = this.contentheight + "px";
  this.edges.e.style.height = this.contentheight + "px";

  // Position right borders
  this.edges.ne.style.left = (this.contentwidth + 6/*border width*/) + "px";
  this.edges.e.style.left = (this.contentwidth + 6/*border width*/) + "px";
  this.edges.se.style.left = (this.contentwidth + 6/*border width*/) + "px";

  // Position bottom borders
  this.edges.sw.style.top = (this.contentheight + 7/*border width*/) + "px";
  this.edges.s.style.top = (this.contentheight + 7/*border width*/) + "px";
  this.edges.se.style.top = (this.contentheight + 7/*border width*/) + "px";
  this.edges.tail.style.top = (this.contentheight + 12/*2*border width*/) + "px";

  this.redraw(true);
}

// Close the info window.
vw_InfoWindow.prototype.Close = function vw_InfoWindow_Close(e)
{
  this.remove();
}

vw_InfoWindow.prototype.OnClick = function vw_InfoWindow_OnClick(e, shouldclose)
{
  // Cancel the event, so it will not be handled by the map as well
  if (!e)
    e = window.event;
  if(e && e.stopPropagation)
  {
    e.stopPropagation();
  }
  else
  {
    window.event.cancelBubble = true;
    window.event.returnValue = false;
  }

  // Close the info window, if requested
  if (shouldclose)
    this.Close();

  return false;
}


///////////////////////////////////////////////////////////////////////////////
// Zoom control

function vw_ZoomControl()
{
  this.buttons = new Object;
}
vw_ZoomControl.prototype = new google.maps.Control();

// Called by google.maps.Map2.addControl()
vw_ZoomControl.prototype.initialize = function vw_ZoomControl_initialize(map)
{
  this.map = map;

  // Create a container for our buttons
  this.node = document.createElement("div");
  this.node.style.textAlign = "center";

  var self = this;
  // Create our buttons buttons and add them to the container
  this.buttons.zoomin = this.CreateButton("zoomin", function(e) { self.ZoomIn(e); }, this.node);
  this.buttons.zoomout = this.CreateButton("zoomout", function(e) { self.ZoomOut(e); }, this.node);

  // Insert our button container in the map controls container
  this.map.getContainer().appendChild(this.node);
  return this.node;
}

// Called by google.maps.Map2.addControl()
vw_ZoomControl.prototype.getDefaultPosition = function vw_ZoomControl_getDefaultPosition()
{
  // Position this control at the top left corner, at 8 pixels from the map's edge
  return new google.maps.ControlPosition(G_ANCHOR_TOP_LEFT, new google.maps.Size(8, 6));
}

vw_ZoomControl.prototype.DeInit = function vw_ZoomControl_DeInit()
{
  for (var b in this.buttons)
    if (this.buttons[b])
      google.maps.Event.clearInstanceListeners(this.buttons[b]);
}

vw_ZoomControl.prototype.CreateButton = function vw_ZoomControl_CreateButton(buttonimage, callback, parent)
{
  var button = document.createElement("img");
  button.src = "/design/images/maps/" + buttonimage + ".png";
  button.width = 30;
  button.height = 32;

  button.map = this.map;
  button.style.cursor = "pointer";
  google.maps.Event.addDomListener(button, "click", callback);
  parent.appendChild(button);
  return button;
}

vw_ZoomControl.prototype.ZoomIn = function vw_ZoomControl_ZoomIn()
{
  this.map.zoomIn();
}

vw_ZoomControl.prototype.ZoomOut = function vw_ZoomControl_ZoomOut()
{
  this.map.zoomOut();
}


///////////////////////////////////////////////////////////////////////////////
// Fullscreen control

function vw_FullscreenControl()
{
  this.buttons = new Object;
  this.fullscreen = false;
  this.fullscreencontainer = null;
}
vw_FullscreenControl.prototype = new google.maps.Control();

// Called by google.maps.Map2.addControl()
vw_FullscreenControl.prototype.initialize = function vw_FullscreenControl_initialize(map)
{
  this.map = map;

  // Create a container for the fullscreen view
  this.fullscreencontainer = document.createElement("div");
  this.fullscreencontainer.style.position = "absolute";
  this.fullscreencontainer.style.top = "0";
  this.fullscreencontainer.style.left = "0";
  this.fullscreencontainer.style.display = "none";
  this.fullscreencontainer.style.zIndex = "1000";
  document.body.appendChild(this.fullscreencontainer);

  // Create a container for our buttons
  this.node = document.createElement("div");
  this.node.style.textAlign = "center";

  var self = this;
  // Create our buttons buttons and add them to the container
  this.buttons.fullscreen = this.CreateButton("fullscreen", function(e) { self.Fullscreen(e); }, this.node);

  // Insert our button container in the map controls container
  this.map.getContainer().appendChild(this.node);
  return this.node;
}

// Called by google.maps.Map2.addControl()
vw_FullscreenControl.prototype.getDefaultPosition = function vw_FullscreenControl_getDefaultPosition()
{
  // Position this control at the top left corner, at 8 pixels from the map's edge
  return new google.maps.ControlPosition(G_ANCHOR_TOP_RIGHT, new google.maps.Size(8, 6));
}

vw_FullscreenControl.prototype.DeInit = function vw_FullscreenControl_DeInit()
{
  for (var b in this.buttons)
    if (this.buttons[b])
      google.maps.Event.clearInstanceListeners(this.buttons[b]);
}

vw_FullscreenControl.prototype.CreateButton = function vw_FullscreenControl_CreateButton(buttonimage, callback, parent)
{
  var button = document.createElement("img");
  button.src = "/design/images/maps/" + buttonimage + ".png";
  button.width = 32;
  button.height = 32;

  button.map = this.map;
  button.style.cursor = "pointer";
  google.maps.Event.addDomListener(button, "click", callback);
  parent.appendChild(button);
  return button;
}

vw_FullscreenControl.prototype.Fullscreen = function vw_FullscreenControl_Fullscreen()
{
  var map_canvas = document.getElementById("map_canvas");
  var map_container = document.getElementById("map_container");

  this.fullscreen = !this.fullscreen;
  if (this.fullscreen)
  {
    this.scrollTop = { bodyTop: document.body.scrollTop
                     , htmlTop: document.documentElement.scrollTop
                     , bodyLeft: document.body.scrollLeft
                     , htmlLeft: document.documentElement.scrollLeft
                     };
    document.body.scrollTop = 0;
    document.documentElement.scrollTop = 0;
    document.body.scrollLeft = 0;
    document.documentElement.scrollLeft = 0;

    var latlng = this.map.getCenter();
    this.fullscreencontainer.style.width = (document.documentElement.clientWidth) + "px";
    this.fullscreencontainer.style.height = (document.documentElement.clientHeight) + "px";
    this.fullscreencontainer.style.display = "block";
    this.fullscreencontainer.appendChild(map_canvas);
    this.map.checkResize();
    this.map.setCenter(latlng);
  }
  else
  {
    var latlng = this.map.getCenter();
    map_container.appendChild(map_canvas);
    this.fullscreencontainer.style.display = "none";
    this.map.checkResize();
    this.map.setCenter(latlng);

    document.body.scrollTop = this.scrollTop.bodyTop;
    document.documentElement.scrollTop = this.scrollTop.htmlTop;
    document.body.scrollLeft = this.scrollTop.bodyLeft;
    document.documentElement.scrollLeft = this.scrollTop.htmlLeft;
  }
}
