
function Map (div, centerPoint, model, styleActive, styleInactive, styleDestroyed, styleHilited, tileBaseUrl, copyright, assetBaseUrl) {
	this.canvas = div;
	this.initialized = false;
	this.polygonManager = null;
	this.currentPeriodId = null;
	this.initialize = function(model) {
		/*
		if(document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#SVG","1.1")) {
			_mSvgEnabled = true;
			_mSvgForced = true;
		}
		*/
		// Create the map object
		this.gMap = new GMap2(div, {mapTypes:[G_SATELLITE_MAP], backgroundColor:"#fafafa"});
		this.gMap.setCenter(new GLatLng(centerPoint[1], centerPoint[0]), 16, G_SATELLITE_MAP);
		this.gMap.addControl(new GLargeMapControl());
		this.gMap.addControl(new GMapTypeControl());
		// Convert MapCuncher tile path to google tile path
		function TileToQuadKey (tx, ty, zl) {
			var quad = "";
			for (var i = zl; i > 0; i--) {
				var mask = 1 << (i - 1);
				var cell = 0;
				if ((tx & mask) != 0) cell++;
				if ((ty & mask) != 0) cell += 2;
				quad += cell;
			}
			return quad;
		}
		// Build Plan view
		var planLayer = new GTileLayer(null, 14, 20); 
		planLayer.getTileUrl = function (a,b) { return tileBaseUrl + "/" + TileToQuadKey(a.x,a.y,b) + ".png"; }; 
		planLayer.getCopyright = function(a,b) { return copyright; }; 
		// Add the Plan map type
		var planMapType = new GMapType([planLayer], G_SATELLITE_MAP.getProjection(), "Plan"); 
		planMapType.getTileLayers()[0].getOpacity = function() {return "0.65"};
		this.gMap.addMapType(planMapType);
		// Add the Hybrid map type
		var tilelayers = [G_SATELLITE_MAP.getTileLayers()[0], planMapType.getTileLayers()[0]];
		var hybridMapType = new GMapType(tilelayers, G_SATELLITE_MAP.getProjection(), "Hybrid");
		this.gMap.addMapType(hybridMapType); 
		// Set the default map type
		this.gMap.setMapType(hybridMapType);
		// Limit min and max zoom levels
		G_SATELLITE_MAP.getMinimumResolution = function() {return 5;}
		G_SATELLITE_MAP.getMaximumResolution = function() {return 18;}
		hybridMapType.getMinimumResolution = function() {return 14;}
		hybridMapType.getMaximumResolution = function() {return 18;}
		// Add a move listener to restrict the bounds range
		var theMap = this.gMap; GEvent.addListener(this.gMap, "move", function() {checkBounds(theMap);});
		// The allowed region which the whole map must be within
		var allowedBounds = new GLatLngBounds(new GLatLng(25.69,32.63), new GLatLng(25.73,32.68));
		// If the map position is out of range, move it back
		function checkBounds(map) {
			// Perform the check and return if OK
			if (allowedBounds.contains(map.getCenter())) return;
			// It`s not OK, so find the nearest allowed point and move there
			var C = map.getCenter();
			var X = C.lng();
			var Y = C.lat();
			var AmaxX = allowedBounds.getNorthEast().lng();
			var AmaxY = allowedBounds.getNorthEast().lat();
			var AminX = allowedBounds.getSouthWest().lng();
			var AminY = allowedBounds.getSouthWest().lat();
			if (X < AminX) {X = AminX;}
			if (X > AmaxX) {X = AmaxX;}
			if (Y < AminY) {Y = AminY;}
			if (Y > AmaxY) {Y = AmaxY;}
			map.setCenter(new GLatLng(Y,X));
		}
		// Build the footprint polygons
		var showInactive = true;
		this.polygonManager = new PolygonManager(this.gMap, showInactive, this.canvas);
		this.polygonManager.buildPolys(model, styleActive, styleInactive, styleDestroyed, styleHilited, assetBaseUrl);
		GEvent.addListener(this.gMap, "infowindowclose", function() { model.deselectFeature(); });
		this.initialized = true;
	};
};
Map.prototype.update = function(model) {
	if (!this.initialized) 
		this.initialize(model);
	if (model.currentPeriodId != this.currentPeriodId) {
		// Do nothing unless a new period has been selected
		this.currentPeriodId = model.currentPeriodId;
		this.polygonManager.updatePolygons(model);
	}
	if (model.hasSelectedFeaturePhase()) {
		// Don't need to do this if we are showing an info window on select (GMaps auto-pans on info window open)
		//this.gMap.panTo(this.polygonManager.getPolygon(model.getSelectedFeaturePhaseId()).getGCenter());
	}
	this.polygonManager.updateHilited(model);
};
Map.prototype.unload = function() {
	GUnload();
}



