//https://www.weatherbit.io/account/create
var weatherbitApiKey = ""

// https://developer.mapquest.com/user/me/plan
var mapQuestApiKey = ""


//-------------------------------------------------------------------------------------

/*
This Apple widget originally (at different times) pulled data from accuweather or weather.com. Both APIs died in 2019.
I (Wowfunhappy) have heavily modified this file to use the Weatherbit API instead.

Copyright ＿ 2005, Apple Computer, Inc.  All rights reserved.
NOTE:  Use of this source code is subject to the terms of the Software
License Agreement for Mac OS X, which accompanies the code.  Your use
of this source code signifies your agreement to such license terms and
conditions.  Except as expressly granted in the Software License Agreement
for Mac OS X, no other copyright, patent, or other intellectual property
license or right is granted, either expressly or by implication, by Apple.
*/

var accuweatherIcons = 
[
	["sun"], 					// 1 Sunny
	["sun"],					// 2 Mostly Sunny
	["partlycloudy"],				// 3 Partly Sunny
	["partlycloudy"],				// 4 Intermittent Clouds
	["sun", "haze"],				// 5 Hazy Sunshine
	["partlycloudy"],				// 6 Mostly Cloudy
	["clouds"],					// 7 Cloudy (am/pm)
	["clouds"],					// 8 Dreary (am/pm)
	null,						// 9 retired
	null,						// 10 retired
	["fog"],					// 11 fog (am/pm)
	["rain"],					// 12 showers (am/pnm)
	["rain&clouds"],				// 13 Mostly Cloudy with Showers
	["rain&sun"],					// 14 Partly Sunny with Showers
	["lightening"],					// 15 Thunderstorms (am/pm)
	["lightening"],					// 16 Mostly Cloudy with Thunder Showers
	["lightening"],					// 17 Partly Sunnty with Thunder Showers
	["rain"],					// 18 Rain (am/pm)
	["flurries"],					// 19 Flurries (am/pm)
	["flurries"],					// 20 Mostly Cloudy with Flurries
	["flurries"],					// 21 Partly Sunny with Flurries
	["snow"],					// 22 Snow (am/pm)
	["snow"],					// 23 Mostly Cloudy with Snow
	["ice"],					// 24 Ice (am/pm)
	["hail"],					// 25 Sleet (am/pm)
	["hail"],					// 26 Freezing Rain (am/pm)
	null,						// 27 retired
	null,						// 28 retired
	["rain&snow"],					// 29 Rain and Snow Mixed (am/pm)
	["sun"],					// 30 Hot (am/pm)
	["sun"],					// 31 Cold (am/pm)
	["wind"],					// 32 Windy (am/pm)
	// Night only Icons
	["moon"],					// 33 Clear
	["moon"],					// 34 Mostly Clear
	["moon", "partlycomboclouds"],			// 35 Partly Cloudy
	["moon", "partlycomboclouds"],			// 36 Intermittent Clouds
	["moon", "haze"],				// 37 Hazy
	["moon", "partlycomboclouds"],			// 38 Mostly Cloudy
	["rain&clouds"],				// 39 Partly Cloudy with Showers
	["rain&clouds"], 				// 40 Mostly Cloudy with Showers
	["lightening"],					// 41 Partly Cloudy with Thunder Showers
	["lightening"],					// 42 Mostly Cloudy with Thunder Showers
	["snow"],					// 43 Mostly Cloudy with Flurries
	["snow"]					// 44 Mostly Cloudy with Flurries
];

