Number.prototype.toRad = function() {  // convert degrees to radians
  return this * Math.PI / 180;
}
Number.prototype.toDeg = function() {  // convert radians to degrees (signed)
  return this * 180 / Math.PI;
}

/**
* Function : dump()
* Arguments: The data - array,hash(associative array),object
*    The level - OPTIONAL
* Returns  : The textual representation of the array.
* This function was inspired by the print_r function of PHP.
* This will accept some data as the argument and return a
* text that will be a more readable version of the
* array/hash/object that is given.
*/
function dump(arr,level) {
var dumped_text = "";
if(!level) level = 0;

//The padding given at the beginning of the line.
var level_padding = "";
for(var j=0;j<level+1;j++) level_padding += "    ";

if(typeof(arr) == 'object') { //Array/Hashes/Objects
 for(var item in arr) {
  var value = arr[item];
 
  if(typeof(value) == 'object') { //If it is an array,
   dumped_text += level_padding + "'" + item + "' ...\n";
   dumped_text += dump(value,level+1);
  } else {
   dumped_text += level_padding + "'" + item + "' => \"" + value + "\"\n";
  }
 }
} else { //Stings/Chars/Numbers etc.
 dumped_text = "===>"+arr+"<===("+typeof(arr)+")";
}
return dumped_text;
}


