﻿//---------------------------------------------------------------------
// Dev Notes
//---------------------------------------------------------------------
// 2008-Sep-09:	Experimented with Google Suggest style city entry.
//				Decided there are just too many suggestions (from AccuWeather)
//				to easily handle this.
// 2008-Sep-21:	Why doesn't weatherPage.title show up on Weather Page reload?
// 2008-Sep-29: 1) Bad CSS on details page: image align, word-wrap, width 50%
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// Global variables
//---------------------------------------------------------------------

// Get the outermost JSON expression from a Web Service response
var reGetJSON = new RegExp("({.*})");

// The client-side data storage of cities
var cities;

// positions for weather columns (with default values)
var graphWidth  = 300;
var graphHeight = 300;
var numColumns  = 6;
var columnWidth = graphWidth / numColumns;

// The weather image tag
var weatherImage;
var radarImage;

//---------------------------------------------------------------------
// City class
//---------------------------------------------------------------------
function City(id, city, state)
{
	this.id = id;
	this.city = city;
	this.state = state;
}

City.prototype.id;
City.prototype.city;
City.prototype.state;

City.prototype.name = function()
{
	return this.city + ", " + this.state;
}

City.prototype.toString = function()
{
	return '(' + this.id + ',' + this.city + ',' + this.state + ')';
}

//---------------------------------------------------------------------
// Cities class
//---------------------------------------------------------------------
function Cities(initializeDoneEventHandler, layerName)
{
	this.db = null;
	this.hash = new Object();
	this.initializeDoneEvent = new Event();
	this.layerName = layerName;
	this.initializeDoneEvent.addListener(this, 'initialize', initializeDoneEventHandler);

	try
	{
		if (window.openDatabase)
		{
			this.db = openDatabase('Cities', '1.0', 'Tafiya Weather Cities Database', 20000);
			//console.log("Cities: database created");
			this.initialize();
		}
		else
		{
			console.log("Cities: javascript database not supported");
		}
	}
	catch(e)
	{
		console.log("Cities: Unknown error opening Cities database: " + e);
	}
}

// Client-side data storage of cities
Cities.prototype.db;

// In-memory array of cities
Cities.prototype.hash;

// Events
Cities.prototype.initializeDoneEvent;
Cities.prototype.layerName;

// Currently selected city
Cities.prototype.selectedCity;

// Next SQL function
Cities.prototype.nextSqlFunction;

function DBnullDataHandler(transaction, results)
{
	//console.log("DBnullDataHandler");
	if (cities.nextSqlFunction)
		cities.nextSqlFunction();
}

function DBerrorHandler(transaction, error)
{
	//console.log("DBerrorHandler");
	if (error.message != "table cities already exists" && error.message != "table current_city already exists")
	{
		console.log('Cities.DBerrorHandler: ' + error.message + ' (' + error.code + ')');
	}
	if (cities.nextSqlFunction)
		cities.nextSqlFunction();
	return true;
}

Cities.prototype.initialize = function()
{
	this.initializeCreateCitiesTable();
}

Cities.prototype.initializeCreateCitiesTable = function()
{
	this.nextSqlFunction = this.initializeCreateCurrentCityTable;
	this.db.transaction(
		function (transaction)
		{
			//console.log("Cities.initializeCreateCitiesTable");
			transaction.executeSql(
				'CREATE TABLE cities(' +
					'id TEXT NOT NULL PRIMARY KEY,' +
					'city TEXT NOT NULL,' +
					'state TEXT NOT NULL);',
				[], DBnullDataHandler, DBerrorHandler);
		}
	);
}

Cities.prototype.initializeCreateCurrentCityTable = function()
{
	this.nextSqlFunction = this.initializeReadCities;
	this.db.transaction(
		function (transaction)
		{
			//console.log("Cities.initializeCreateCurrentCityTable");
			transaction.executeSql(
				'CREATE TABLE current_city(id TEXT NOT NULL);',
				[], DBnullDataHandler, DBerrorHandler);
		}
	);
}