var accuweatherMiniIcons = 
[
	"sun", 						// 1 Sunny
	"sun",						// 2 Mostly Sunny
	"suncloud",					// 3 Partly Sunny
	"suncloud",					// 4 Intermittent Clouds
	"sunhaze",					// 5 Hazy Sunshione
	"suncloud",					// 6 Mostly Cloudy
	"clouds",					// 7 Cloudy (am/pm)
	"clouds",					// 8 Dreary (am/pm)
	null,						// 9 retired
	null,						// 10 retired
	"fog",						// 11 fog (am/pm)
	"rain",						// 12 showers (am/pnm)
	"cloudrain",					// 13 Mostly Cloudy with Showers
	"sunrain",					// 14 Partly Sunny with Showers
	"lightening",					// 15 Thunderstorms (am/pm)
	"lightening",					// 16 Mostly Cloudy with Thunder Showers
	"lightening",					// 17 Partly Sunnty with Thunder Showers
	"rain",						// 18 Rain (am/pm)
	"flurries",					// 19 Flurries (am/pm)
	"flurries",					// 20 Mostly Cloudy with Flurries
	"flurries",					// 21 Partly Sunny with Flurries
	"snow",						// 22 Snow (am/pm)
	"snow",						// 23 Mostly Cloudy with Snow
	"ice",						// 24 Ice (am/pm)
	"hail",						// 25 Sleet (am/pm)
	"hail",						// 26 Freezing Rain (am/pm)
	null,						// 27 retired
	null,						// 28 retired
	"snowrain",					// 29 Rain and Snow Mixed (am/pm)
	"sun",						// 30 Hot (am/pm)
	"sun",						// 31 Cold (am/pm)
	"wind",						// 32 Windy (am/pm)
	// Night only Icons (shouldn't get these);
	null,						// 33 Clear
	null,						// 34 Mostly Clear
	null,						// 35 Partly Cloudy
	null,						// 36 Intermittent Clouds
	null,						// 37 Hazy
	null,						// 38 Mostly Cloudy
	null,						// 39 Partly Cloudy with Showers
	null,			 			// 40 Mostly Cloudy with Showers
	null,						// 41 Partly Cloudy with Thunder Showers
	null,						// 42 Mostly Cloudy with Thunder Showers
	null,						// 43 Mostly Cloudy with Flurries
	null						// 44 Mostly Cloudy with Flurries
];

//Experiment to find the right number. Don't reference the above arrays, those numbers are different. Don't understand why.
var getAccuweatherIconNumFromWeatherbitIcon = {
	
	//https://www.weatherbit.io/api/codes
	
	//Clear Sky
	"c01d": 1, //1
	"c01n": 33,
	
	//Partly Cloudy
	"c02d": 2, //2
	"c02n": 35,
	"c03d": 2,
	"c03n": 35,
	
	//Clouds
	"c03d": 7,
	"c03n": 7,
	"c04d": 7,
	"c04n": 7,
	
	//Thunderstorms
	"t01d": 15,
	"t01n": 15,
	"t02d": 15,
	"t02n": 15,
	"t03d": 15,
	"t03n": 15,
	"t04d": 15,
	"t04n": 15,
	"t05d": 15,
	"t05n": 15,
	
	//Drizzle, Light Showers
	"d01d": 12,
	"d01n": 12,
	"d02d": 12,
	"d02n": 12,
	"d03d": 12,
	"d03n": 12,
	"r04d": 12,
	"r04n": 12,
	"u00d": 12,
	
	//Rain, Moderate & Heavy Showers
	"r01d": 11,
	"r01n": 11,
	"r02d": 11,
	"r02n": 11,
	"r03d": 11,
	"r03n": 11,
	"r05d": 11,
	"r05n": 11,
	"r06d": 11,
	"r06n": 11,
	
	//Freezing Rain
	"f01d": 24,
	"f01n": 24,
	
	//Snow
	"s01d": 22,
	"s01n": 22,
	"s02d": 22,
	"s02n": 22,
	"s03d": 22,
	"s03n": 22,
	
	//Flurries
	"s06d": 19,
	"s06n": 19,
	
	//Snow & Rain, Sleet
	"s04d": 28,
	"s04n": 28,
	"s05d": 28,
	"s05n": 28,
	
	//Mist, Fog, Smoke
	"a01d": 10,
	"a01n": 10,
	"a02d": 10,
	"a02n": 10,
	"a05d": 10,
	"a05n": 10,
	"a06d": 10,
	"a06n": 10,
	
	//Haze, Sand/Dust
	"a03d": 4,
	"a03n": 36,
	"a04d": 4,
	"a04n": 36,
	
	//Inferred
	"wind": 31
};

