////////////////////////
// give google a chance to clean up memory leaks
add_unload_hook(function(e) { GUnload(); });

////////////////////////
// precinct shape data:
// these get overwritten by pdata<ld>.js.

var shaperes = 100000.0; // scale factor
var shapes = {}; 
var _ = undefined;
// shapes[region_number] = region object with properties
//   x0,y0,x1,y1 - bounding box
//   b - array of polygons [x,y,...]

////////////////////////
// XGMap object
//   wrapper for GMap2
// 
// properties:
//   gmap          - (GMap2)      underlying Google map object 
//   overlays      - (GOverlay[]) current polygon overlays 
//   prev_overlays - (GOverlay[]) next most recent polygon overlays 
//
//  specified in constructor
//
//   id   - (string) id of element containing map
//   type - (G_<foo>_MAP) initial map type 
//   zoom - (number) initial zoom - 0-17 as per GMap2
//   autozoom -
//        1 => zoom as needed
//       -1 => only zoom out
//        0 => never change the zoom
//   panlimit -
//        (number) when moving fewer than this many screenwidths,
//        animate ('pan') the move, otherwise jump
//        0       => always jump
//        +bignum => always pan
//   pt  -  (GLatLng) initial center point, which may also be given by
//   lat -  (number) lattitude of initial center point
//   lng -  (number) longitude of initial center point
//          pt takes precedence over lat/lng
//          
function XGMap(options) {
   // defaults
   this.autozoom = 1;
   this.panlimit = 1;
   this.type = G_NORMAL_MAP;
   this.zoom = 12;
   this.lat = 47.5431;
   this.lng = -122.13825;

   // initialize
   for (prop in options) {
      this[prop] = options[prop];
   }
   if (this.pt == undefined) {
      this.pt = new GLatLng(this.lat,this.lng);
   }
   delete this.lat;
   delete this.lng;

   this.prev_overlays = [];
   this.overlays = [];

   // initialize GMap2 object
   this.gmap = new GMap2(document.getElementById(this.id));
   this.gmap.addControl(options.control == 'big' || options.control == 'large' ? new GLargeMapControl() : new GSmallMapControl());
   this.gmap.addControl(new GMapTypeControl());
   this.gmap.setCenter(this.pt, this.zoom, this.type);
}

// return number of "screen widths" between old_pt (GLatLng) and new_pt (GLatLng)
// where the screen was originally of the given size (GLatLngBounds) before
// we zoomed out by a factor of 2^dzoom)
function screen_distance(old_pt,new_pt,size,dzoom) {
  var power = dzoom ? Math.pow(2,dzoom) : 1;
  return Math.max(Math.abs(new_pt.lng() - old_pt.lng())/(size.lng()*power), 
                  Math.abs(new_pt.lat() - old_pt.lat())/(size.lat()*power));
}  

// Display the overlays for a given region;
// return the overlays as an array 
// (for later use by remove_overlays)
XGMap.prototype.put_overlays = function(region) {
  if (region.ep) {
     return this.put_overlays_ep(region.ep,"#0000FF",5);
  }
  var overlays = [];
  for (s in region.b) {
    var polygon = region.b[s];
    var pts = [];
    var xx = region.x0;
    var yy = region.y0;
    for (var j = 0; j < polygon.length; j+=2) {
      xx += polygon[j];
      yy += polygon[j+1];
      pts.push(new GLatLng(yy/shaperes,xx/shaperes));
    }
    o = new GPolyline(pts);
    overlays.push(o);
    this.gmap.addOverlay(o);
  }
  return overlays;
}
XGMap.prototype.put_overlays_ep = function(shapes,color,weight) {
  var overlays = [];
  for (var ep = 0; ep < shapes.length; ++ep) {
     shapes[ep].color = color;
     shapes[ep].weight = weight;
     shapes[ep].zoomFactor = 2;
     shapes[ep].numLevels = 18;
     var o = new GPolyline.fromEncoded(shapes[ep]);
     overlays.push(o);
     this.gmap.addOverlay(o);
  }
  return overlays;
}

// Remove the given overlays
XGMap.prototype.remove_overlays = function(overlays) {
  for (var o in overlays) {
    this.gmap.removeOverlay(overlays[o])
  }
}