Cities.prototype.initializeReadCities = function()
{
	this.nextSqlFunction = this.initializeReadCurrentCity;
	this.db.transaction(
		function (transaction)
		{
			//console.log("Cities.initializeReadCities");
			transaction.executeSql("SELECT * from cities;",
				[ ], // array of values for the ? placeholders
				function (transaction, results)
				{
					//console.log("Cities.initializeReadCities: results.rows.length=" + results.rows.length);
					for (var i=0; i<results.rows.length; i++)
					{
						var row = results.rows.item(i);
						var city = new City(row['id'], row['city'], row['state']);
						cities.hash[city.id] = city;
						//console.log("Cities.initializeReadCities: cities.hash[" + city.id + "]=" + cities.hash[city.id]);
					}
					cities.assertDatabaseMatchesHash("Cities.initializeReadCities:");
					cities.initializeReadCurrentCity();
				},
				DBerrorHandler);
		}
	);
}

Cities.prototype.initializeReadCurrentCity = function()
{
	this.nextSqlFunction = null;
	this.db.transaction(
		function (transaction)
		{
			//console.log("Cities.initializeReadCurrentCity");
			transaction.executeSql("SELECT * from current_city;",
				[ ], // array of values for the ? placeholders
				function (transaction, results)
				{
					for (var i=0; i<results.rows.length; i++)
					{
						var row = results.rows.item(i);
						cities.selectedCity = cities.hash[row['id']];
						//console.log("Cities.initializeReadCurrentCity: selectedCity=" + cities.selectedCity);
					}
					//console.log("Cities.initializeReadCurrentCity: firing initializeDoneEvent");
					cities.initializeDoneEvent.fireEvent(null, cities, 'initialize', cities.layerName);
				},
				DBerrorHandler);
		}
	);
}

Cities.prototype.cityExists = function(id)
{
	//console.log("Cities.cityExists(" + id + "): this.hash[" + id + "]=" + this.hash[id]);
	return this.hash[id] != undefined;
}

Cities.prototype.addCity = function(city)
{
	var cityExists = this.cityExists(city.id);
	if (cityExists)
		return;
		
	this.hash[city.id] = city;
	//console.log("Cities.addCity: this.hash[" + city.id + "]=" + this.hash[city.id]);

	this.nextSqlFunction = null;
	this.db.transaction(
		function (transaction)
		{
			transaction.executeSql('insert into cities (id, city, state) VALUES (?, ?, ?);',
				[city.id, city.city, city.state], DBnullDataHandler, DBerrorHandler);
			cities.assertDatabaseMatchesHash("Cities.addCity:");
		}
	);
}

Cities.prototype.removeCity = function(cityId)
{
	delete this.hash[cityId];
	
	// Delete city from database
	this.nextSqlFunction = null;
	this.db.transaction(
		function (transaction)
		{
			transaction.executeSql('delete from cities where id = ?;',
				[cityId], DBnullDataHandler, DBerrorHandler);
			cities.assertDatabaseMatchesHash("Cities.removeCity:");
		}
	);
}

Cities.prototype.eachCity = function(func)
{
	for (var id in this.hash)
		func(this.hash[id]);
}

Cities.prototype.length = function()
{
	var length = 0;
	for (var id in this.hash)
		length++;
	return length;
}

Cities.prototype.assertDatabaseMatchesHash = function(prefix)
{
	//console.log("Cities.assertDatabaseMatchesHash: " + prefix);
	this.nextSqlFunction = null;
	this.db.transaction(
		function (transaction)
		{
			transaction.executeSql("SELECT * from cities;",
				[ ], // array of values for the ? placeholders
				function (transaction, results)
				{
					assert(cities.length() == results.rows.length,
						"  cities.length(" + cities.length() + ") != results.rows.length(" + results.rows.length + ")");
					for (var i=0; i<results.rows.length; i++)
					{
						var row = results.rows.item(i);
						var dbCity = new City(row['id'], row['city'], row['state']);
						assertCityIsValid(dbCity, "dbCity");
						var hashCity = cities.hash[dbCity.id];
						assertCityIsValid(hashCity, "hashCity");
						assert(dbCity.id == hashCity.id, "  dbCity.id != hashCity.id");
						assert(dbCity.city == hashCity.city, "  dbCity.city != hashCity.city");
						assert(dbCity.state == hashCity.state, "  dbCity.state != hashCity.state");
						//console.log('  id=' + dbCity.id + ' city=' + dbCity.city + ' state=' + dbCity.state);
					}
					//console.log("Cities.assertDatabaseMatchesHash: Done");
				},
				DBerrorHandler);
		}
	);
}