var MoonMap=[1, 2, 3, 4, 5, 6, 7, 7, 8, 9, 10, 11, 12, 13, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23, 24];

var windThreshold = 25;

if (window.timerInterval != 300000)
	window.timerInterval = 300000; // 5 minutes




// returns an anonymous object like so
// object
//		error: 	Boolean false for success
//		errorString: failure string
//		hi:		Fahrenheit
//		lo: 		Fahrenheit
//		temp: 	Fahrenheit
//		icon	:	accuweather icon code
//		icons:	our icons to display
//		description:	accuweather description
//		city:	City (first caps)
//		time:	time 24 hours(nn:nn)
//		sunset:	time 24 hours (nn:nn)
//		sunrise: time 24 hours (nn:nn)
//		phases: array[7] of integers; -1 means no phase data 1-24
//		forcast: array[6] of anonymous objects like so
//			object
//				hi:		Fahrenheit
//				lo: 		Fahrenheit
//				icon:	accuweather icon code
//				ouricon:	our icon code to display
//				description: accuweather description
//				daycode:	(MON/TUE/WED/THU/FRI/SAT/SUN)

function fetchWeatherData (callback, lat, lon, name)
{	
	var currWeatherURL = 'http://api.weatherbit.io/v2.0/current?lat=' + lat + '&lon=' + lon + '&key=' + weatherbitApiKey + '&units=I'
	var forecastURL = 'http://api.weatherbit.io/v2.0/forecast/daily?lat=' + lat + '&lon=' + lon + '&key=' + weatherbitApiKey + '&units=I&days=6'
	
	if (window.timerInterval != 300000)
		window.timerInterval = 300000; // 5 minutes

	var xml_requestCurr = new XMLHttpRequest();
	xml_requestCurr.onload = function(e) {
		var xml_requestForecast = new XMLHttpRequest();
		xml_requestForecast.open("GET", forecastURL);
		xml_requestForecast.setRequestHeader("Cache-Control", "no-cache");
		xml_requestForecast.send(null);
		//widget.openURL(forecastURL); //uncomment for debugging
		xml_requestForecast.onload = function(e) {xml_loaded(name, e, xml_requestCurr, xml_requestForecast, callback);}
	};
	xml_requestCurr.open("GET", currWeatherURL);
	xml_requestCurr.setRequestHeader("Cache-Control", "no-cache");
	xml_requestCurr.send(null);
	
	//widget.openURL(currWeatherURL); //uncomment for debugging
	
	return xml_requestCurr;
}

function constructError (string)
{
	return {error:true, errorString:string};
}

// parses string of the form nn:nn
function parseTimeString(string)
{
	var obj = null;
	try {
		var array = string.match (/\d{1,2}/g);
		
		obj = {hour:parseInt(array[0], 10), minute:parseInt(array[1],10)};
	}
	catch (ex)
	{
		// ignore
	}
	
	return obj;
}

//Apple expects time to come in format nn:nn
function makeTimeString(unixTime) {
	return new Date(unixTime * 1000).toUTCString();
}

function getDayOfWeek(time) {
	var weekArray = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
	
	var date = new Date()
	
	var year = time.substr(0,4);
	var month = parseInt(time.substr(5,2)) - 1;
	var day = time.substr(8,2);
	
	date.setUTCFullYear(year);
	date.setUTCMonth(month);
	date.setUTCDate(day);
	
	var dayNum = date.getUTCDay();
	return weekArray[dayNum];
}