// Pan/Zoom/Move the map so that bounds (GLatLngBounds object) are in
// view, as per the settings of .autozoom and .panlimit.
XGMap.prototype.move_to = function (bounds) {
   var new_pt = bounds.getCenter();
   if (!this.autozoom) {
      if (this.panlimit <= 0) {
	 this.gmap.setCenter(new_pt);
      }
      else {
	 this.gmap.panTo(new_pt);
      }
   }
   else {
      var size = this.gmap.getBounds().toSpan();
      var old_pt = this.gmap.getCenter();
      var old_zoom = this.gmap.getZoom();
      var new_zoom = this.gmap.getBoundsZoomLevel(bounds);
      var _this = this;
      if (old_zoom > new_zoom) {
	 // need to zoom out
	 if (screen_distance(old_pt, new_pt, size, old_zoom - new_zoom) <= this.panlimit) {
	    var lisn = GEvent.addListener(this.gmap, "zoomend", function() {
	       GEvent.removeListener(lisn);
	       _this.gmap.panTo(new_pt);
	    });
	    this.gmap.setZoom(new_zoom);
	 }
	 else {
	    // just jump
	    this.gmap.setCenter(new_pt, new_zoom);
	 }
      }
      else if (screen_distance(old_pt, new_pt, size) <= this.panlimit) {
	 if (this.autozoom > 0 && old_zoom < new_zoom) {
	    // we can zoom in
	    var lisn = GEvent.addListener(this.gmap, "moveend", function() {
	       GEvent.removeListener(lisn);
	       _this.gmap.setZoom(new_zoom);
	    });
	 }
	 this.gmap.panTo(new_pt);
      }
      else {
	 // too far; just jump and zoom if we need to
	 this.gmap.setCenter(new_pt, this.autozoom > 0 ? new_zoom : old_zoom);
      }
   }
}

// Put overlays for region on map, recenter+zoom as needed
// (keep the immediately previous region's overlays;
//  remove anything that came before that)
XGMap.prototype.show_region = function(region_number) {

   this.remove_overlays(this.prev_overlays);

   if (!region_number) return;
   var region = shapes[region_number];
   if (!region) {
      return false;
   }

   var noverlays      = this.put_overlays(region);
   this.prev_overlays = this.overlays;
   this.overlays      = noverlays;

   this.move_to(new GLatLngBounds(new GLatLng(region.y0/shaperes, region.x0/shaperes), 
				  new GLatLng(region.y1/shaperes, region.x1/shaperes)));
   return true;
}

// Tie a form control (selectbox by default) to this map
XGMap.prototype.attach = function(select, onevent, getvalue) {
   if (!onevent)  onevent = "onchange";
   if (!getvalue) getvalue = function(s) { 
      return s.options[s.selectedIndex].value; 
   }

   var _this = this;

   select[onevent] = function (e) {
      if (!e) e = window.event;
      _this.show_region(getvalue(select));

      e.cancelBubble = true;
      if (e.stopPropagation) e.stopPropagation();
      return false;
   }

   this.show_region(getvalue(select));
}


///////////
//  event manager object
//  there should only be one outstanding at any time.
//  Each successive invocation cancels all of the previous ones.
//
//  .onclick = new SingleHandler({
//       animation: new IDedObj('spinnything');
//       launch: function(callback,args...) { ... do stuff with args..., callback(cargs...); }
//       land: function(cargs...) { ...more stuff... }


function SingleHandler(options) {
   this.counter = 0;
   this.animation = options.animation || null;
   this.launch_obj = options.launch_obj || options.obj || this;
   this.launch = options.launch;
   this.land_obj = options.land_obj || options.obj || this;
   this.land = options.land;
}

 
function toarray(obj) {
   var r = []; i = 0;
   while (i < obj.length) {
      r.push(obj[i++]);
   }
   return r;
}

SingleHandler.prototype.receiver = function() { 
   var _this = this;
   return function() { return _this.receive.apply(_this,toarray(arguments)); };
};
SingleHandler.prototype.receive = function() {
   if (this.animation) this.animation.style.display='block';
   var cc = ++this.counter;
   var _this = this;
   var a2 = toarray(arguments);
   a2.unshift(function() {
         if (_this.counter != cc) return;
	 if (_this.animation) _this.animation.style.display='none'; 
         _this.land.apply(_this.land_obj,toarray(arguments));
   });
   return this.launch.apply(this.launch_obj,a2);
};