function PolygonManager(gMap, showInactive, canvas) {
	this.showInactive = showInactive;
	this.gMap = gMap;
	this.polys = new Array();
	this.footprintPolygonLookup = new Array();
	this.currentlyActive = new Array();
	this.currentlyInactive = new Array();
	this.currentlyDestroyed = new Array();
	this.infoWindowId = -1;
	this.bindEvents = function(poly, model) {
		var pManager = this;
		GEvent.addListener(poly.gPolygonActive, "click", function(point) {
			model.selectFeature(poly.footprint.phase.feature.id);
		});
		//GEvent.addListener(poly.gPolygonActive, "click", function(point) {
		//	window.location.href = poly.footprint.phase.feature.url;
		//});
		GEvent.addListener(gMap, "infowindowclose", function() {
			pManager.infoWindowId = -1;
		});
		GEvent.addListener(poly.gPolygonActive, "mouseover", function() { 
			//if (model.hasSelectedFeaturePhase() && poly.footprint.id != model.getSelectedFeaturePhaseId()) 
			//	model.unhiliteFeature();
			model.hiliteFeature(poly.footprint.phase.feature.id);
			canvas.firstChild.firstChild.style.cursor = 'pointer'; // Undocumented in API
		});
		GEvent.addListener(poly.gPolygonActive, "mouseout", function() {
			if (!model.hasSelectedFeaturePhase() || poly.footprint.id != model.getSelectedFeaturePhaseId()) {
				model.unhiliteFeature();
			}
			//if (pManager.currentlyClickedPoly != null)
			//	pManager.currentlyClickedPoly.hilite();
		});
	};
};
PolygonManager.prototype.getPolygon = function(phaseId) {
	return this.footprintPolygonLookup[phaseId];
}
PolygonManager.prototype.buildPolys = function(model, styleActive, styleInactive, styleDestroyed, styleHilited, assetBaseUrl) {
	var footprints = model.getFootprints();
	new SeedPolygon(this.gMap).addToMap(); // Add a dummy polygon before any others (Google Maps API bug workaround)
	var poly, footprint;
	for (var i=0; i<footprints.length; i++) {
		footprint = footprints[i];
		poly = new MapPolygon(footprint, this.gMap, styleActive, styleInactive, styleDestroyed, styleHilited, this.showInactive, assetBaseUrl);
		this.polys.push(poly);
		this.footprintPolygonLookup[footprint.phase.id] = poly;
	}
	if (this.showInactive) {
		for (var i=0; i<this.polys.length; i++) {
			poly = this.polys[i];
			poly.addInactiveToMap();
			poly.hide();
		}
	}
	for (var i=0; i<this.polys.length; i++) {
		poly = this.polys[i];
		poly.addActiveToMap();
		poly.hide();
	}
	for (var i=0; i<this.polys.length; i++) {
		poly = this.polys[i];
		this.bindEvents(poly, model);
	}
};
PolygonManager.prototype.updatePolygons = function(model) {
	var newActive = model.getCurrentlyActiveFootprints();
	var newInactive = this.showInactive ? model.getCurrentlyInactiveFootprints() : new Array();
	var newDestroyed = model.getCurrentlyDestroyedFootprints();
	var hidden = this.updateHidden(newActive, newInactive, newDestroyed);
	var activated = this.updateActive(newActive);
	if (this.showInactive) {
		var inactivated = this.updateInactive(newInactive);
	}
	var destroyed = this.updateDestroyed(newDestroyed);
	//if (model.hasHilitedFeature()) {
		//this.footprintPolygonLookup[model.getStartupPhaseId()].showInfoWindow(model);
		// hilite the respective footprint
	this.gMap.closeInfoWindow(); // Close the info window balloon if one is open
};
PolygonManager.prototype.updateHidden = function(newActive, newInactive, newDestroyed) {
	var currentlyVisible = this.currentlyActive.concat(this.currentlyInactive).concat(this.currentlyDestroyed);
	var newVisible = newActive.concat(newInactive).concat(newDestroyed);
	var footprint;
	for (var i=0; i<currentlyVisible.length; i++) {
		var footprint = currentlyVisible[i];
		if (!newVisible.indexOf(footprint)) {
			this.footprintPolygonLookup[footprint.phase.id].hide();
		}
	}
};
PolygonManager.prototype.updateActive = function(newFootprints) {
	var footprint;
	for (var i=0; i<newFootprints.length; i++) {
		footprint = newFootprints[i];
		if (!this.currentlyActive.indexOf(footprint)) {
			this.footprintPolygonLookup[footprint.phase.id].makeActive();
		}
	}
	this.currentlyActive = newFootprints;
};
PolygonManager.prototype.updateInactive = function(newFootprints) {
	var footprint;
	for (var i=0; i<newFootprints.length; i++) {
		footprint = newFootprints[i];
		if (!this.currentlyInactive.indexOf(footprint)) {
			this.footprintPolygonLookup[footprint.phase.id].makeInactive();
		}
	}
	this.currentlyInactive = newFootprints;
};
PolygonManager.prototype.updateDestroyed = function(newFootprints) {
	var footprint;
	for (var i=0; i<newFootprints.length; i++) {
		footprint = newFootprints[i];
		if (!this.currentlyDestroyed.indexOf(footprint)) {
			this.footprintPolygonLookup[footprint.phase.id].makeDestroyed();
		}
	}
	this.currentlyDestroyed = newFootprints;
};

