// Copyright (C) 2007 Michael Kosowsky  All rights reserved.

		// class that draws big filled polygons of a certain form correctly
/*********
if the map has multiple copies, it cloaks all but a strip along the center of a map
you hand it objects to draw
************/
/*** XXXXXXXXXX
if (document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#SVG", "1.1")) {
  _mSvgEnabled = true;
  _mSvgForced  = true;
  alert('forced');
}
**********/

SingleMapCanvas = function() {};
derive(SingleMapCanvas, GOverlay);


SingleMapCanvas.prototype.create_absolute_div = function(color, opacity) {
  var div = document.createElement("div");
  div.style.position        = "absolute";
  div.style.border          = "none";
  div.style.backgroundColor = color;
  div.style.opacity         = opacity;
  div.style.MozOpacity      = opacity;
  div.style.filter          = 'alpha(opacity=' + Math.round(100*opacity) + ')';
  div.style.display         = 'none';
  return div;
}

SingleMapCanvas.prototype.initialize = function(map) {
  this.map = map;
	// cloak on either side
  this.left_cloak  = this.create_absolute_div('#E5E3DF', 1);
  this.right_cloak = this.create_absolute_div('#E5E3DF', 1);
  this.cloak_pane  = map.getPane(G_MAP_FLOAT_SHADOW_PANE);
  this.cloak_pane.appendChild(this.left_cloak);
  this.cloak_pane.appendChild(this.right_cloak);
  this.shapes_pane = map.getPane(G_MAP_MARKER_SHADOW_PANE);
  //  this.shapes_pane = map.getPane(G_MAP_FLOAT_PANE);  // to put shapes above cloak
  this.shapes = [];

  this.x0               = null;
  this.map_full_width   = null;
  this.map_single_width = null;
}



SingleMapCanvas.prototype.remove = function() {
  this.shapes._foreach(function(s) { s.remove(); });
  this.cloak_pane.removeChild(this.left_cloak);
  this.cloak_pane.removeChild(this.right_cloak);
}

SingleMapCanvas.prototype.copy = function() {
   return new SingleMapCanvas();
}

SingleMapCanvas.prototype.cloak_off = function() {
   this.left_cloak.style.display = 'none';
  this.right_cloak.style.display = 'none';
}

SingleMapCanvas.prototype.cloak_on = function() {
   this.left_cloak.style.display = '';
  this.right_cloak.style.display = '';
}


// whenever we pan we have to reposition the curtains
// we only need to redraw the shapes if the widths have changed,
// or we've scrolled to a new copy of the map (the fromLatLngToDivPixel mapping has changed)

SingleMapCanvas.prototype.redraw = function(force) {
		// We build a canonical map so the lat/lon at the center of the screen is the center of the cloaked map
  var c = this.map.getCenter();

		// Why does this happen?? (i.e when zooming out on sky)
  if (!c) {
     var t = this;
     setTimeout(function() { t.redraw() }, 1000);
     return;
  }

  var zoom             = this.map.getZoom();
  var map_single_width = 256 * Math.pow(2, zoom);
  var map_full_width   = this.map.getSize().width;
  var border           = map_full_width - map_single_width;

  if (border <= 0) {
    this.is_cropped = 0;
    this.cloak_off();

  } else {
    var l           = this.left_cloak.style;
    var r           = this.right_cloak.style;
    var cc          = this.map.fromLatLngToDivPixel(c);
    var full_height = this.map.getSize().height;
    var top         = cc.y - full_height / 2; //Math.floor(cc.y - full_height / 2);
    var far_left    = cc.x - map_full_width / 2; //Math.floor(cc.x - map_full_width / 2);
    var left_edge   = far_left + border / 2; //Math.floor(border / 2);
    var right_edge  = left_edge + map_single_width;

    l.top    = r.top    = top                                      + 'px';
    l.height = r.height = full_height                              + 'px';
    l.left   =            far_left                    	           + 'px';
    l.width  =            (left_edge - far_left) 	           + 'px';
    r.left   =            right_edge             	           + 'px';
    r.width  =            (far_left + map_full_width - right_edge) + 'px';

    this.is_cropped = 1;
    this.cloak_on();
  }

/****log(this.is_cropped, h, this.cloak_pane.style.top, this.cloak_pane.style.bottom,'<br>',
c.lat(), c.lng(), '<br>',this.map.fromLatLngToDivPixel(c).x, this.map.fromLatLngToDivPixel(c).y,'<br>',
'0,0<br>',this.map.fromLatLngToDivPixel(new GLatLng(0,0)).x,this.map.fromLatLngToDivPixel(new GLatLng(0,0)).y,
'<br>80,0<br>',this.map.fromLatLngToDivPixel(new GLatLng(90,0)).x,this.map.fromLatLngToDivPixel(new GLatLng(90,0)).y );
******/

  var x0               = this.map.fromLatLngToDivPixel(new GLatLng(0,0)).x;
  if (map_single_width == this.map_single_width && map_full_width == this.map_full_width && x0 == this.x0)
    return;

  this.map_single_width = map_single_width;
  this.map_full_width   = map_full_width;
  this.x0               = x0;
  this.shapes._foreach(function(s) { s.redraw(force); });
}

