var CityGuesser =
{
	MapElement: null,
	LocationGuessElement: null,
	SubmitGuessElement: null,
	GiveUpElement: null,
	GuessCountElement: null,
	FormElement: null,
	PreviousScoresElement: null,
	LoadCitiesUrl: null,
	MaxDistance: 1000,
	NearColor: 0xFFFFFF,
	FarColor: 0x000000,
	MapZoom: 10,
	_guessCount: 0,
	_geocoder: null,
	_map: null,
	_cities: null,
	_currentCity: null,
	
	Initialize: function()
	{
		if (!GBrowserIsCompatible())
		{
			alert('Your browser does not support Google Maps.');
		}
		CityGuesser._map = new GMap2(CityGuesser.MapElement, { mapTypes: [ G_SATELLITE_MAP ] });
		CityGuesser._map.disableDragging();
		CityGuesser._geocoder = new GClientGeocoder();
		CityGuesser._UpdateGuessCount();
		CityGuesser._SetFormEnabled(false);
		//CityGuesser.SubmitGuessElement.addEvent('click', CityGuesser._SubmitGuess);
		CityGuesser.FormElement.addEvent('submit', CityGuesser._SubmitGuess);
		CityGuesser._LoadCities();
	},
	
	_LoadCities: function()
	{
		var r = new Request.JSON({ url: CityGuesser.LoadCitiesUrl });
		r.setHeader('Accept', 'text/javascript, text/html, */*');
		r.addEvent('onSuccess', CityGuesser._OnCitiesLoaded);
		r.addEvent('onFailure', function(xhr) { alert('Unable to load cities') });
		r.post();
	},
	
	_OnCitiesLoaded: function(data, text)
	{
		data = JSON.decode(text);
		if(data.error)
		{
			alert('Unable to load cities');
		}
		else
		{
			CityGuesser._cities = data.locations;
			CityGuesser._ResetMap();
		}
	},

	_SubmitGuess: function(e)
	{
		e = new Event(e);
		e.preventDefault();
		if(CityGuesser.LocationGuessElement.value)
		{
			CityGuesser._SetFormEnabled(false);
			CityGuesser._LoadMapLocation(CityGuesser.LocationGuessElement.value);
		}
	},
	
	_ResetMap: function()
	{
		CityGuesser._SetFormEnabled(false);
		var index = Math.floor(CityGuesser._cities.length * Math.random());
		CityGuesser._currentCity = CityGuesser._cities[index];
		CityGuesser._cities.splice(index, 1);
		CityGuesser._map.setCenter(new GLatLng(CityGuesser._currentCity.latitude, CityGuesser._currentCity.longitude), CityGuesser._currentCity.zoom);
		CityGuesser._SetFormEnabled(true);
	},
	
	_SetFormEnabled: function(enabled)
	{
		CityGuesser.GiveUpElement.removeEvents();
		CityGuesser.SubmitGuessElement.disabled = CityGuesser.LocationGuessElement.disabled = !enabled;
		if(enabled)
		{
			CityGuesser.GiveUpElement.addEvent('click', CityGuesser._OnClickGiveUp);
			CityGuesser.LocationGuessElement.focus();
		}
	},

	_OnClickGiveUp: function(e)
	{
		e = new Event(e);
		e.preventDefault();
		CityGuesser._EndGame(false);
	},
	
	_UpdateGuessCount: function()
	{
		CityGuesser.GuessCountElement.innerHTML = CityGuesser._guessCount;
	},

	_LoadMapLocation: function(name, point)
	{
		CityGuesser._geocoder.getLatLng(name, function(point) { CityGuesser._OnMapLocationLoaded(name, point); });
	},

	_OnMapLocationLoaded: function(name, point)
	{
		CityGuesser.LocationGuessElement.value = '';
		if(!point)
		{
			alert('Could not find location: ' + name);
			CityGuesser._SetFormEnabled(true);
		}
		else if(CityGuesser._map.getBounds().contains(point))
		{
			alert('Correct!');
			CityGuesser._EndGame(true);
		}
		else
		{
			var center = CityGuesser._map.getCenter();
			var color = CityGuesser._DecToHexColor(CityGuesser._LerpRGB(point.distanceFrom(center) / (1000 * CityGuesser.MaxDistance),
				CityGuesser.NearColor, CityGuesser.FarColor));
			CityGuesser._map.addOverlay(new LocationLabel(name, point, center, color));
			CityGuesser._guessCount++;
			CityGuesser._SetFormEnabled(true);
		}
		CityGuesser._UpdateGuessCount();
		CityGuesser.LocationGuessElement.focus();
	},
	
	_EndGame: function(win)
	{
		var tr = document.createElement('tr');
		var tdCity = document.createElement('td');
		tr.appendChild(tdCity);
		var tdScore = document.createElement('td');
		tr.appendChild(tdScore);
		tdCity.innerHTML = CityGuesser._currentCity.name;
		tdScore.innerHTML = win ? (CityGuesser._guessCount + 1) : '&#8734;';
		var parent = CityGuesser.PreviousScoresElement.getElement('tbody') ||  CityGuesser.PreviousScoresElement;
		parent.appendChild(tr);
		CityGuesser._map.clearOverlays();
		CityGuesser._ResetMap();
		CityGuesser._guessCount = 0;
		CityGuesser._UpdateGuessCount();
	},

	_LerpRGB: function(k, color1, color2)
	{
		k = Math.max(0, Math.min(1, k));
		var r1 = (color1 & 0xFF0000) >> 16;
		var g1 = (color1 & 0xFF00) >> 8;
		var b1 = (color1 & 0xFF);
		var r2 = (color2 & 0xFF0000) >> 16;
		var g2 = (color2 & 0xFF00) >> 8;
		var b2 = (color2 & 0xFF);
		return ((r1 + k * (r2 - r1)) << 16) + ((g1 + k * (g2 - g1)) << 8) + Math.round(b1 + k * (b2 - b1));
	},

	_DecToHexColor: function(dec)
	{
		var hex = '000000' + dec.toString(16);
		return '#' + hex.substr(hex.length - 6, 6);
	}
};