PolygonManager.prototype.updateHilited = function(model) {
	var footprints = this.currentlyActive.concat(this.currentlyDestroyed);
	for (var i=0; i<footprints.length; i++) {
		var hilited = model.featureIsHilited(footprints[i].phase.feature.id);
		var selected = model.featureIsSelected(footprints[i].phase.feature.id);
		if (selected) {
			this.footprintPolygonLookup[footprints[i].phase.id].hilite();
			if (this.infoWindowId != footprints[i].phase.id) {
				this.gMap.closeInfoWindow();
				this.footprintPolygonLookup[footprints[i].phase.id].showInfoWindow(model);
				this.infoWindowId = footprints[i].phase.id;
			}
		} else if (hilited) {
			this.footprintPolygonLookup[footprints[i].phase.id].hilite();
		} else {
			this.footprintPolygonLookup[footprints[i].phase.id].unhilite();
		}
	}
};


function MapPolygon(theFootprint, gMap, styleActive, styleInactive, styleDestroyed, styleHilited, showInactive, assetBaseUrl) {
	this.footprint = theFootprint;
	this.gMap = gMap;
	this.styleActive = styleActive;
	this.styleInactive = styleInactive;
	this.styleDestroyed = styleDestroyed;
	this.styleHilited = styleHilited;
	this.showInactive = showInactive;
	this.assetBaseUrl = assetBaseUrl;
	var buildGPolygon = function(footprint, style) {
		var verts = new Array();
		for (var v=0; v<footprint.verts.length; v++) {
			var vert = footprint.verts[v];
			verts.push(new GLatLng(vert[1], vert[0])); // XY-LatLong rotated 90 to convert to GLatLng
		}
		return new GPolygon(verts, 
			style.strokeColor, style.strokeWidth, style.strokeOpacity, 
			style.fillColor, style.fillOpacity);
	};
	this.gPolygonActive = buildGPolygon(theFootprint, this.styleActive);
	this.gPolygonInactive = showInactive ? buildGPolygon(theFootprint, this.styleInactive) : null;
	this.periodStatus = -1;
	//this.hide();
}
MapPolygon.prototype.getGCenter = function() {
	return this.gPolygonActive.getBounds().getCenter();
}
MapPolygon.prototype.addActiveToMap = function() {
	this.gMap.addOverlay(this.gPolygonActive);
}
MapPolygon.prototype.addInactiveToMap = function() {
	if (this.showInactive) this.gMap.addOverlay(this.gPolygonInactive);
}
MapPolygon.prototype.equals = function(other) {
	if (!(other instanceof MapPolygon)) return false;
	return this.footprint.phase.id == other.footprint.phase.id;
}
MapPolygon.prototype.hilite = function() {
	this.applyStyle(this.styleHilited, this.gPolygonActive);
}
MapPolygon.prototype.unhilite = function() {
	this.applyStyle(this.styleActive, this.gPolygonActive);
}
MapPolygon.prototype.hide = function() {
	this.gPolygonActive.hide();
	if (this.showInactive) this.gPolygonInactive.hide();
	this.periodStatus = 0;
}
MapPolygon.prototype.makeActive = function() {
	if (this.showInactive) this.gPolygonInactive.hide();
	this.gPolygonActive.show();
	this.periodStatus = 1;
}
MapPolygon.prototype.makeInactive = function() {
	if (this.showInactive) {
		this.gPolygonActive.hide();
		this.gPolygonInactive.show();
		this.periodStatus = 2;
	}
}
MapPolygon.prototype.makeDestroyed = function() {
	if (this.showInactive) this.gPolygonInactive.hide();
	this.gPolygonActive.show();
	this.periodStatus = 3;
}
MapPolygon.prototype.applyStyle = function(style, gpoly) {
	gpoly.setStrokeStyle({color:style.strokeColor,weight:style.strokeWeight,opacity:style.strokeOpacity});
  	gpoly.setFillStyle({color:style.fillColor,opacity:style.fillOpacity});
};
MapPolygon.prototype.isHidden = function() { 
	return this.periodStatus == 0; 
}
MapPolygon.prototype.isActive = function() { 
	return this.periodStatus == 1; 
}
MapPolygon.prototype.isInactive = function() { 
	return this.periodStatus == 2; 
}
MapPolygon.prototype.isDestroyed = function() { 
	return this.periodStatus == 3; 
}
MapPolygon.prototype.showInfoWindow = function(model) {
	var renderPhaseLifecycleEvent = function(evt) {
		if (evt == null) return "";
		var isCurrentPeriod = model.isCurrentPeriod(evt.periodId);
		return "<p"+(model.isCurrentPeriod(evt.periodId) ? " class='lifecycleEventSelected'" : "")+">"+evt.description+'</p>'; // TODO - link if not current (see feature/show.rhtml)
	}
	var myHtml = "<div class='timemapInfoWindow'>";
	myHtml += "<div class='icon'><a href='"+this.footprint.phase.feature.url+"'><img src='"+this.assetBaseUrl+"/media/resources/"+this.footprint.phase.feature.codename+"/icon.jpg' width='70' height='70' /></a></div><p class='featureName'><a href='"+
		this.footprint.phase.feature.url+"'>"+
		this.footprint.phase.feature.name+"</a></p>";
	/*
	var phases = this.footprint.phase.feature.getPhases();
	for (var i=0; i<phases.length; i++) {
		var phase = phases[i];
		myHtml += renderPhaseLifecycleEvent(phase.getCreationEvent());
		myHtml += renderPhaseLifecycleEvent(phase.getModificationEvent());
		myHtml += renderPhaseLifecycleEvent(phase.getDestructionEvent());
	}
	*/
	myHtml += "</div>";
	this.gMap.openInfoWindowHtml(new GLatLng(this.footprint.getCenterY(), this.footprint.getCenterX()), myHtml);
}


// A workaround for a bug in GM API. The first GPolygon added using 
// map.addOverlay() doesn't scale with zoom changes or respond to 
// style changes. Is there a proper fix for this???
function SeedPolygon(gMap) {
	this.gMap = gMap;
	verts = [new GLatLng(1,1), new GLatLng(1.001, 1.001), new GLatLng(1.002, 1.002)];
	this.gPolygon = new GPolygon(verts, '#999999', 1, 0, '#999999', 0);
}
SeedPolygon.prototype.addToMap = function() {
	this.gMap.addOverlay(this.gPolygon);
}