SingleMapCanvas.prototype.addOverlay = function(s) {
  s.initialize(this);
  this.shapes.push(s);
  s.redraw(1);
}

SingleMapCanvas.prototype.removeOverlay = function(s) {
  for (var i = 0; i < this.shapes.length; i++)
    if (this.shapes[i] == s) {
      this.shapes.splice(i, 1);
      s.remove();
      break;
    }
}

SingleMapCanvas.prototype.clearOverlays = function() {
  this.shapes._foreach(function(s) { s.remove(); });
  this.shapes = [];
}


SingleMapCanvas.prototype.appendShapeDiv = function(d) {
  this.shapes_pane.appendChild(d);
}

SingleMapCanvas.prototype.removeShapeDiv = function(d) {
  this.shapes_pane.removeChild(d);
}


SMCShape = function() {
}

SMCShape.prototype.initialize = function(smc) {
  this.smc = smc;
}

SMCShape.prototype.redraw = function(force) {
}

SMCShape.prototype.remove = function() {
}


SMCRect = function(bounds, color, opacity) {
  this.bounds  = bounds;
  this.color   = color != null?   color   : '#000000';
  this.opacity = opacity != null? opacity : 1;
}

derive(SMCRect, SMCShape);
// simpleminded.  one for current veiwport, one 360 degrees ahead, another 60 behind


SMCRect.prototype.initialize = function(smc) {
  this.smc = smc;
  this.div0 = smc.create_absolute_div(this.color, this.opacity);
  this.div1 = smc.create_absolute_div(this.color, this.opacity);
  this.div2 = smc.create_absolute_div(this.color, this.opacity);
  smc.appendShapeDiv(this.div0);
  smc.appendShapeDiv(this.div1);
  smc.appendShapeDiv(this.div2);
  this.visible = 0;
}

SMCRect.prototype.remove = function() {
  this.smc.removeShapeDiv(this.div0);
  this.smc.removeShapeDiv(this.div1);
  this.smc.removeShapeDiv(this.div2);
}

SMCRect.prototype.redraw = function(force) {
  var sw = this.smc.map.fromLatLngToDivPixel(this.bounds.getSouthWest());
  var ne = this.smc.map.fromLatLngToDivPixel(this.bounds.getNorthEast());

  var s0 = this.div0.style;
  var s1 = this.div1.style;
  var s2 = this.div2.style;
  s0.top    = s1.top    = s2.top    = ne.y          + 'px';
  s0.height = s1.height = s2.height = (sw.y - ne.y) + 'px';
  var w = ne.x - sw.x;
  if (w < 0)
    w += this.smc.map_single_width;
  s0.width  = s1.width  = s2.width  = w             + 'px';
  s0.left                           = (sw.x - this.smc.map_single_width) + 'px';
  s1.left                           = sw.x          + 'px';
  s2.left                           = (sw.x + this.smc.map_single_width) + 'px';

  if (!this.visible) {
    s0.display = s1.display = s2.display = '';
    this.visible = 1;
  }
}


SMCShapes = function() {
  this.smc    = null;
  this.shapes = [];
}

SMCShapes.prototype.initialize = function(smc) {
  this.smc = smc;
  this.shapes._foreach(function(s) { s.initialize(smc); });
}

SMCShapes.prototype.remove = function() {
  this.shapes._foreach(function(s) { s.remove(); });
}

SMCShapes.prototype.redraw = function(force) {
  this.shapes._foreach(function(s) { s.redraw(force); });
}

SMCShapes.prototype.addShape = function(s) {
	// can't add AFTER you addOverlay this
  if (this.smc) {
    alert('SMCShapes.prototype.addShape: added too late');
    return;
  }
  this.shapes.push(s);
}



SMCRaster = function(color, opacity) {
  SMCShapes.call(this);
  this.color   = color   != null? color   : '#000000';
  this.opacity = opacity != null? opacity : 1;
	// each is a [lat, west_lon, east_lon]
  this.rasters = [];
}

derive(SMCRaster, SMCShapes);

SMCRaster.prototype.addRaster = function(lat, wlon, elon) {
	// can't add AFTER you addOverlay this
  if (this.smc) {
    alert('SMCRaster.prototype.addRaster: added too late');
    return;
  }
  this.rasters.push([lat, wlon, elon]);
}

function cmp_rasters_by_lat(a, b) {
  return a[0] - b[0];
}

SMCRaster.prototype.initialize = function(smc) {
	// turn our rasters into SMCRects
  this.rasters.sort(cmp_rasters_by_lat);
  for (var i = 1; i < this.rasters.length; i++)
    this.addShape(new SMCRect(new GLatLngBounds(new GLatLng(this.rasters[i-1][0], this.rasters[i][1]),
                                                new GLatLng(this.rasters[i][0],   this.rasters[i][2])),
			      this.color,
		              this.opacity));
  SMCShapes.prototype.initialize.call(this, smc);
}