function LocationLabel(name, position, centerPoint, color)
{
	this._name = name;
	this._position = position;
	this._center = centerPoint;
	this._element = null;
	this._polyline = null;
	this._map = null;
	this._color = color;
}

LocationLabel.prototype = new GOverlay;

LocationLabel.prototype.initialize = function(map)
{
	this._map = map;
	this._element = document.createElement("div");
	this._element.className = LocationLabel.ElementCssClass;
	this._element.style.visibility = 'hidden';
	this._element.innerHTML = this._name;
	this._element.style.backgroundColor = this._color;
	var pane = map.getPane(G_MAP_MARKER_PANE);
	pane.appendChild(this._element);
};

LocationLabel.ElementCssClass = null;
LocationLabel.Margin = { top: 0, bottom: 0, left: 0, right: 0 };
LocationLabel.LinePadding = 0;

LocationLabel.prototype.redraw = function(force)
{
	// get map bounds
	var bounds = this._map.getBounds();
	// contract bounds by the padding on each side
	var margin = LocationLabel.Margin;
	var linePadding = LocationLabel.LinePadding;
	var swPoint = this._map.fromLatLngToDivPixel(bounds.getSouthWest());
	var paddedSwPoint = new GPoint(swPoint.x + margin.left + linePadding, swPoint.y - margin.bottom - linePadding);
	var nePoint = this._map.fromLatLngToDivPixel(bounds.getNorthEast());
	var paddedNePoint = new GPoint(nePoint.x - margin.right - linePadding, nePoint.y + margin.top + linePadding);
	var lineBounds = new GLatLngBounds(this._map.fromDivPixelToLatLng(paddedSwPoint), this._map.fromDivPixelToLatLng(paddedNePoint));
	var labelLatLng;
	if(!lineBounds.containsLatLng(this._position))
	{
		// position is outside the map bounds - move it to the closest edge
		var center = { x: this._center.lat(), y: this._center.lng() };
		// get the distance from the map center to the position
		var dx = this._position.lat() - center.x;
		var dy = this._position.lng() - center.y;
		// find the smallest ratio of distance from the map center to the postion to distance from the map center to the intersecting edge
		var rx = dx < 0 ? center.x - lineBounds.getSouthWest().lat() : lineBounds.getNorthEast().lat() - center.x;
		rx = Math.abs(rx / dx);
		var ry = dy < 0 ? center.y - lineBounds.getSouthWest().lng() : lineBounds.getNorthEast().lng() - center.y;
		ry = Math.abs(ry / dy);
		var r = Math.min(rx, ry);
		labelLatLng = new GLatLng(center.x + r * dx, center.y + r * dy);
	}
	else
	{
		// position is inside the map bounds
		labelLatLng = this._position;
	}
	var labelPos = this._map.fromLatLngToDivPixel(labelLatLng);
	var w = this._element.offsetWidth;
	var h = this._element.offsetHeight;
	// center label on the position
	labelPos.x -= w / 2;
	labelPos.y -= h / 2;
	// ensure that the label fits in the map
	labelPos.x = Math.min(nePoint.x - w - margin.right, Math.max(swPoint.x + margin.left, labelPos.x));
	labelPos.y = Math.min(swPoint.y - h - margin.bottom, Math.max(nePoint.y + margin.top, labelPos.y));
	// move the label
	this._element.style.left = labelPos.x + "px";
	this._element.style.top = labelPos.y + "px";
	this._element.style.visibility = 'visible';
	// create a polyline from the center of the map to the label
	if(this._polyline)
	{
		this._map.removeOverlay(this._polyline);
	}
	this._polyline = new GPolyline([ this._center, labelLatLng ], this._color, 4, 0.5);
	this._map.addOverlay(this._polyline);
};

LocationLabel.prototype.remove = function()
{
	//this._map.removeOverlay(this._polyline);
	this._map.getPane(G_MAP_MARKER_PANE).removeChild(this._element);
};