function assertCityIsValid(city, cityVarName)
{
	assert(city != null, "  assertCityIsValid: " + cityVarName + " is null");
	assert(city != undefined, "  assertCityIsValid: " + cityVarName + " is undefined");
	assert(city.id != null, "  assertCityIsValid: " + cityVarName + ".id is null");
	assert(city.id != undefined, "  assertCityIsValid: " + cityVarName + ".id is undefined");
	assert(city.city != null, "  assertCityIsValid: " + cityVarName + ".city is null");
	assert(city.city != undefined, "  assertCityIsValid: " + cityVarName + ".city is undefined");
	assert(city.state != null, "  assertCityIsValid: " + cityVarName + ".state is null");
	assert(city.state != undefined, "  assertCityIsValid: " + cityVarName + ".state is undefined");
}

function assert(condition, message)
{
	if (!condition)
		console.log(message);
}

Cities.prototype.setSelectedCity = function(city)
{
	//console.log("Cities.setSelectedCity: city=" + city);
	if (!this.cityExists(city.id))
		this.addCity(city)
	this.selectedCity = this.hash[city.id];
	this.deleteSelectedCity();
}

Cities.prototype.deleteSelectedCity = function()
{
	this.nextSqlFunction = this.insertSelectedCity;
	this.db.transaction(
		function (transaction)
		{
			//console.log("Cities.deleteSelectedCity");
			transaction.executeSql('DELETE FROM current_city;',
				[], DBnullDataHandler, DBerrorHandler);
		}
	);
}

Cities.prototype.insertSelectedCity = function()
{
	this.nextSqlFunction = this.assertSelectedCity;
	this.db.transaction(
		function (transaction)
		{
			//console.log("Cities.insertSelectedCity: city=" + cities.selectedCity);
			transaction.executeSql('insert into current_city (id) VALUES (?);',
				[cities.selectedCity.id], DBnullDataHandler, DBerrorHandler);
		}
	);
}

Cities.prototype.assertSelectedCity = function()
{
	this.nextSqlFunction = null;
	this.db.transaction(
		function (transaction)
		{
			//console.log("Cities.assertSelectedCity");
			transaction.executeSql("SELECT * from current_city;",
				[ ], // array of values for the ? placeholders
				function (transaction, results)
				{
					for (var i=0; i<results.rows.length; i++)
					{
						var row = results.rows.item(i);
						assert(cities.selectedCity.id == row['id'], "selectedCity bad in database");
					}
				},
				DBerrorHandler);
		}
	);
}

//---------------------------------------------------------------------
// Event handlers
//---------------------------------------------------------------------

WebApp.AddEventListener("beginslide", DispatchBeginSlideEvent);	
WebApp.AddEventListener("load", HandleWebAppLoadEvent);

function HandleWebAppLoadEvent(e)
{
	weatherImage = document.getElementById('weatherImg');
	radarImage = document.getElementById('radarImg');
	graphWidth  = weatherImage.width;
	graphHeight = weatherImage.height;
	numColumns  = 6;
	columnWidth = graphWidth / numColumns;
	
	if (e.context[0] === undefined)
		cities = new Cities(PrepareLayer, "waMyCities");
	else
		cities = new Cities(PrepareLayer, e.context[0]);
}

function DispatchBeginSlideEvent(e)
{
	PrepareLayer(e, e.context[1]);
}

function PrepareLayer(e, layerName)
{
	console.log("PrepareLayer: layerName=" + layerName);
	if (layerName == "waEditCities")
		LoadEditCities();
	else if (layerName == "waChooseCity")
		LoadChooseCity();
	else if (layerName == "waWeatherGraph")
		LoadWeatherGraph();
	else if (layerName == "waWeatherDetails")
		LoadWeatherGraph();
	else if (layerName == "waRadarMap")
		LoadWeatherGraph();
	else if (cities.length() == 0)
		location.href = "#_Search";
	else
		LoadMyCities();
}