SMCSphericalCap = function(latlng, angular_radius, color, opacity) {
  this.color   = color   != null? color   : '#000000';
  this.opacity = opacity != null? opacity : 1;
  SMCRaster.call(this, this.color, this.opacity);

  this.lat0   = latlng.lat();
  this.lon0   = latlng.lng();
  this.radius = angular_radius;
  if (Math.abs(this.lat0) < .0001 && this.radius >= this.MAXLAT)
    this.rectangular_terminator();
   else if (Math.abs(this.lat0) > 89.9)
    this.horizontal_terminator();
   else
    this.curved_terminator();
}

derive(SMCSphericalCap, SMCRaster);

SMCSphericalCap.prototype.RADIANS_PER_DEGREE = Math.PI / 180.;
SMCSphericalCap.prototype.MAXLAT             = 85.0511288;

SMCSphericalCap.prototype.curved_terminator = function() {
  this.cosr   = Math.cos(this.radius * this.RADIANS_PER_DEGREE);
  this.sinlat0 = Math.sin(this.lat0 * this.RADIANS_PER_DEGREE);
  this.coslat0 = Math.cos(this.lat0 * this.RADIANS_PER_DEGREE);

  var lat_min;
  var lat_max;
  for (var lat = -this.MAXLAT; lat <= this.MAXLAT; lat++) {
    if (this.add_r(lat)) {
      if (lat_min == null)
        lat_min = lat;
      lat_max = lat;
    }
  }

	// finer resolution at top and bottom of graph
  var lat_min2 = lat_min;
  var lat_max2 = lat_max;
  for (var i = 5; i >= -1; i -= .1) {
    if (lat_min + i >= -this.MAXLAT)
      if (this.add_r(lat_min + i))
        lat_min2 = lat_min + i;

    if (lat_max - i <= this.MAXLAT)
      if (this.add_r(lat_max - i))
        lat_max2 = lat_max - i;
  }

	// finally, we may need to cover the top or bottom
	// (use two rectangles because -180 to 180 doesn't work)
  if (this.cosdlon(-this.MAXLAT) <= 1 && lat_min2 > -this.MAXLAT + .0001) {
    this.addShape(new SMCRect(new GLatLngBounds(new GLatLng(-this.MAXLAT, -180), new GLatLng(lat_min2,   0)), this.color, this.opacity));
    this.addShape(new SMCRect(new GLatLngBounds(new GLatLng(-this.MAXLAT,    0), new GLatLng(lat_min2, 180)), this.color, this.opacity));
  }
  if (this.cosdlon(this.MAXLAT) <= 1 && lat_max2 < this.MAXLAT - .0001) {
    this.addShape(new SMCRect(new GLatLngBounds(new GLatLng(lat_max2, -180), new GLatLng(this.MAXLAT,   0)), this.color, this.opacity));
    this.addShape(new SMCRect(new GLatLngBounds(new GLatLng(lat_max2,    0), new GLatLng(this.MAXLAT, 180)), this.color, this.opacity));
  }
}


SMCSphericalCap.prototype.cosdlon = function(lat) {
  var l = lat * this.RADIANS_PER_DEGREE;
  return (this.cosr - this.sinlat0 * Math.sin(l))/(this.coslat0 * Math.cos(l));
}


SMCSphericalCap.prototype.add_r = function(lat) {
  var c = this.cosdlon(lat);
  if (c > -1 && c < 1) {
    var dlon = Math.acos(c) / this.RADIANS_PER_DEGREE;
    this.addRaster(lat, this.lon0 - dlon, this.lon0 + dlon);
    return 1;
  }
  return 0;
}


SMCSphericalCap.prototype.rectangular_terminator = function() {
  this.addShape(new SMCRect(new GLatLngBounds(new GLatLng(-this.MAXLAT, this.lon0 - this.radius), new GLatLng(this.MAXLAT, this.lon0 + this.radius)), this.color, this.opacity));
}

SMCSphericalCap.prototype.horizontal_terminator = function() {
  var l = 90 - this.radius;
  if (l >= this.MAXLAT)
    return;
  if (this.lat0 < 0) {
    this.addShape(new SMCRect(new GLatLngBounds(new GLatLng(-this.MAXLAT, -180), new GLatLng(-l,   0)), this.color, this.opacity));
    this.addShape(new SMCRect(new GLatLngBounds(new GLatLng(-this.MAXLAT,    0), new GLatLng(-l, 180)), this.color, this.opacity));
  } else {
    this.addShape(new SMCRect(new GLatLngBounds(new GLatLng(l, -180), new GLatLng(this.MAXLAT,   0)), this.color, this.opacity));
    this.addShape(new SMCRect(new GLatLngBounds(new GLatLng(l,    0), new GLatLng(this.MAXLAT, 180)), this.color, this.opacity));
  }
}