function xml_loaded (cityName, event, requestCurrent, requestForecast, callback)
{
		var obj = {error:false, errorString:null};
		
		var CurrentConditions = JSON.parse(requestCurrent.responseText).data[0];
		
		obj.time = makeTimeString(CurrentConditions.ts);
		obj.city = cityName;
		obj.temp = parseInt(CurrentConditions.temp);
		obj.description = CurrentConditions.summary
		
		var icon = CurrentConditions.weather.icon;
		if (icon.substr(0,1) === 'c' && CurrentConditions.wind_spd > windThreshold) {
			icon = "wind"
		}
		var accuweatherIconNum = getAccuweatherIconNumFromWeatherbitIcon[icon];
		obj.icon = accuweatherIconNum;
		obj.icons = accuweatherIcons[accuweatherIconNum];
		
		var Forecast = JSON.parse(requestForecast.responseText).data;
		if (Forecast == null) {callback(constructError("no <Forecast>")); return;}
		
		obj.hi = parseInt(Math.round(Forecast[0].max_temp));
		obj.lo = parseInt(Math.round(Forecast[0].min_temp));
		
		obj.sunset = makeTimeString(Forecast[0].sunset_ts);
		obj.sunrise = makeTimeString(Forecast[0].sunrise_ts);
		
		var lunationNum = parseFloat(Forecast[0].moon_phase_lunation);
		obj.phase = MoonMap[parseInt(Math.round(lunationNum * (MoonMap.length)))];
		
		obj.forecast = new Array;
		
		for (i = 0; i < Forecast.length; i++) {
			var foreobj = {description:null, hi:0, lo:0, icon:-1};
			
			//Likely accessibility-related; not normally visible anywhere in the widget. Untested.
			foreobj.description = Forecast[i].weather.description;
			
			foreobj.hi = parseInt(Math.round(Forecast[i].max_temp));
			foreobj.lo = parseInt(Math.round(Forecast[i].min_temp));

			var icon = Forecast[i].weather.icon;
			if (icon.substr(0,1) === 'c' && Forecast[i].wind_spd > windThreshold) {
				icon = "wind"
			}
			var accuweatherIconNum = getAccuweatherIconNumFromWeatherbitIcon[icon];
			foreobj.icon = accuweatherIconNum;
			foreobj.ouricon = accuweatherMiniIcons[accuweatherIconNum];
			
			foreobj.daycode = getDayOfWeek(Forecast[i].valid_date);
			
			obj.forecast[i]=foreobj;
		}
		
		callback (obj);

}

function validateWeatherLocation (location, callback)
{
	var url = 'http://open.mapquestapi.com/geocoding/v1/address?key=' + mapQuestApiKey + '&location=';
	
	var xml_request = new XMLHttpRequest();
	xml_request.onload = function(e) {xml_validateloaded(e, xml_request, callback);}
	xml_request.overrideMimeType("text/xml");
	xml_request.open("GET", url+location);
	xml_request.setRequestHeader("Cache-Control", "no-cache");
	xml_request.send(null);
}

function xml_validateloaded (event, request, callback)
{
	if (request.responseText)
	{
		var json = JSON.parse(request.responseText);
		var obj = {error:false, errorString:null, cities:new Array, refine:false};
		
		for (i = 0; i < json.results[0].locations.length; i++) {
			var city = json.results[0].locations[i].adminArea5;
			var state = json.results[0].locations[i].adminArea3 || json.results[0].locations[i].adminArea1;
			zip = json.results[0].locations[i].postalCode;

			var lat = json.results[0].locations[i].displayLatLng.lat;
			var lon = json.results[0].locations[i].displayLatLng.lng;
		
			if (city && state && lat && lon) {
				if (obj.cities.length === 0 || city !== obj.cities[obj.cities.length - 1].name || state !== obj.cities[obj.cities.length - 1].state) {
					obj.cities[obj.cities.length] = {name:city, state:state, zip:zip, lat: lat, lon: lon};
				}
			}
		}
		
		callback (obj);
	}
	else
	{
		callback ({error:true, errorString:"request failed. no response"});
	}
}