function HandleGraphClickEvent(e)
{
	if (e.clientY <= graphHeight && e.clientX <= graphWidth)
	{
		var column = Math.floor(e.clientX / columnWidth);
		location.href = "#_WeatherDetails/" + column.toString();
	}
}

//---------------------------------------------------------------------
// Functions
//---------------------------------------------------------------------

//-------------------- My Cities layer --------------------------------
function LoadMyCities()
{
	var ul = document.getElementById('MyCitiesList');
	ul.innerHTML = "";
	cities.eachCity(AppendMyCitiesListItem);
	//console.log("LoadMyCities: Done");
	return true;
}

function AppendMyCitiesListItem(city)
{
	var ul = document.getElementById('MyCitiesList');
	ul.appendChild(Element('li',
		Element('a',
			Attribute("href", "#_WeatherGraph"),
			Attribute("onclick", "cities.setSelectedCity(new City('" + city.id + "','" + city.city + "','" + city.state + "'))"),
			city.name())));
}

//-------------------- Edit Cities layer ------------------------------
function LoadEditCities()
{
	var ul = document.getElementById('EditCitiesList');
	ul.innerHTML = "";
	cities.eachCity(AppendEditCitiesListItem);
	//console.log("LoadEditCities: Done");
	return true;
}

function AppendEditCitiesListItem(city)
{
	var ul = document.getElementById('EditCitiesList');
	//<li>city, state <a href="">Delete</a></li>
	var li = Element('li', city.name());
	//console.log("AppendEditCitiesListItem: li=" + li.innerHTML);
	li.appendChild(Element('a',
		Attribute("href", "#_EditCities"),
		Attribute("class", "iButton iBWarn"),
		Attribute("onclick", "cities.removeCity('" + city.id + "');LoadMyCities(); return LoadEditCities()"),
		"Delete"));
	//console.log("AppendEditCitiesListItem: li=" + li.innerHTML);
	ul.appendChild(li);
}

//-------------------- Choose City layer ------------------------------
function LoadChooseCity()
{
	var cityEntry = document.getElementById('cityEntry').value;
	if (cityEntry.length == 0) 
	{
		alert('Please enter a US city name or postal code');
		WA.Back();
		return false;
	}
	
	var url = "/WebServiceProxy.asmx/ServiceRequest?url=" + encodeURIComponent(
		'http://tafiy.accu-weather.com/widget/tafiy/city-find.asp?location=' + cityEntry);
	Ajax(url, ProcessCityFindResponse, AjaxRequestFailure);
	//console.log("LoadChooseCity()");
	return true;
}

function ProcessCityFindResponse(request)
{
	// Remove current city list
	var ul = document.getElementById('ChooseCityList');
	while (ul.childNodes.length)
		ul.removeChild(ul.firstChild);
		
	var resultsArray = reGetJSON.exec(request.responseText);
	if (resultsArray === undefined || resultsArray == null)
	{
		// No response from server
		alert('Sorry, we couldn\'t find this city:\n' +
		entityify(document.getElementById('cityEntry').value) +
		'\nPlease try again');
		console.log("ProcessCityFindResponse: status=" + request.status);
		console.log("ProcessCityFindResponse: statusText=" + request.statusText);
		console.log("ProcessCityFindResponse: responseText=" + request.responseText);
	}
	else
	{
		var json = eval('(' + resultsArray[0] + ')');
		var searchResults = json.adc_database.citylist;
		if (searchResults.location === undefined || searchResults.location == null)
		{
			// AccuWeather didn't find this city
			alert('Sorry, we don\'t recognize this city:\n' +
			entityify(document.getElementById('cityEntry').value) +
			'\nPlease search for a different city');
			console.log("ProcessCityFindResponse: status=" + request.status);
			console.log("ProcessCityFindResponse: statusText=" + request.statusText);
			console.log("ProcessCityFindResponse: responseText=" + request.responseText);
		}
		else if (isArray(searchResults.location))
		{
			while (searchResults.location.length)
			{
				var location = searchResults.location.shift();
				var cityId = location.location;
				var cityName = location.city;
				var stateName = location.state;
				var city = new City(cityId, cityName, stateName);
				AppendChooseCityListItem(city);
			}
		}
		else
		{
			var cityId = searchResults.location.location;
			var cityName = searchResults.location.city;
			var stateName = searchResults.location.state;
			var city = new City(cityId, cityName, stateName);
			AppendChooseCityListItem(city);
		}
	}
}