function MultiRegionMapper(mapNode, controlsNode, existingRegions){
	var thisObj = this;	//used to access this object from anonymously defined functions
	var lastLocation;
	
	//should try to read these from the stylesheet
	var stateOff = {color:"#000000", opacity:0};
	var stateOffHover = {color:"#0066CC", opacity:.2};
	var stateOn = {color:"#0066CC", opacity:.5};
	var stateOnHover = {color:"#0066CC", opacity:.8};
	
	var regions = Array();


	var checkStateCode = function(description){
		if (description.length != 2) return false;
		description = description.toUpperCase();
		for (var i in data_us){
			if (description == data_us[i].abr) return data_us[i];
		}
		return null;
	}
	var checkStateName = function(description){
		//put it into the proper capitalized format
		description = description.substr(0,1).toUpperCase() + description.toLowerCase().substr(1);
		for (var i in data_us){
			if (description == data_us[i].name) return data_us[i];
		}
		return null;
	}
	if (GBrowserIsCompatible()) {
	  	// Create and Center a Map
		this.map = new GMap2(mapNode);
		//centered and showing entire continental US
		this.map.setCenter(new GLatLng(38.27268853598097, -95.09765625), 4);
		this.map.addControl(new GLargeMapControl());
		this.map.addControl(new GScaleControl());
		//Create Geocoding object
		this.geocoder = new GClientGeocoder();
	}
	this.numRegions = document.createElement('INPUT');
	this.numRegions.type = "hidden";
	this.numRegions.name = MultiRegionMapper.prefix+"_num_regions";
	controlsNode.appendChild(this.numRegions);
	
	this.regionTextfield = document.createElement('INPUT');
	controlsNode.appendChild(this.regionTextfield);
	this.regionTextfield.id = MultiRegionMapper.prefix+"_region_textfield";
	this.regionTextfield.value="Type an address, intersection, or city here";
	this.regionTextfield.onkeypress = function(e){		
		if (!e) var e = window.event;
		if (e.keyCode==13){
			thisObj.subBut.onclick();
			return false;  			//cancel the default action to stop the form from submitting
		}		
	}
	
	
	this.addRegion = function(r){
		regions.push(r);
		this.numRegions.value = regions.length;
		controlsNode.appendChild(r.getDbox());
		var polys = r.getPolys();
		for (var p in polys){
			this.map.addOverlay(polys[p]);
		}
		this.map.addOverlay(r.getMarker());
		r.getDbox().onclick = function(e){
			thisObj.removeRegion(r);
		}
	}
	this.removeRegion = function(r){
		regions.splice(r.getIndex(), 1);
		for (var i=0; i < regions.length; i++){
			regions[i].changeIndex(i);
		}
		this.map.removeOverlay(r.getMarker());
		var polys = r.getPolys();
		for (var p in polys){
			this.map.removeOverlay(polys[p]);
		}
		controlsNode.removeChild(r.getDbox());
		this.numRegions.value = regions.length;
	}
	this.enableInputClear = function(){
		this.regionTextfield.onfocus = function(e){
			this.value="";
			this.onfocus=null;
		}
	}

	
	
	
	
	
	this.subBut = document.createElement('INPUT');
	this.subBut.id = MultiRegionMapper.prefix+"_add_button";
	this.subBut.type="button";
	this.subBut.value="";
	controlsNode.appendChild(this.subBut);
	
	controlsNode.appendChild(function(){var t = document.createElement('DIV'); t.id=MultiRegionMapper.prefix+"_examples"; t.innerHTML='Examples: "MD", "Virginia", "San Francisco", "Under 20 miles from 123 Clark Street, NY"'; return t;}());
	
	this.subBut.onclick = function(e){
		thisObj.addRegionByDescription(thisObj.regionTextfield.value);		
	}
	this.addRegionByDescription = function(description){
		this.enabled = false;
		// var description = thisObj.regionTextfield.value;
		lastLocation = description;
		//trim extra spaces
		lastLocation = lastLocation.replace(/^\s+|\s+$/g,'').replace(/\s+/g,' ');
		thisObj.regionTextfield.value="processing...";
		
		//if its a state, create the state overlay and add it
		if (lastLocation.length < 25){		//location description is short
			var stateObj;
			if (lastLocation.length == 2) stateObj = checkStateCode(lastLocation);
			else stateObj = checkStateName(lastLocation);
			if (stateObj != null){
				thisObj.addRegion(new MultiRegionMapper.Region(lastLocation, MultiRegionMapper.STATE, stateObj, regions.length, thisObj.map));
				thisObj.regionTextfield.value = '';
				return true;
			}
		}
		
		//check for a smart distance query (ex: Less than 20 miles from 123 Dixie St., MD 20202)
		var matched = lastLocation.match(/(within|under|less than|<)?[ ]?([0-9]+)[ ]?(mile|miles|mi|kilometer|kilometers|km|kms|k) (of|from)[ ]?(.+)/i);
		var distance;
		if (matched != null){
			distance = matched[2];
			if (matched[3].toUpperCase().substr(0,2) == 'MI') {
				distance /= 0.621371192;
			}
			lastLocation = matched[5];	
		}
		
		//otherwise, use the returned latlng bounds
		
		
		thisObj.geocoder.getLocations(lastLocation, function(result) {
		  if (result.Status.code != 200) {
			thisObj.regionTextfield.value = "ERROR: Invalid location";
		  	thisObj.enableInputClear();
		  } else {
			  if (result.Placemark.length == 1){
				  //there is only 1 response, we can handle it directly
					var center = new GLatLng(result.Placemark[0].Point.coordinates[1], result.Placemark[0].Point.coordinates[0]);
					var R = 6371;	//radius of earth in KM
					var data = new Object();
					if (distance != null){
						//adjust bounding box						
						result.Placemark[0].ExtendedData.LatLonBox.west = center.lng() + Math.atan2(-Math.sin(distance/R)*Math.cos(center.lat().toRad()), Math.cos(distance/R)-Math.sin(center.lat().toRad())*Math.sin(center.lat().toRad())).toDeg();
						result.Placemark[0].ExtendedData.LatLonBox.east = center.lng() + Math.atan2(Math.sin(distance/R)*Math.cos(center.lat().toRad()), Math.cos(distance/R)-Math.sin(center.lat().toRad())*Math.sin(center.lat().toRad())).toDeg();
						result.Placemark[0].ExtendedData.LatLonBox.north = Math.asin( Math.sin(center.lat().toRad())*Math.cos(distance/R) + Math.cos(center.lat().toRad())*Math.sin(distance/R) ).toDeg();
						result.Placemark[0].ExtendedData.LatLonBox.south = Math.asin( Math.sin(center.lat().toRad())*Math.cos(distance/R) - Math.cos(center.lat().toRad())*Math.sin(distance/R) ).toDeg();
						//may have problems with normalizing west/east or something????												 
					}
/*						thisObj.map.addOverlay(new GMarker(new GLatLng(data.west, data.south)));
						thisObj.map.addOverlay(new GMarker(new GLatLng(data.west, data.north)));
						thisObj.map.addOverlay(new GMarker(new GLatLng(data.east, data.south)));
						thisObj.map.addOverlay(new GMarker(new GLatLng(data.east, data.north)));
						thisObj.map.addOverlay(new GMarker(new GLatLng((data.west+data.east)/2, (data.north+data.south)/2)));
//						marker = new GMarker(new GLatLng((data.west+data.east)/2, (data.north+data.south)/2), {icon:new GIcon(MultiRegionMapper.baseIcon), draggable:false});
*/
					
					
					thisObj.addRegion(new MultiRegionMapper.Region(description, MultiRegionMapper.BOX, result.Placemark[0].ExtendedData.LatLonBox, regions.length, thisObj.map));
					thisObj.regionTextfield.value = '';
					return true;
			  } else {
				alert("There are "+result.Placemark.length+" placemark responses");
			  }			
			thisObj.regionTextfield.value = '';
		  }
		});
	}	
	this.enableInputClear();
	
	for ( i=0; i < existingRegions.length; i++){
		thisObj.addRegionByDescription( existingRegions[i] );
	}
}