function AjaxRequestFailure(request)
{
	alert("Weather data request failed");
	console.log("AjaxRequestFailure: status=" + request.status);
	console.log("AjaxRequestFailure: statusText=" + request.statusText);
	console.log("AjaxRequestFailure: responseText=" + request.responseText);
}

function AppendChooseCityListItem(city)
{
	var ul = document.getElementById('ChooseCityList');
	ul.appendChild(Element('li',
		Element('a',
			Attribute("href", "#_WeatherGraph"),
			Attribute("onclick", "cities.setSelectedCity(new City('" + city.id + "','" + city.city + "','" + city.state + "'))"),
			city.name())));
}

function EnterCityInForm(city)
{
	document.getElementById('cityEntry').value = city;
}

//-------------------- Weather Graph layer ------------------------------
function LoadWeatherGraph()
{
	//console.log("LoadWeatherGraph: selectedCity=" + cities.selectedCity);

	// Add city to data store
	var city = cities.selectedCity;
	cities.addCity(city);

	// Prepare weather page for this city
	var weatherPage = document.getElementById('waWeatherGraph');
	weatherPage.title = city.name();
	weatherImage.id = city.id;
	
	// Prepare radar map page for this city
	var radarMap = document.getElementById('waRadarMap');
	radarMap.title = city.name();

	// Fetch the weather for this city
	var url = "/WeatherPage.asmx/GetWeatherAsJson" +
		"?id=" + city.id +
		"&city=" + encodeURIComponent(city.city) +
		"&state=" + encodeURIComponent(city.state) +
		"&width=" + graphWidth +
		"&height=" + graphHeight;
	Ajax(url, ProcessWeatherResponse, AjaxRequestFailure);
	return true;
}

var aw;

function ProcessWeatherResponse(request)
{
	var resultsArray = reGetJSON.exec(request.responseText);
	if (resultsArray === undefined || resultsArray == null)
	{
		weatherImage.src = "";
		radarImage.src = "";
		alert("Weather forecast is not available.\nPlease try again later.");
		console.log("ProcessWeatherResponse: status=" + request.status);
		console.log("ProcessWeatherResponse: statusText=" + request.statusText);
		console.log("ProcessWeatherResponse: responseText=" + request.responseText);
	}
	else
	{
		var json = eval('(' + resultsArray[0] + ')');
		aw = json.adc_database;
		// Update image tag to reference the weather graph just constructed on the server
		weatherImage.src = "/images/weathergraph/" + weatherImage.id + ".jpg";
		weatherImage.onclick = HandleGraphClickEvent;
		
		// Update Radar Map
		radarImage.src = aw.images.radar;

		setTimeout(UpdateWeatherDetails, 10, aw);
		setTimeout(scrollTo, 1, 0, 1);
	}
}