MultiRegionMapper.Region = function(locString, ntype, data, nindex, map){
	var thisObj = this;
	var description = locString;
	var type = ntype;
	
	var bounds;	//type GLatLngBounds
	var minLat, maxLat, minLng, maxLng;
	var stateCode;
	
	var index;
	var dbox;

	var typeInput;
	var westInput, eastInput, northInput, southInput;
	var stateCodeInput;
	var descInput;
	var mapRef = map;
	
	var marker;
	var circleMarker;
	var xbut;	
	var overlay;
	var polys = Array();
	
	////////
	this.changeIndex = function(newIndex){
		index = newIndex;

		newLetter = String.fromCharCode(("A").charCodeAt(0) + index);
		//cannot use setImage because the icon hasnt necessarily been loaded onto the map yet
		// this might cause a little delay because its not hiding it until it loads properly...

		//change the marker letter on the map
		if (marker.isHidden()) marker.getIcon().image = "http://www.google.com/mapfiles/marker" + newLetter + ".png";
		else marker.setImage("http://www.google.com/mapfiles/marker" + newLetter + ".png");

		var newCircle = document.createElement('IMG');
		newCircle.src = "http://www.google.com/mapfiles/circle" + newLetter + ".png";
		circleMarker.parentNode.replaceChild(newCircle,circleMarker);
		circleMarker = newCircle;

		typeInput.name = MultiRegionMapper.prefix+"_region_"+index+"_type";
		descInput.name = MultiRegionMapper.prefix+"_region_"+index+"_description";
		if (type == MultiRegionMapper.STATE){
			stateCodeInput.name = MultiRegionMapper.prefix+"_region_"+index+"_statecode";
		} else {
			northInput.name = MultiRegionMapper.prefix+"_region_"+index+"_north";
			southInput.name = MultiRegionMapper.prefix+"_region_"+index+"_south";
			eastInput.name = MultiRegionMapper.prefix+"_region_"+index+"_east";
			westInput.name = MultiRegionMapper.prefix+"_region_"+index+"_west";
		}
	}
	this.getIndex = function(){ return index; }
	this.getMarker = function(){ return marker; }
	this.updateLatLng = function(){
		//updates the form fields with the current lat and lng of the associated marker
		var ll = marker.getLatLng();
		latInput.value = ll.lat();
		lngInput.value = ll.lng();
		//adjust entire box?
	}
	
	//creates a default marker located at the point passed in
 	//marker = new GMarker(center, {icon:new GIcon(MultiRegionMapper.baseIcon), draggable:true});
/*	GEvent.addListener(marker, "dragend", function (newLatLng){
		thisObj.updateLatLng();
		overlay.remove();
		overlay = MultiRegionMapperCircle.createCircleOverlay(center, circRadius);
		mapRef.addOverlay(overlay);
	});
*/		
/////////////////
	
	//INITIALIZE
	if (type == MultiRegionMapper.STATE){
		stateCode = data.abr;
		description = data.name;
		bounds = new GLatLngBounds();
		for (var shapeIndex in data.shapes){				
			var shapeObject = data.shapes[shapeIndex];
			var ptarray = [];
			var p;
			for (var pointIndex in shapeObject){
				p = new GLatLng(shapeObject[pointIndex][1],shapeObject[pointIndex][0]);
				bounds.extend(p);
				ptarray.push(p);
			}
			var s = new GPolygon(ptarray, '#000000', 1, .1, '#0066CC', .5);
			polys.push(s);
		}
		marker = new GMarker(bounds.getCenter(), {icon:new GIcon(MultiRegionMapper.baseIcon), draggable:false});
	} else {
		var ptarray = Array();
		ptarray.push(new GLatLng(data.south, data.west));
		ptarray.push(new GLatLng(data.north, data.west));
		ptarray.push(new GLatLng(data.north, data.east));
		ptarray.push(new GLatLng(data.south, data.east));
		ptarray.push(new GLatLng(data.south, data.west));
		polys.push(new GPolygon(ptarray, '#000000', 1, .1, '#0066CC', .5));
		marker = new GMarker(new GLatLng((data.north+data.south)/2, (data.west+data.east)/2), {icon:new GIcon(MultiRegionMapper.baseIcon), draggable:false});
	}
	
	//create the box
	dbox = document.createElement("DIV");
	dbox.className = MultiRegionMapper.prefix+"_regionBox";
	xbut = document.createElement('SPAN');
	dbox.appendChild(xbut);	
	circleMarker = document.createElement('IMG');
	dbox.appendChild(circleMarker);
	
	typeInput = document.createElement("INPUT");
	typeInput.type = "hidden";
	typeInput.value = type;
	dbox.appendChild(typeInput);
	descInput = document.createElement("INPUT");
	descInput.type="hidden";
	descInput.value=description;
	dbox.appendChild(descInput);

	//maybe do something different for describing states? 
	if (type == MultiRegionMapper.STATE){
		dbox.appendChild(function(){var t = document.createElement('SPAN'); t.innerHTML=description; return t;}());
		stateCodeInput = document.createElement("INPUT");
		stateCodeInput.type = "hidden";
		stateCodeInput.value = data.abr;
		dbox.appendChild(stateCodeInput);
	} else {
		dbox.appendChild(function(){var t = document.createElement('SPAN'); t.innerHTML=description; return t;}());
		westInput = document.createElement("INPUT");
		westInput.type = "hidden";
		westInput.value = data.west;
		dbox.appendChild(westInput);
		eastInput = document.createElement("INPUT");
		eastInput.type = "hidden";
		eastInput.value = data.east;
		dbox.appendChild(eastInput);
		northInput = document.createElement("INPUT");
		northInput.type = "hidden";
		northInput.value = data.north;
		dbox.appendChild(northInput);
		southInput = document.createElement("INPUT");
		southInput.type = "hidden";
		southInput.value = data.south;
		dbox.appendChild(southInput);
	}
		
	this.getDbox = function(){ return dbox; }
	this.getPolys = function(){ return polys; }
	this.changeIndex(nindex);
}

//region types
MultiRegionMapper.STATE = 1;
MultiRegionMapper.BOX = 2;

MultiRegionMapper.prefix = 'multiRegionMapper';
//static baseIcon variable
MultiRegionMapper.baseIcon = new GIcon(G_DEFAULT_ICON);
MultiRegionMapper.baseIcon.shadow = "http://www.google.com/mapfiles/shadow50.png";
MultiRegionMapper.baseIcon.iconSize = new GSize(20, 34);
MultiRegionMapper.baseIcon.shadowSize = new GSize(37, 34);
MultiRegionMapper.baseIcon.iconAnchor = new GPoint(9, 34);
MultiRegionMapper.baseIcon.infoWindowAnchor = new GPoint(9, 2);