//-------------------- Weather Details layer ------------------------------
function UpdateWeatherDetails(aw)
{
	var city = cities.selectedCity;
	var panel = document.getElementById('weatherDetails');
	panel.innerHTML = "";

	panel.appendChild(
		Element("a",
			Attribute("name", "0"),
			Attribute("class", "detailsTitle"),
			"Now"));

	var table =
		Element("table",
			Attribute("class", "detailsTable"),
			Attribute("cellspacing", "0"),
			Attribute("cellpadding", "5"));
	panel.appendChild(table);

	var tr = Element("tr");
	table.appendChild(tr);
	tr.appendChild(
		Element("td",
			Attribute("class", "tdleft"),
			Element("img",
				Attribute("src", "/images/weatherlow/" + aw.currentconditions.weathericon + ".gif"),
				Attribute("width", "75"),
				Attribute("height", "65"))));
	var td = Element("td",
		Attribute("class", "tdright"));
	tr.appendChild(td);
	td.appendChild(Element("div", aw.currentconditions.temperature + "&#176;" + aw.units.temp));
	td.appendChild(Element("div", aw.currentconditions.weathertext));

	AddTableRow(table, "Real Feel:",	aw.currentconditions.realfeel + "&#176;" + aw.units.temp);
	AddTableRow(table, "Humidity:",		aw.currentconditions.humidity);
	AddTableRow(table, "Dew Point:",	aw.currentconditions.dewpoint + "&#176;" + aw.units.temp);
	AddTableRow(table, "Precipitation:",aw.currentconditions.precip + " " + aw.units.prec);
	AddTableRow(table, "Wind:",			aw.currentconditions.windspeed + " " + aw.units.speed + " " +
										aw.currentconditions.winddirection);
	AddTableRow(table, "Pressure:",		aw.currentconditions.pressure.value + " " + aw.units.pres + " (" +
										aw.currentconditions.pressure.state + ")");
	AddTableRow(table, "Visibility:",	aw.currentconditions.visibility + " " + aw.units.dist);
	AddTableRow(table, "Cloud Cover:",	aw.currentconditions.cloudcover);
	AddTableRow(table, "UV Index:",		aw.currentconditions.uvindex.value);
	AddTableRow(table, "Sun Rise:",		aw.planets.sun.rise);
	AddTableRow(table, "Sun Set:",		aw.planets.sun.set);
	AddTableRow(table, "Moon Rise:",	aw.planets.moon.rise);
	AddTableRow(table, "Moon Set:",		aw.planets.moon.set);
	AddTableRow(table, "Latitude:",		aw.local.lat);
	AddTableRow(table, "Longitude:",	aw.local.lon);

	for (var i = 0; i < aw.forecast.day.length; i++)
	{
		var fd = aw.forecast.day[i];

		for (var j = 0; j < 2; j++)
		{
			var fdp = (j == 0) ? fd.daytime : fd.nighttime;

			// Anchor for linking directly to this day part
			panel.appendChild(
				Element("a",
					Attribute("name", fdp.anchorname),
					Attribute("class", "detailsTitle"),
					fdp.dayname));

			var table =
				Element("table",
					Attribute("class", "detailsTable"),
					Attribute("cellspacing", "0"),
					Attribute("cellpadding", "5"));
			panel.appendChild(table);

			var tr = Element("tr");
			table.appendChild(tr);
			tr.appendChild(
				Element("td",
					Attribute("class", "tdleft"), 
					Element("img",
						Attribute("src", "/images/weatherlow/" + fdp.weathericon + ".gif"),
						Attribute("width", "75"),
						Attribute("height", "65"))));
			tr.appendChild(
				Element("td",
					Attribute("class", "tdright"),
					fdp.txtlong));
			
			AddTableRow(table, "High Temp:", fdp.hightemperature + "&#176;" + aw.units.temp);
			AddTableRow(table, "Low Temp:", fdp.lowtemperature + "&#176;" + aw.units.temp);
			AddTableRow(table, "Real Feel High:", fdp.realfeelhigh + "&#176;" + aw.units.temp);
			AddTableRow(table, "Real Feel Low:", fdp.realfeellow + "&#176;" + aw.units.temp);
			AddTableRow(table, "Wind:", fdp.windspeed + " " + aw.units.speed + " " + fdp.winddirection);
			AddTableRow(table, "Wind Gust:", fdp.windspeed + aw.units.speed);
			AddTableRow(table, "Rain:", fdp.rainamount + " " + aw.units.prec);
			AddTableRow(table, "Snow:", fdp.snowamount + " " + aw.units.prec);
			AddTableRow(table, "Ice:", fdp.iceamount + " " + aw.units.prec);
			AddTableRow(table, "Total Precipitation:", fdp.precipamount + " " + aw.units.prec);
			AddTableRow(table, "Prob of Thunderstorms:", fdp.tstormprob + "%");

			if (j == 0)
			{
				AddTableRow(table, "Sun Rise:", fd.sunrise);
				AddTableRow(table, "Sun Set:", fd.sunset);
			}
		}
	}
}

function AddTableRow(table, caption, value)
{
	var tr = Element("tr");
	table.appendChild(tr);
	tr.appendChild(Element("td",
		Attribute("class", "tdleft"),
		caption));
	tr.appendChild(Element("td",
		Attribute("class", "tdright"),
		value));
}
