////////////////////////////////////////////////////////////////////////////////
//
// THIS CODE IS NOT APPROVED FOR USE IN/ON ANY OTHER UI ELEMENT OR PRODUCT COMPONENT.
// Copyright (c) 2009 Microsoft Corporation. All rights reserved.
//
////////////////////////////////////////////////////////////////////////////////

var g_locationAPIAvailable = false;
var g_functionToCall = "MicrosoftGadget.requestUpdate()";

var gDefaultDisplayMode = "docked"; 
var gDisplaySizeDocked = { width: 130, height: 67 }
var gDisplaySizeUnDocked = { width: 264, height: 194 }
var gDefaultRefreshInterval = 60;	
var gDefaultPollingForServiceExistence = 1; 
var gDefaultSunRise = "06:30:00";
var gDefaultSunSet = "18:30:00";
var gDefaultWeatherLocation = getLocalizedString('DefaultCity');
var gDefaultWeatherLocationCode = getLocalizedString('DefaultLocationCode');
var gDefaultDisplayDegreesIn = 'Celsius';

var gTimeStampLastRefreshAvailable = false;
var gTimeToNextRefresh = 1; // default to 1 minute

var gMinimumDistance = 10; // Threshold distance (in kilometres) to do a location update ( when recieving location change events )

var bUseCelsius = false;

var LCID_ARRAY = new Array(1025,1028,1029,1030,1031,1032,1033,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1053,1054,1055,1056,1057,1058,1060,1061,1062,1063,1068,1070,1080,1081,1083,1106,1117,1122,1124,1131,1134,1135,1146,1153,1158,2049,2052,2055,2057,2058,2060,2064,2067,2068,2070,2074,2092,2094,2107,2108,2141,2155,3073,3076,3079,3081,3082,3084,3098,3131,3179,4097,4100,4103,4105,4106,4108,4155,5121,5127,5129,5130,5132,5146,5179,6145,6153,6154,6156,6203,7169,7177,7178,7227,8193,8202,8218,8251,9217,9226,9275,10241,10250,11265,11274,12289,12298,13313,13321,13322,14337,15361,15370,16385,16393,16394,17417,17418,18441,18442,19466,20490,21514);
var LOCCODE_ARRAY = new Array("wc:SAXX0017","wc:TWXX0021","wc:EZXX0012","wc:DAXX0009","wc:GMXX0007","wc:GRXX0004","wc:USNY0996","wc:FIXX0002","wc:FRXX0076","wc:ISXX0010","wc:HUXX0002","wc:ICXX0002","wc:ITXX0067","wc:JAXX0085","wc:KSXX0037","wc:NLXX0002","wc:NOXX0029","wc:PLXX0028","wc:BRXX0232","wc:7162","wc:ROXX0003","wc:RSXX0063","wc:HRXX0005","wc:LOXX0001","wc:SWXX0031","wc:THXX0002","wc:TUXX0014","wc:PKXX0008","wc:IDXX0022","wc:UPXX0016","wc:SIXX0002","wc:ENXX0004","wc:LGXX0004","wc:LHXX0005","wc:AJXX0001","wc:3166","wc:33739","wc:INXX0096","wc:9734330","wc:UKXX0030","wc:CAXX0202","wc:NLXX0002","wc:RPXX0017","wc:BLXX0006","wc:LUXX0003","wc:GLXX0003","wc:CIXX0020","wc:NZXX0049","wc:GTXX0002","wc:IZXX0008","wc:CHXX0008","wc:SZXX0033","wc:UKXX0085","wc:MXDF0132","wc:BEXX0005","wc:SZXX0005","wc:BEXX0005","wc:NOXX0029","wc:POXX0016","wc:YIXX0005","wc:AJXX0001","wc:GMXX0171","wc:SWXX0019","wc:EIXX0014","wc:CAXX0202","wc:ECXX0008","wc:EGXX0004","wc:CHXX0049","wc:AUXX0025","wc:ASXX0023","wc:SPXX0050","wc:CAXX0385","wc:YIXX0005","wc:8633427","wc:PEXX0011","wc:LYXX0009","wc:SNXX0006","wc:LUXX0003","wc:CAXX0343","wc:GTXX0002","wc:SZXX0013","wc:9746201","wc:AGXX0001","wc:LSXX0002","wc:NZXX0049","wc:CSXX0009","wc:LUXX0003","wc:BKXX0004","wc:SWXX0019","wc:MOXX0007","wc:EIXX0014","wc:PMXX0004","wc:8548036","wc:NOXX0049","wc:TSXX0010","wc:SFXX0023","wc:DRXX0009","wc:24724","wc:MUXX0003","wc:VEXX0008","wc:BKXX0004","wc:8633427","wc:YMXX0005","wc:COXX0004","wc:8633427","wc:SYXX0004","wc:PEXX0011","wc:JOXX0002","wc:ARBA0009","wc:LEXX0003","wc:ECXX0008","wc:KUXX0003","wc:RPXX0017","wc:CIXX0020","wc:AEXX0004","wc:BAXX0001","wc:PAXX0001","wc:QAXX0003","wc:INXX0096","wc:BLXX0006","wc:MYXX0008","wc:ESXX0001","wc:SNXX0006","wc:HOXX0008","wc:NUXX0004","wc:USPR0087","wc:USCA0638","wc:MXDF0132","wc:USWA0367","wc:AEXX0004","wc:AEXX0004");
var UNIT_ARRAY = new Array("C","C","C","C","C","C","F","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","C","F","C","C","C","C");

var MicrosoftGadget;


////////////////////////////////////////////////////////////////////////////////
//
// cleanup() - triggered by body.onunload event
//
////////////////////////////////////////////////////////////////////////////////
function cleanup() 
{
 System.Debug.outputString("Releasing resources...");
 try
 {
	factory.object.StopListeningForReports();
	System.Debug.outputString("StopListeningForReports succeeded");
 }
 catch(err)
 {
	System.Debug.outputString("StopListeningForReports failed");
 }

 try
 {
	MicrosoftGadget.factory = null;
	System.Debug.outputString("Pointer to node cleared");
	factory.removeNode( true );
	System.Debug.outputString("RemoveNode succeeded");
 }
 catch(err)
 {
	System.Debug.outputString("RemoveNode failed");
 }

 System.Debug.outputString("Done");
}

////////////////////////////////////////////////////////////////////////////////
//
// setup() - triggered by body.onload event
//
////////////////////////////////////////////////////////////////////////////////
function setup() 
{
 var OSLocale = vbsGetLocale();
 for (i = 0; i < LCID_ARRAY.length ; i++)
 {
	if ( LCID_ARRAY[i] == OSLocale )
	{
		gDefaultWeatherLocation = LOCNAME_ARRAY[i];
		gDefaultWeatherLocationCode = LOCCODE_ARRAY[i];
		if ( UNIT_ARRAY[i] == "C" )
		{
		gDefaultDisplayDegreesIn = 'Celsius';
		}
		else
		{
		gDefaultDisplayDegreesIn = 'Fahrenheit';
		}
	}
 }

 MicrosoftGadget = new WeatherGadget();

 // If we are in BIDI Mode, apply some special css to help folks read things Right to Left
 if (gBIDIMode) 
 {
	document.body.className = 'BIDI'; 
 }
 setDisplayMode();

 MicrosoftGadget.factory = document.getElementById("factory").object;

 MicrosoftGadget.refreshState();
 
 if (MicrosoftGadget.isValid) 
 {
	MicrosoftGadget.refreshSettings();
 } 
 else 
 {
	// The only way that we can get here is if the Service itself is invalid. 
	// Therefore, no need to set up refresh intervals, etc.
	showOrHideServiceError( true );
 }
 
 // Hook the various events to our custom support functions
 if (gGadgetMode) 
 {	
	System.Gadget.settingsUI="settings.html";
	System.Gadget.onSettingsClosed = function(event) 
	{ 
		MicrosoftGadget.refreshSettings();
	}
	System.Gadget.onDock = function()
	{ 
		setDisplayMode(); 
	}	
	System.Gadget.onUndock = function()
	{ 
		setDisplayMode(); 
	}	
	System.Gadget.onShowSettings = function() 
	{
		MicrosoftGadget.suspendPeriodicRefresh();
		if ( MicrosoftGadget.pollingForServiceExistenceIsRunning ) 
		{
		MicrosoftGadget.endPollingForServiceExistence();
		}
		MicrosoftGadget.wasPollingForServiceExistence = false;
	}
	System.Gadget.visibilityChanged = function() 
	{
		if ( System.Gadget.visible ) 
		{
		if ( !IsWeatherDataFresh() && !MicrosoftGadget.pollingForServiceExistenceIsRunning )
		{
			MicrosoftGadget.refreshSettings();
		}
		}
	}
 }	
}


////////////////////////////////////////////////////////////////////////////////
//
// IsWeatherDataFresh ( )
//
// return true if weather data last pulled is still fresh
// Also, if the data is still fresh, update global gTimeToNextRefresh to indicate time in minutes when refresh must be triggered to get fresh data
////////////////////////////////////////////////////////////////////////////////
function IsWeatherDataFresh ( )
{
 if ( gTimeStampLastRefreshAvailable && MicrosoftGadget.oMSNWeatherServiceTimeStamp !== undefined && MicrosoftGadget.oMSNWeatherServiceTimeStamp !== null )
 {
	var ageLastRefresh = calculateAge( MicrosoftGadget.oMSNWeatherServiceTimeStamp );
	var serviceRefreshInterval = MicrosoftGadget.refreshInterval / ( 60 * 1000 );
	var ageLastRefreshInterval = ageLastRefresh.minutesAge + ( ageLastRefresh.hoursAge * 60 );

	if ( ageLastRefresh.daysAge !== 0 || ageLastRefreshInterval >= serviceRefreshInterval )
	{
		return false;
	}
	else
	{
		gTimeToNextRefresh = serviceRefreshInterval - ageLastRefreshInterval;
		return true;
	}
 }
 else
 {
	return false;
 }
}

////////////////////////////////////////////////////////////////////////////////
//
// TemperatureInSelectedUnit (degreesFahrenheit) 
//
// If Fahrenheit selected, return temperature passed as-is, else
// Return Tc = (5/9)*(Tf-32); 
////////////////////////////////////////////////////////////////////////////////
function TemperatureInSelectedUnit (degreesFahrenheit) 
{
	bUseCelsius = ( MicrosoftGadget.displayDegreesIn == "Celsius" );
	if ( bUseCelsius )
	{
		var degreesCelsius = 0;

		if ( degreesFahrenheit == undefined )
			return (degreesFahrenheit);

		if ( !( degreesFahrenheit == parseFloat( degreesFahrenheit ) ) )
			return (degreesFahrenheit);

		degreesFahrenheit = parseFloat( degreesFahrenheit );
		degreesCelsius = ( degreesFahrenheit - 32 )*5.0/9.0;
		degreesCelsius = Math.round( degreesCelsius );

		return (degreesCelsius);
	}
	else
	{
		return (degreesFahrenheit);
	}
}


////////////////////////////////////////////////////////////////////////////////
//
// WeatherGadget() - main Constructor
//
////////////////////////////////////////////////////////////////////////////////
function WeatherGadget() 
{
 var self = this;


 ////////////////////////////////////////////////////////////////////////////////
 //
 // Public Members
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.isValid = true;
 this.isStaleData = false;
 this.dataExpired = false;
 this.ageStampText = ""; // XX <units> ago (holds the localized string indicating staleness of data
 this.SkyText = "";
 this.statusMessage = "";
 this.weatherLocation	= gDefaultWeatherLocation;
 this.weatherLocationCode = gDefaultWeatherLocationCode;
 this.displayDegreesIn	= gDefaultDisplayDegreesIn;
 this.SunRise	= gDefaultSunRise;
 this.SunSet	= gDefaultSunSet;
 this.offsetFromLocalTime = 0;
 this.refreshInterval	= gDefaultRefreshInterval;
 this.displayMode	= gDefaultDisplayMode;
 this.currentState = null;

 this.oMSNWeatherServiceTimeStamp = null;

 try 
 {
	this.spinner = new getSpinner( "PleaseWaitLoadingSpinner" ); 
 }
 catch (objException) 
 {
	this.spinner = null;
 }

 this.status = 200;
 try 
 {
	// Connect to Weather Service .dll
	var oMSN = new ActiveXObject("wlsrvc.WLServices");
	var oMSN2 = new ActiveXObject("wlsrvc.WLServices");
	this.oMSN = oMSN.GetService("weather"); // Object to send and recieve weather data queries and to poll for service existence
	this.oMSN2 = oMSN2.GetService("weather"); // Object to send a latlong query and recieve a location code corresponding to the latlong
 }
 catch (objException) 
 {
	this.isValid = false;
	this.statusMessage = getLocalizedString('ServiceNotAvailable');
	this.oMSN = new Object();
	this.oMSN2 = new Object();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //
 // Public Methods
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.onUpdate	= refreshEverything;
 this.lastPositionUsedForGPSQuery = null;
 this.onLocationEventFired = function( currentPosition )
 {
	var bTimerReset = false;
 	try
	{
		var bReadyToUpdate = true;
		if( self.lastPositionUsedForGPSQuery !== null )
		{
			bReadyToUpdate = ( DistanceMovedSinceLastPositionUpdate(self.lastPositionUsedForGPSQuery, currentPosition) > gMinimumDistance );
		}

 		if ( bReadyToUpdate )
 		 // If new latlong event fired with location coordinates more than the 
 		 // threshold distance since the last attempt to update using location coordinates, update using new coordinates recieved
 		 // NOTE: This assumes that the location sensor is working ie REPORT_RUNNING state (4) since latlong is only recieved through events when reports are running
		{
			self.clearWeatherUpdateTimer();
			bTimerReset = true;

			System.Debug.outputString("Updating weather to new location");
			self.lastPositionUsedForGPSQuery = currentPosition;	// Always maintain current GPS position locally before calling getCurrentLocationCode()
			self.getCurrentLocationCode ( currentPosition.Latitude, currentPosition.Longitude );
		}
 	}
	catch(err)
	{
		System.Debug.outputString("Error while processing location change event");
		System.Debug.outputString( "Error Description: " + err.description + "");
		if ( bTimerReset ) // Incase exception occurred after clearing the timer.. re-set it.. to prevent gadget from completely aborting periodic refresh
		{
			self.beginPeriodicRefresh();
		}
	}
 }

 ////////////////////////////////////////////////////////////////////////////////
 //
 // getWeatherUpdate - location sensitive request of update from Weather Feed
 // ignoreAPIStatusUpdate - indicates if status check before update should be skipped
 ////////////////////////////////////////////////////////////////////////////////
 this.getWeatherUpdate = function( ignoreAPIStatusUpdate ) 
 {
	if ( !ignoreAPIStatusUpdate )
	{
		self.APIStatusChanged( getAPIStatus( MicrosoftGadget ) ); // check api status - is it ready to provide location?
	}

	if ( self.currentState == LOCATION_SENSING_STATE_OK_AUTO ) // gadget configured for location updates
	{
		try
		{
		var report = getAPILatLongReport( self );
		System.Debug.outputString( "Current Geo-coordinates: (" + report.Latitude + "," + report.Longitude + ")");
		self.lastPositionUsedForGPSQuery = report;	// Always maintain current GPS position locally before calling getCurrentLocationCode()
		self.getCurrentLocationCode ( report.Latitude, report.Longitude );
		return;
		}
		catch(err)
		{
		System.Debug.outputString( "Error getting geo-coordinates from Location API");
		System.Debug.outputString( "Error Description: " + err.description + "");
		self.initializeState ( LOCATION_SENSING_STATE_ERROR_AUTO , false );
		}
	}
	self.requestUpdate();
 }

 ////////////////////////////////////////////////////////////////////////////////
 //
 // getCurrentLocationCode - queries weather service to find location for geo-coordinates
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.getCurrentLocationCode = function( latitude , longitude ) 
 {
	var coordinates = latitude + ", " + longitude;

	this.oMSN2.OnDataReady = getLocationCode;

	self.statusMessage='Requesting location Update...';

	if ( self.spinner !== null ) 
	{
		self.spinner.start();
	}

	showOrHideGettingLocationMessage( true );
	self.oMSN2.SearchByLocation( coordinates );
 }

 ////////////////////////////////////////////////////////////////////////////////
 //
 // requestUpdate - request update from Weather Feed.
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.requestUpdate = function() 
 {
	if ( IsWeatherDataFresh ( ) )
	{
		self.setWeatherUpdateTimer( g_functionToCall , gTimeToNextRefresh * 60 * 1000 );
		return;
	}

	self.statusMessage='Requesting Update...';

	if (self.spinner !== null) 
	{
		self.spinner.start();
	}

	showOrHideGettingDataMessage( true ); 
	self.oMSN.Celsius = false;
	self.oMSN.OnDataReady = onDataReadyHandler;
	self.oMSN.SearchByCode( self.weatherLocationCode );
 }
 ////////////////////////////////////////////////////////////////////////////////
 //
 // refreshSettings - populate values with stored settings 
 //	and request update(s) of Weather data from Service
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.refreshSettings = function () 
 {
	var savedWeatherLocationCode = URLDecode(readSetting("WeatherLocationCode")) || gDefaultWeatherLocationCode;
	if ( savedWeatherLocationCode != self.weatherLocationCode )
	{
		gTimeStampLastRefreshAvailable = false;
	}

	self.statusMessage='RefreshSettings';
	self.weatherLocation	= unescape(readSetting("WeatherLocation")) || gDefaultWeatherLocation;
	self.weatherLocationCode = savedWeatherLocationCode;
	self.displayDegreesIn	= readSetting("DisplayDegreesIn") || gDefaultDisplayDegreesIn;

	saveSetting("WeatherLocation", self.weatherLocation );
	saveSetting("WeatherLocationCode", self.weatherLocationCode );
	saveSetting("DisplayDegreesIn", self.displayDegreesIn );

	self.refreshState();

	this.updateRefreshInterval();
	if (self.spinner !== null) 
	{
		self.spinner.hide();
	}

	self.updateWeather( false );
	MicrosoftGadget.onUpdate();
 }

 ////////////////////////////////////////////////////////////////////////////////
 //
 // updateRefreshInterval - update MSN Weather service refresh frequency
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.updateRefreshInterval = function( )
 {
	// Compute frequency of refresh 
	if (self.oMSN.RefreshInterval > 0 )
	{
		self.refreshInterval = self.oMSN.RefreshInterval;
	}
	else
	{
		self.refreshInterval = readSetting("RefreshInterval");
	}
	self.refreshInterval = ( self.refreshInterval || gDefaultRefreshInterval ) * 60 * 1000;
 }
 ////////////////////////////////////////////////////////////////////////////////
 //
 // updateWeather - update weather data and schedule periodic updates
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.updateWeather = function( bIgnoreAPIStatusCheck )
 {
	if ( g_locationAPIAvailable )
	{
		self.getWeatherUpdate( bIgnoreAPIStatusCheck );
	}
	else
	{
		self.requestUpdate();
	}

	self.beginPeriodicRefresh();
 }



 ////////////////////////////////////////////////////////////////////////////////
 //
 // beginPeriodicRefresh - begins periodic polling of weather service for updates
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.beginPeriodicRefresh = function() 
 {
	// Clear any pending refresh requests first
	self.suspendPeriodicRefresh();	
	self.endPollingForServiceExistence();
	// Set up recurring requests for updates*	
	self.setWeatherUpdateTimer( g_functionToCall , self.refreshInterval);
	self.periodicRefreshIsRunning = true;
 }

 ////////////////////////////////////////////////////////////////////////////////
 //
 // setWeatherUpdateTimer - sets timer to request weather update
 // ( also clears any existing timers for the same )
 ////////////////////////////////////////////////////////////////////////////////
 this.setWeatherUpdateTimer = function( updateFunction, timeout )
 {
	this.clearWeatherUpdateTimer();
	self.interval_RefreshTemperature = setTimeout( updateFunction, timeout ); 
 }

 ////////////////////////////////////////////////////////////////////////////////
 //
 // clearWeatherUpdateTimer - clears currently set timer (if any) to request weather update
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.clearWeatherUpdateTimer = function( )
 {
	clearTimeout( self.interval_RefreshTemperature ); 
 }

 ////////////////////////////////////////////////////////////////////////////////
 //
 // suspendPeriodicRefresh - cancels polling of weather service for updates
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.suspendPeriodicRefresh = function() 
 {
	this.clearWeatherUpdateTimer();
	self.periodicRefreshIsRunning = false;
 }
 ////////////////////////////////////////////////////////////////////////////////
 //
 // beginPollingForServiceExistence - when network connectivity is lost, 
 // begin special polling testing for it to come back.
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.beginPollingForServiceExistence = function() 
 {
	// Clear any pending refresh requests first
	self.suspendPeriodicRefresh();	
	self.endPollingForServiceExistence();

	// Remap the onDataReady Handler
	self.oMSN.OnDataReady = isDataReadyHandler; 
	self.pollingForServiceExistence = setInterval( "MicrosoftGadget.oMSN.SearchByCode('" + self.weatherLocationCode + "')", gDefaultPollingForServiceExistence * 60 * 1000);
	self.pollingForServiceExistenceIsRunning = true;
 }
 ////////////////////////////////////////////////////////////////////////////////
 //
 // endPollingForServiceExistence - cancel special network connectivity polling
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.endPollingForServiceExistence = function() 
 {
	clearInterval( self.pollingForServiceExistence );
	clearInterval( MicrosoftGadget.pollingForServiceExistence ); 
	self.pollingForServiceExistenceIsRunning = false;
 }
 ////////////////////////////////////////////////////////////////////////////////
 //
 // weatherState() - Computes generalized state of weather [Sunny, Cloudy, etc.]
 // for all SkyCodes. Determines what image is used to represent...
 //
 ////////////////////////////////////////////////////////////////////////////////
 self.WeatherState = function() 
 {
	switch ( self.SkyCode ) 
	{
		case (26) : case (27) : case (28) :
			theWeatherState = "cloudy";
			break;
		case (35) : case (39) : case (45) : case (46) : 
			theWeatherState = "few-showers";
			break;
		case (19) : case (20) : case (21) : case (22) :
			theWeatherState = "foggy";
			break;
		case (29) : case (30) : case (33) :
			theWeatherState = "partly-cloudy";
			break;
		case (5) : case (13) : case (14) : case (15) : case (16) : case (18) : case (25) : case (41) : case (42) : case (43) : 
			theWeatherState = "snow";
			break;
		case (1) : case (2) : case (3) : case (4) : case (37) : case (38) : case (47) : 
			theWeatherState = "thunderstorm";
			break;
		case (31) : case (32) : case (34) : case (36) : case (44) :		// Note 44- "Data Not Available"
			theWeatherState = "sun";
			break;
		case (23) : case (24) :
			theWeatherState = "windy";
			break;
		case (9) : case (10) : case (11) : case (12) : case (40) :
			theWeatherState = "Rainy";
			break;
		case (6) : case (7) : case (8) : case (17) : 
			theWeatherState = "hail";
			break;
		default:
			theWeatherState = "sun";
			break;
		}
		return theWeatherState;
	}
 ////////////////////////////////////////////////////////////////////////////////
 //
 // isNight - boolean indicating whether its currently night *wherever*
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.isNight = function() 
 {
	var curTime = GMTTime(); 
	// Before SunRise or after Sunset means it's Night
	var deltaTime = self.SunSet - curTime;
	deltaTime = deltaTime / (1000 * 60 * 60);
	if (deltaTime > 24)
		curTime = new Date(curTime.getTime() + 24 * 60 * 60 * 1000);
	else if (deltaTime < 0)
		curTime = new Date(curTime.getTime() - 24 * 60 * 60 * 1000);
 
	return ( ( curTime < self.SunRise ) || ( curTime > self.SunSet ) );
 }
 ////////////////////////////////////////////////////////////////////////////////
 //
 // makesSenseToDisplayTheMoon - boolean indicating whether it makes sense to 
 //	display the moon. Dependant on weather state
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.makesSenseToDisplayTheMoon = function() 
 {
	var retVal = false;
	if ( self.isNight() ) 
	{ 
		var theWeatherState = self.WeatherState();
		if ( ( theWeatherState=="sun" ) || ( theWeatherState=="partly-cloudy" ) ) 
		{
		retVal = true;
		}
	}
	return retVal;
 }
 ////////////////////////////////////////////////////////////////////////////////
 //
 // self.backdrop - returns backdrop color required for active weather state
 //
 ////////////////////////////////////////////////////////////////////////////////
self.backdrop = function() 
 {
	var theBackground = "BLUE";
	var theDisplayMode = activeDisplayMode();
	
	switch ( self.SkyCode ) 
	{
		case (26) : case (27) : case (28) :
		case (35) : case (39) : case (45) : case (46) : 
		case (19) : case (20) : case (21) : case (22) :
		case (1) : case (2) : case (3) : case (4) : case (5) : case (37) : case (38) : case (47) : 
		case (9) : case (10) : case (11) : case (12) : case (40) : case (41) : case (42) : case (43) :
		case (6) : case (7) : case (8) : case (17) : case (13) : case (14) : case (15) : case (16) : case (18) :
		theBackground = "GRAY";
		break;
		case (29) : case (30) : case (33) : case (34) :
		case (31) : case (32) : case (36) : case (44) :		
		case (23) : case (24) : case (25) : default : 
		theBackground = "BLUE";
		break;
	} 
	
	if (self.isNight()) 
	{ 
		theBackground = "BLACK"; 
	}
	if ( !self.isValid )
	{ 
		theBackground = "BLUE"; 
	}
	return theBackground;
}

 
 ////////////////////////////////////////////////////////////////////////////////
 //
 // HighTemp() - use tomorrow's High as an approximation 
 //	if today's High cannot be returned by service
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.HighTemp = function() 
 {
	var theHighTemp = 0;
	try 
	{
		theHighTemp = TemperatureInSelectedUnit( self.oMSNWeatherService.ForeCast(0).High );
		if (theHighTemp == 0) 
		{
		theHighTemp = TemperatureInSelectedUnit( self.oMSNWeatherService.Forecast(1).High );
		}
	}
	catch (objException) 
	{
	}
	return theHighTemp;
 }


 ////////////////////////////////////////////////////////////////////////////////
 //
 // refreshState( )
 //	- re-check and assign state on reload/load/settings finalized
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.refreshState = function()
 {
	if ( self.factory == null )
	{
		self.DisableLocationAware();
		self.initializeState ( LOCATION_SENSING_STATE_DISABLED , false );
	}
	else
	{
		g_locationAPIAvailable = true;
		saveSetting ( "LocationAPIAvailable" , g_locationAPIAvailable );

		try
		{
		self.factory.StopListeningForReports();
		}
		catch(err)
		{
		}

		g_functionToCall = "MicrosoftGadget.getWeatherUpdate( false )";

		self.currentState = readSetting("CurrentState") || LOCATION_SENSING_STATE_INVALID;
		self.currentState = parseInt ( self.currentState );

		if ( !LOCATION_SENSING_VALID_STATES_ARRAY.exists( self.currentState ) ) // setting not saved yet
		{
		self.currentState = null;
		}

		self.APIStatusChanged( getAPIStatus( MicrosoftGadget ) );

		try // try hooking up status changed event
		{
		self.factory.ListenForReports(0);
		}
		catch(err)
		{
		System.Debug.outputString( "Error hooking up status changed event.");
		System.Debug.outputString( "Error number: " + err.number + "");
		System.Debug.outputString( "Error Description: " + err.description + "");
		}

	}
 }

 ////////////////////////////////////////////////////////////////////////////////
 //
 // DisableLocationAware( )
 //	- disable gadget's location aware feature
 //
 ////////////////////////////////////////////////////////////////////////////////
 this.DisableLocationAware = function( )
 {
	self.initializeState( LOCATION_SENSING_STATE_DISABLED , false );
	g_locationAPIAvailable = false;
	g_functionToCall = "MicrosoftGadget.requestUpdate()";
	saveSetting ( "LocationAPIAvailable" , g_locationAPIAvailable );
 }

 ////////////////////////////////////////////////////////////////////////////////
 //
 // APIStatusChanged( status ) - triggered when status of location api
 //	has changed. This updates gadget state and UI to reflect the new status
 ////////////////////////////////////////////////////////////////////////////////
 this.APIStatusChanged = function( status )
 {
	var newState = this.currentState;
	var baseVal;

	if ( status == 3 )
	{
		return;
	}
	else if ( status == 0 )
	{
		baseVal = LOCATION_SENSING_STATE_DISCONNECTED_AUTO;
	}
	else if ( status == 1 || status == 2 )
	{
		baseVal = LOCATION_SENSING_STATE_ERROR_AUTO;
	}
	else if ( status == 4 )
	{
		try
		{
		var report = getAPILatLongReport( this );
		baseVal = LOCATION_SENSING_STATE_OK_AUTO;
		}
		catch(err)
		{
		System.Debug.outputString( "Error getting geo-coordinates from Location API");
		System.Debug.outputString( "Error Description: " + err.description + "");
		baseVal = LOCATION_SENSING_STATE_ERROR_AUTO;
		}
	}
	else
	{
		baseVal = LOCATION_SENSING_STATE_ERROR_AUTO;
	}


	if ( this.currentState == null ) // gadget state not initialized yet
	{
		if ( baseVal == LOCATION_SENSING_STATE_DISCONNECTED_AUTO )
		{
		newState = LOCATION_SENSING_STATE_DISCONNECTED_MANUAL;
		}
		else
		{
		newState = baseVal;
		}
	}
	else
	{
		if ( LOCATION_SENSING_MANUAL_STATES_ARRAY.exists( this.currentState ) ) // State with Automatic Updates not enabled
		{
		switch( baseVal )
		{
			case LOCATION_SENSING_STATE_ERROR_AUTO:
			newState = LOCATION_SENSING_STATE_ERROR_MANUAL;
			break;

			case LOCATION_SENSING_STATE_OK_AUTO:
			newState = LOCATION_SENSING_STATE_OK_MANUAL;
			break;

			case LOCATION_SENSING_STATE_DISCONNECTED_AUTO:
			newState = LOCATION_SENSING_STATE_DISCONNECTED_MANUAL;
			break;
		}
		}
		else
		{
		newState = baseVal;
		}
	}

	self.initializeState ( newState , false );

	if ( this.currentState == LOCATION_SENSING_STATE_OK_AUTO )
	{
		self.updateWeather( true );
	}
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////////
 //
 // initializeState( stateNum , alertStatus ) - set gadget current state to one of 4 location aware states
 //	(default to 2)
 //
 //////////////////////////////////////////////////////////////////////////////////////////////
 this.initializeState = function( stateNum , alertStatus )
 {
	var sensorIconElementDocked = document.getElementById("SensorIconDockedMode");
	var sensorIconElementUndocked = document.getElementById("SensorIconUndockedMode");
	var imageURL = "";
	var hoverMessage = "";
	
	if (( stateNum == undefined ) || ( stateNum == null ) || !LOCATION_SENSING_VALID_STATES_ARRAY.exists( stateNum ) )
	{
		stateNum = LOCATION_SENSING_STATE_ERROR_MANUAL;
		System.Debug.outputString("Unable to determine state. Location Awareness may not work correctly when enabled.");
	}
	stateNum = parseInt( stateNum );

	this.currentState = stateNum;

	saveSetting ( "CurrentState" , stateNum );
	
	switch( stateNum )
	{
		case LOCATION_SENSING_STATE_ERROR_AUTO:
		imageURL = "images\\" + dpiImageFolderPrefix + "redStateIcon.png";
		hoverMessage = getLocalizedString('SensorIconRed');
		break;
		
		case LOCATION_SENSING_STATE_OK_AUTO:
		if ( alertStatus )
		{
			imageURL = "images\\" + dpiImageFolderPrefix + "alertIcon.png";
			hoverMessage = getLocalizedString('EC4');
		}
		else
		{
			imageURL = "images\\" + dpiImageFolderPrefix + "greenStateIcon.png";
			hoverMessage = getLocalizedString('SensorIconGreen');
		}
		break;
		
		case LOCATION_SENSING_STATE_OK_MANUAL:
		imageURL = "images\\" + dpiImageFolderPrefix + "grayStateIcon.png";
		hoverMessage = getLocalizedString('SensorIconGray');
		break;

		case LOCATION_SENSING_STATE_DISCONNECTED_AUTO:
		imageURL = "images\\" + dpiImageFolderPrefix + "notConnectedStateIcon.png";
		hoverMessage = getLocalizedString('SensorIconNotConnected');
		break;

		default:
		sensorIconElementDocked.style.visibility = "hidden";
		sensorIconElementUndocked.style.visibility = "hidden";

		document.getElementById('PlaceUnDockedMode').style.width = document.getElementById('PlaceHrefUnDockedMode').style.width = '222px';
		document.getElementById('PlaceDockedMode').style.width = document.getElementById('PlaceHrefDockedMode').style.width = '116px';
		if ( document.dir=='rtl')
		{
			document.getElementById('PlaceUnDockedMode').style.marginRight = '26px';
		}

		return;
		break;
	}

	sensorIconElementDocked.alt = hoverMessage;
	sensorIconElementUndocked.alt = hoverMessage;

	sensorIconElementDocked.style.visibility = "visible";
	sensorIconElementUndocked.style.visibility = "visible";

	setImage(sensorIconElementDocked, imageURL);
	setImage(sensorIconElementUndocked, imageURL);

	document.getElementById('PlaceUnDockedMode').style.width = document.getElementById('PlaceHrefUnDockedMode').style.width = '210px';
	document.getElementById('PlaceDockedMode').style.width = document.getElementById('PlaceHrefDockedMode').style.width = '104px';
	if ( document.dir=='rtl')
	{
		document.getElementById('PlaceUnDockedMode').style.marginRight='38px';
	}
 }

 
 ////////////////////////////////////////////////////////////////////////////////
 //
 // Private Methods
 //
 ////////////////////////////////////////////////////////////////////////////////
	
 ////////////////////////////////////////////////////////////////////////////////
 //
 // getLocationCode(object data) - asynchronous callback to get location
 //	for current geo-coordinates
 //
 ////////////////////////////////////////////////////////////////////////////////
 function getLocationCode(data) 
 {
	if ( data.RequestPending )
	{
		return;
	}
	
	if (self.spinner !== null) 
	{		
		self.spinner.stop();
	}

	showOrHideGettingLocationMessage( false );	

	if (data!==undefined) 
	{
		self.statusMessage='Location code Received.';

		if (data.Count > 0 && data.item(0)) // Service returned non-zero items
		{
		self.initializeState ( LOCATION_SENSING_STATE_OK_AUTO , false );

		if ( data.item(0).LocationCode != self.weatherLocationCode )
		{
			gTimeStampLastRefreshAvailable = false;
		}
		self.weatherLocation	= data.item(0).Location;
		self.weatherLocationCode = data.item(0).LocationCode;
		saveSetting ( "WeatherLocation" , self.weatherLocation );
		saveSetting ( "WeatherLocationCode" , self.weatherLocationCode );
		}
		else // Service returned 0 items
		{
		if ( data.RetCode == 200 ) 
		{
			self.initializeState ( LOCATION_SENSING_STATE_OK_AUTO , true );
		}
		else if ( data.RetCode == 1506 )
		{
			showOrHideServiceError( true, self.status );
			self.suspendPeriodicRefresh();
			self.beginPollingForServiceExistence();
			setDisplayMode();
			return;
		}
		// else, just continue to fetch data for last location
		}
		
	}


	self.requestUpdate();

 }

 ////////////////////////////////////////////////////////////////////////////////
 //
 // processDataAndStatus( data ) - processes data returned by Weather Feed
 //
 ////////////////////////////////////////////////////////////////////////////////
 function processDataAndStatus( data ) 
 {
	MicrosoftGadget.isStaleData = false;
	MicrosoftGadget.dataExpired = false;
	MicrosoftGadget.ageStampText = "";
	MicrosoftGadget.SkyText = "";
	MicrosoftGadget.oMSNWeatherServiceDataAge = calculateAge( new Date() );
	
	if ( data !== undefined ) 
	{ 
		MicrosoftGadget.statusMessage = 'Update Received.';
		MicrosoftGadget.status = data.RetCode;

		MicrosoftGadget.oMSNWeatherServiceTimeStamp = data.Timestamp;

		MicrosoftGadget.oMSNWeatherServiceDataAge = calculateAge( MicrosoftGadget.oMSNWeatherServiceTimeStamp );
		MicrosoftGadget.dataExpired = MicrosoftGadget.oMSNWeatherServiceDataAge.dataExpired;
		MicrosoftGadget.ageStampText = calculateAgeStampText( MicrosoftGadget.oMSNWeatherServiceDataAge );

		if( MicrosoftGadget.status == 1507 )
		{
		MicrosoftGadget.isStaleData = true;
		}
		else
		{
		MicrosoftGadget.isStaleData = false;
		}

		if ( data.Count > 0 && data.item(0) ) 
		{
		MicrosoftGadget.oMSNWeatherService = data.item(0);

		if ( !gTimeStampLastRefreshAvailable ) // First time pull of data (on load / on location change)
		{
			MicrosoftGadget.weatherLocation = MicrosoftGadget.oMSNWeatherService.Location;
			saveSetting("WeatherLocation", MicrosoftGadget.weatherLocation );
		}

		var gmt = GMTTime();

		// Compute SunRise/SunSet times based on latitude/longitude returned by the feed for location
		var theSunRiseSunset = computeSunRiseSunSet( MicrosoftGadget.oMSNWeatherService.Latitude, MicrosoftGadget.oMSNWeatherService.Longitude, 0, gmt.getFullYear(), gmt.getMonth()+1, gmt.getDate()); // Note - using GMT (no TimeZone offset)
		MicrosoftGadget.SunRise = theSunRiseSunset.SunRise;
		MicrosoftGadget.SunSet = theSunRiseSunset.SunSet;
		
		MicrosoftGadget.MoonState = function() 
		{ 
			return computePhaseOfMoon(new Date().getFullYear(), new Date().getMonth()+1, new Date().getDate()); 
		}
		
		MicrosoftGadget.SkyCode = MicrosoftGadget.oMSNWeatherService.SkyCode;
		MicrosoftGadget.SkyText = MicrosoftGadget.oMSNWeatherService.SkyText;
		if ( !MicrosoftGadget.dataExpired && MicrosoftGadget.isStaleData && MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge > 0 && MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge <= 4 )
		{
			MicrosoftGadget.SkyCode = MicrosoftGadget.oMSNWeatherService.Forecast(MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge).SkyCode;
			MicrosoftGadget.SkyText = MicrosoftGadget.oMSNWeatherService.Forecast(MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge).SkyText;
		}

		gTimeStampLastRefreshAvailable = true;
		}
		else
		{
		// In the case of "No Content", service will return 200 "success", but no data
		if (MicrosoftGadget.status==200) 
		{
			// Actual HTTP Error code for "No Content"
			MicrosoftGadget.status = 204;
		}
		}

		// Gadget is valid if we have a 200 / 1507 retVal
		MicrosoftGadget.isValid = ( MicrosoftGadget.status == 200 || MicrosoftGadget.status == 1507) && !MicrosoftGadget.dataExpired; 

	}
	document.getElementById('AgeStampTextUndockedMode').innerText = MicrosoftGadget.ageStampText;
	if ( MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge > 0 )
	{
		document.getElementById('AgeStampTextDockedMode').innerText = getLocalizedString( 'Forecasted' );
		document.getElementById('AgeStampTextDockedMode').title = MicrosoftGadget.ageStampText;
	}
	else
	{
		document.getElementById('AgeStampTextDockedMode').innerText = MicrosoftGadget.ageStampText;
		document.getElementById('AgeStampTextDockedMode').title = "";
	}
 }
 ////////////////////////////////////////////////////////////////////////////////
 //
 // onDataReadyHandler( data ) - processes data returned by Weather Feed
 //	(asynchronous callback) 
 //
 ////////////////////////////////////////////////////////////////////////////////
 function onDataReadyHandler( data ) 
 {
	if ( data.RequestPending )
	{
		return;
	}

	processDataAndStatus( data );

	if (self.spinner !== null) 
	{		
		self.spinner.stop();
	}

	showOrHideGettingDataMessage( false );	

	if (self.isValid)
	{
		if ( self.isStaleData )
		{
		self.onUpdate();
		self.suspendPeriodicRefresh();
		self.beginPollingForServiceExistence();
		}
		else
		{
		self.onUpdate();
		}
	} 
	else 
	{
		// When we get in this state, begin a special polling for the service coming
		// back online or otherwise correcting itself
		showOrHideServiceError( true, self.status );
		self.suspendPeriodicRefresh();
		// Only poll for Service Existence if it's available in the market
		if ( self.status != 1506 )
		{
		self.beginPollingForServiceExistence();
		}
		setDisplayMode();
	}

	if ( !self.pollingForServiceExistenceIsRunning )
	{
		self.updateRefreshInterval();
		self.setWeatherUpdateTimer( g_functionToCall , self.refreshInterval);		
	}
 }
 ////////////////////////////////////////////////////////////////////////////////
 //
 // isDataReadyHandler( data ) - special handler for use when the service is 
 //	down or otherwise unresponsive
 //
 ////////////////////////////////////////////////////////////////////////////////
 function isDataReadyHandler( data ) 
 {
	processDataAndStatus( data );

	if ( MicrosoftGadget.isValid )
	{
		if ( MicrosoftGadget.isStaleData )
		{
		MicrosoftGadget.onUpdate();
		}
		else
		{
		if ( !data.RequestPending )
		{
			self.endPollingForServiceExistence();
			if ( g_locationAPIAvailable )
			{
			self.getWeatherUpdate( false );
			}
			else
			{
			self.requestUpdate();
			}
			self.beginPeriodicRefresh();
		}
		MicrosoftGadget.onUpdate();
		}
	}
	else
	{
		showOrHideServiceError( true, self.status );
		if ( self.status == 1506 )
		{
		MicrosoftGadget.endPollingForServiceExistence();
		}
		setDisplayMode();
	}
 }
 ////////////////////////////////////////////////////////////////////////////////
 //
 // computeSunRiseSunSet(Latitude, Longitude, TimeZone, Year, Month, Day)
 //	Computes SunRise/SunSet based on Latitude/Longitude
 //
 ////////////////////////////////////////////////////////////////////////////////
 function computeSunRiseSunSet(Latitude, Longitude, TimeZone, Year, Month, Day) 
 {
	// Variable names used: B5, C, C2, C3, CD, D, DR, H, HR, HS, L0, L5, M, MR, MS, N, PI, R1, RD, S1, SC, SD, str
	var retVal = new Object();
	var str = "";
	var PI=Math.PI;
	var DR=PI/180;
	var RD=1/DR;
	var B5=Latitude;
	var L5=Longitude;
	var H =TimeZone;
	// Overriding TimeZone to standardize on UTC
	H = 0; 
	var M =Month;
	var D =Day;
	B5=DR*B5;
	var N=parseInt(275*M/9)-2*parseInt((M+9)/12)+D-30;
	var L0=4.8771+.0172*(N+.5-L5/360);
	var C=.03342*Math.sin(L0+1.345);
	var C2=RD*(Math.atan(Math.tan(L0+C)) - Math.atan(.9175*Math.tan(L0+C))-C);
	var SD=.3978*Math.sin(L0+C);
	var CD=Math.sqrt(1-SD*SD);
	var SC=(SD * Math.sin(B5) + .0145) / (Math.cos(B5) * CD);
	if (Math.abs(SC)<=1) 
	{
		var C3=RD*Math.atan(SC/Math.sqrt(1-SC*SC));
		var R1=6-H-(L5+C2+C3)/15;
		var HR=parseInt(R1);
		var MR=parseInt((R1-HR)*60);
		str = "Sunrise at " + HR + ":" + MR;
		retVal.SunRise = parseTime(HR + ":" + MR);
		var S1=18-H-(L5+C2-C3)/15;
		var HS=parseInt(S1);
		var MS=parseInt((S1-HS)*60);
		retVal.SunSet = parseTime(HS + ":" + MS);
		str += "\nSunset at " + HS + ":" + MS;
	} 
	else 
	{
		if (SC>1) 
		{ 
		str="Sun up all day"; 
		var tDate = new Date(); 
		// Set Sunset to be in the future ...
		retVal.SunSet = new Date( tDate.getFullYear()+1, tDate.getMonth(), tDate.getDay(), tDate.getHours() );
		// Set Sunrise to be in the past ...
		retVal.SunRise = new Date( tDate.getFullYear()-1, tDate.getMonth(), tDate.getDay(), tDate.getHours()-1 );
		}
		if (SC<-1) 
		{ 
		str="Sun down all day"; 
		// Set Sunrise and Sunset to be in the future ...
		retVal.SunRise = new Date( tDate.getFullYear()+1, tDate.getMonth(), tDate.getDay(), tDate.getHours() );
		retVal.SunSet = new Date( tDate.getFullYear()+1, tDate.getMonth(), tDate.getDay(), tDate.getHours() );
		}
	}
	retVal.str = str;
	return retVal;
 }

 ////////////////////////////////////////////////////////////////////////////////
 //
 // computePhaseOfMoon(Year, Month, Day) - Computes Phase of Moon based on Date
 //
 ////////////////////////////////////////////////////////////////////////////////
 function computePhaseOfMoon(Year, Month, Day) 
 {
	// Variable names used: J, K1, K2, K3, MM, P2, V, YY
	var P2 = 3.14159 * 2;	
	var YY = Year - parseInt((12 - Month)/10);
	var MM = Month + 9;
	if (MM >= 12) { MM = MM-12; }
	var K1 = parseInt(365.25 * (YY+4712));
	var K2 = parseInt(30.6 * MM + .5);
	var K3 = parseInt(parseInt((YY/100) + 49) * .75) - 38;
	// J is the Julian date at 12h UT on day in question
	var J = K1+K2+Day+59;			
	// Adjust for Gregorian calendar, if applicable 
	if (J > 2299160) { J = J-K3; }	
	// Calculate illumination (synodic) phase
	var V = (J - 2451550.1)/29.530588853;
	V = V - parseInt(V);
	// Normalize values to range from 0 to 1
	if (V<0) { V=V+1; }
	// Moon's age in days from New Moon
	var AG = V*29.53;	

	switch (true) 
	{ 
		// Each phase lasts approximately 3.28 days
		case ((AG > 27.6849270496875) || (AG <= 1.8456618033125)) :
		var retVal = 'New';
		break;
		case ((AG > 1.8456618033125) && (AG <= 5.5369854099375)) :
		var retVal = 'Waxing-Crescent';
		break;
		case ((AG > 5.5369854099375) && (AG <= 9.2283090165625)) :
		var retVal = 'First-Quarter';
		break;
		case ((AG > 9.2283090165625) && (AG <= 12.9196326231875)) : 
		var retVal = 'Waxing-Gibbous';
		break;
		case ((AG > 12.9196326231875) && (AG <= 16.6109562298125)) :
		var retVal = 'Full';
		break;
		case ((AG > 16.6109562298125) && (AG <= 20.3022798364375)) :
		var retVal = 'Waning-Gibbous';
		break;
		case ((AG > 20.3022798364375) && (AG <= 23.9936034430625)) :
		var retVal = 'Last-Quarter';
		break;
		case ((AG > 23.9936034430625) && (AG <= 27.6849270496875)) :
		var retVal = 'Waning-Crescent';
		break;
		default : 
		var retVal = 'Full';
		break;
	}	
	return retVal;
 }

 ////////////////////////////////////////////////////////////////////////////////
 //
 // SGN(aNumber) - Returns an integer indicating the sign of a number
 //
 ////////////////////////////////////////////////////////////////////////////////
 function SGN(aNumber) {
	if (aNumber===undefined) { aNumber = 0; } 
	var theNumber = parseFloat(aNumber);
	retVal = 0;
	if ( theNumber != 0 )
	{
		if (theNumber>0)
		{
		retVal = 1;
		}
		else
		{
		retVal = -1;
		}
	}
	return retVal;
 }
 ////////////////////////////////////////////////////////////////////////////////
 //
 // parseTime(string aTime) - takes a string of time in the format HH:MM:SS 
 //	and returns Javascript Date Object 
 //
 ////////////////////////////////////////////////////////////////////////////////
 function parseTime(aTime) 
 {
	var aDateTimeObject = 'none';
	if (aTime!==undefined && aTime.length) 
	{
		aDateTimeObject = GMTTime();
		try 
		{
		var theHour	= parseInt(aTime.split(':')[0]);
		var theMinutes = parseInt(aTime.split(':')[1]);
		aDateTimeObject.setHours(theHour);
		aDateTimeObject.setMinutes(theMinutes);
		}
		catch (ex) 
		{
		}
	}
	return aDateTimeObject;
 }
 ////////////////////////////////////////////////////////////////////////////////
 //
 // GMTTime() - returns time adjusted to GMT (Universal Time)
 //
 ////////////////////////////////////////////////////////////////////////////////
 function GMTTime() 
 { 
	var aDate = new Date();
	var aDateAdjustedToGMTInMS = aDate.getTime() + (aDate.getTimezoneOffset() * 60 * 1000);
	return ( new Date( aDateAdjustedToGMTInMS ) );
 } 
}

// *** END WeatherGadget Object Constructor ***

////////////////////////////////////////////////////////////////////////////////
//
// Routines for Refreshing Display
//
////////////////////////////////////////////////////////////////////////////////

function WeatherStateToDisplay(oWeatherGadget)
{
	var theState = "";
	if (oWeatherGadget.makesSenseToDisplayTheMoon())
	{
		theState = 'moon-' + oWeatherGadget.MoonState();

		// if we are displaying the moon, the only weather patterns
		// are "sun" or "partly-cloudy". we don't show the sun and the moon
		// at the same time (we don't support an eclipse), but we do show
		// light clouds over the moon.
		if (oWeatherGadget.WeatherState() == 'partly-cloudy')
		{
			theState = theState + '_partly-cloudy';
		}
	}
	else
	{
		theState = oWeatherGadget.WeatherState();
	}
	return theState;
}

function GetBackgroundImageFileName(oWeatherGadget)
{  
	var path = 'url(images/' + activeDisplayMode();
	if (oWeatherGadget.SkyCode != 44)
	{
		path = path + '_' + oWeatherGadget.backdrop() + '_' + WeatherStateToDisplay(oWeatherGadget) + '.png)';
	}
	else
	{
		path = path + '-loading.png)';
	}
	return path;
}


////////////////////////////////////////////////////////////////////////////////
//
// setDisplayMode() - resets gadget size and background based on current host
//
////////////////////////////////////////////////////////////////////////////////
function setDisplayMode( ) 
{
 showOrHide('DockedModeDisplayArea',false);
 showOrHide('UnDockedModeDisplayArea',false);

 var theWidth = gDisplaySizeUnDocked.width;
 var theHeight = gDisplaySizeUnDocked.height;
 var theActiveDisplayArea = 'DisplayArea' + gDefaultDisplayMode;

 switch ( activeDisplayMode() ) 
 {
	case "undocked" :
		theActiveDisplayArea = 'UnDockedModeDisplayArea';
		theWidth = gDisplaySizeUnDocked.width;
		theHeight = gDisplaySizeUnDocked.height;
		document.getElementById('PlaceHrefUnDockedMode').tabIndex = 1;
		document.getElementById('DayOfWeek1').tabIndex = 5;
		document.getElementById('DayOfWeek2').tabIndex = 5;
		document.getElementById('DayOfWeek3').tabIndex = 5;
		document.getElementById('UnDockedModeDisplayArea').className = MicrosoftGadget.backdrop(); 
		refreshUnDockedModeValues( MicrosoftGadget );
		if(MicrosoftGadget.isStaleData)
		{
		document.getElementById("AgeStampTextUndockedMode").style.visibility = "visible";
		}
		else
		{
		document.getElementById("AgeStampTextUndockedMode").style.visibility = "hidden";
		}
		break;

	case "docked" : default : 
		theActiveDisplayArea = 'DockedModeDisplayArea';
		theWidth = gDisplaySizeDocked.width;
		theHeight = gDisplaySizeDocked.height;
		document.getElementById('PlaceHrefDockedMode').tabIndex = 1;
		document.getElementById('DockedModeDisplayArea').className = MicrosoftGadget.backdrop(); 
		refreshDockedModeValues( MicrosoftGadget );
		if(MicrosoftGadget.isStaleData)
		{
		document.getElementById("AgeStampTextDockedMode").style.visibility = "visible";
		}
		else
		{
		document.getElementById("AgeStampTextDockedMode").style.visibility = "hidden";
		}
		break;
 }
 
 document.body.style.width = theWidth;
 document.body.style.height = theHeight;

 // Only show the data layers if we have data
 showOrHide(theActiveDisplayArea, MicrosoftGadget.isValid); 
 
 if (!MicrosoftGadget.isValid) 
 {
	showOrHide('WeatherMessage', true);
 }
 else
 {
 	setBackground( GetBackgroundImageFileName(MicrosoftGadget));
 }
}



////////////////////////////////////////////////////////////////////////////////
//
// refreshEverything() - update display for all modes with active data
//
////////////////////////////////////////////////////////////////////////////////
function refreshEverything() 
{ 
 refreshDockedModeValues( this );
 refreshUnDockedModeValues( this );
 setDisplayMode();
}
////////////////////////////////////////////////////////////////////////////////
//
// refreshDockedModeValues() - Refreshes display of Docked window
//
////////////////////////////////////////////////////////////////////////////////
function refreshDockedModeValues( oWeatherGadget ) 
{
 if ( !oWeatherGadget.isValid )
 { 
	// Since the weather service can become invalidated at any time (not necessarily
	// onRefreshSettings), we must manually set isValid to false if the service 
	// becomes undefined
	showOrHideServiceError(true, oWeatherGadget.status);
	return; 
 }
 if ( oWeatherGadget.oMSNWeatherService===undefined )
 { 
	// NO data to display.
	return; 
 }

 showOrHideServiceError(false);
 document.getElementById('PlaceHrefDockedMode').href = cleanURL( oWeatherGadget.oMSNWeatherService.Url );
 document.getElementById('PlaceHrefDockedMode').innerText = oWeatherGadget.oMSNWeatherService.Location;

 var theAltText;
 if ( MicrosoftGadget.isStaleData )
 {
	theAltText = getLocalizedString( 'ServiceNotAvailable' );
 }
 else
 {
	theAltText = oWeatherGadget.SkyText;	
 }

 // only need to update the background image (which is shared) if we are docked
 if (activeDisplayMode() == "docked")
 {
	setBackground( GetBackgroundImageFileName(oWeatherGadget));
 }
 
 if ( oWeatherGadget.makesSenseToDisplayTheMoon() ) 
 {

	// For NightTime Weather states, show additional Moon Phase tooltip
	if ( !MicrosoftGadget.isStaleData )
	{
		theAltText += " - " + getLocalizedString( 'Night-' + oWeatherGadget.MoonState());
	}
 } 
 else 
 {
	showOrHide('DropShadowDockedMode', false);
 }
 
  document.getElementById('DockedModeAccessibilityInformation').alt = theAltText;

 
 if ( MicrosoftGadget.dataExpired || !MicrosoftGadget.isStaleData || MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge == 0 )
 {
	if ( oWeatherGadget.oMSNWeatherService.Temperature && oWeatherGadget.oMSNWeatherService.Temperature != "" ) 
	{
		document.getElementById('TemperatureCurrent').innerText = TemperatureInSelectedUnit( oWeatherGadget.oMSNWeatherService.Temperature ) + "°";
	} 
	else
	{
		// In the unlikely event data is “Not Available”, do not display a temperature
		document.getElementById('TemperatureCurrent').innerText = " - ";
	}
 }
 else
 {
	if ( oWeatherGadget.oMSNWeatherService.ForeCast(MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge) )
	{
		var highPresent = false;
		if ( oWeatherGadget.oMSNWeatherService.ForeCast(MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge).High && oWeatherGadget.oMSNWeatherService.ForeCast(MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge).High != "" )
		{
		document.getElementById('TemperatureCurrent').innerText = TemperatureInSelectedUnit( oWeatherGadget.oMSNWeatherService.ForeCast(MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge).High ) + "°";
		highPresent = true;
		}
		if ( oWeatherGadget.oMSNWeatherService.ForeCast(MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge).Low && oWeatherGadget.oMSNWeatherService.ForeCast(MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge).Low != "" )
		{
		if ( highPresent )
		{
			document.getElementById('TemperatureCurrent').innerText = document.getElementById('TemperatureCurrent').innerText + " - ";
		}
		else
		{
			document.getElementById('TemperatureCurrent').innerText = "";
		}

		document.getElementById('TemperatureCurrent').innerText = document.getElementById('TemperatureCurrent').innerText + TemperatureInSelectedUnit( oWeatherGadget.oMSNWeatherService.ForeCast(MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge).Low ) + "°";
		}
		else
		{
		if ( !highPresent )
		{
			document.getElementById('TemperatureCurrent').innerText = " - ";
		}
		}
	} 
	else
	{
		// In the unlikely event data is “Not Available”, do not display a temperature
		document.getElementById('TemperatureCurrent').innerText = " - ";
	}
 }
 
}
////////////////////////////////////////////////////////////////////////////////
//
// cleanURL( sUrl ) - Verifies that the URL starts with http://
//
////////////////////////////////////////////////////////////////////////////////
function cleanURL( sURL )
{
	var safeUrl = "javascript:void();";
	var httpIndex = sURL.search( "http://" );

	if ( httpIndex != 0 )
	{
		return safeUrl;
	}
	return window.toStaticHTML(sURL);		
}
////////////////////////////////////////////////////////////////////////////////
//
// refreshUnDockedModeValues( oWeatherGadget ) - Refreshes UnDocked window
//
////////////////////////////////////////////////////////////////////////////////
function refreshUnDockedModeValues( oWeatherGadget ) 
{
 if ( !oWeatherGadget.isValid )
 { 
	showOrHideServiceError(true, oWeatherGadget.status);
	return; 
 }
 if ( oWeatherGadget.oMSNWeatherService===undefined )
 { 
	// NO data to display.
	return; 
 }

 showOrHideServiceError(false);
 document.getElementById('Attribution').innerText = oWeatherGadget.oMSNWeatherService.Attribution2;

 // Update Today's Forecast Temperatures
 document.getElementById('PlaceHrefUnDockedMode').href = cleanURL( oWeatherGadget.oMSNWeatherService.Url );
 document.getElementById('PlaceHrefUnDockedMode').innerText = oWeatherGadget.oMSNWeatherService.Location;
 document.getElementById('ConditionCurrentUnDockedMode').innerText = oWeatherGadget.SkyText;
 document.getElementById('TemperatureSeparator').innerText = '-';

 if ( MicrosoftGadget.dataExpired || !MicrosoftGadget.isStaleData || MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge == 0 )
 {
	document.getElementById('TemperatureHigh0').innerText = oWeatherGadget.HighTemp() + "°";
	document.getElementById('TemperatureLow0').innerText = TemperatureInSelectedUnit( oWeatherGadget.oMSNWeatherService.ForeCast(0).Low ) + "°";
 }
 else
 {
	document.getElementById('TemperatureHigh0').innerText = TemperatureInSelectedUnit( oWeatherGadget.oMSNWeatherService.ForeCast(MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge).High ) + "°";
	document.getElementById('TemperatureLow0').innerText = TemperatureInSelectedUnit( oWeatherGadget.oMSNWeatherService.ForeCast(MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge).Low ) + "°";
 }

 var theAltText;
 if ( MicrosoftGadget.isStaleData )
 {
	theAltText = getLocalizedString( 'ServiceNotAvailable' );
 }
 else
 {
	theAltText = oWeatherGadget.SkyText;	
 }

 // only need to update the background image (which is shared) if we are undocked
 if (activeDisplayMode() == "undocked")
 {
	setBackground( GetBackgroundImageFileName(oWeatherGadget));
 }

 if ( oWeatherGadget.makesSenseToDisplayTheMoon() ) 
 {
	// For NightTime Weather states, show additional Moon Phase tooltip
	if ( !MicrosoftGadget.isStaleData )
	{
		theAltText += " - " + getLocalizedString( 'Night-' + oWeatherGadget.MoonState());
	}
 } 

 if ( !MicrosoftGadget.isStaleData || MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge == 0 )
 {
	if ( oWeatherGadget.oMSNWeatherService.Temperature && oWeatherGadget.oMSNWeatherService.Temperature != "" ) 
	{
		document.getElementById('TemperatureCurrentUnDockedMode').innerText = TemperatureInSelectedUnit( oWeatherGadget.oMSNWeatherService.Temperature ) + "°";
	} 
	else
	{
		// In the unlikely event data is “Not Available”, do not display a temperature
		document.getElementById('TemperatureCurrentUnDockedMode').innerText = " - ";
	}
 }
 else
 {
	document.getElementById('TemperatureCurrentUnDockedMode').innerText = "";
 }

 document.getElementById('UnDockedModeAccessibilityInformation').alt = theAltText;
 
 for (var i=1;i<4;i++) 
 {
	var counterI;
	if ( !MicrosoftGadget.dataExpired && MicrosoftGadget.isStaleData && MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge > 0 )
	{
		counterI = MicrosoftGadget.oMSNWeatherServiceDataAge.daysAge + i;
	}
	else
	{
		counterI = i;
	}
	if ( counterI < 5 )
	{
		with ( oWeatherGadget.oMSNWeatherService.Forecast( counterI ) )
		{
		var theDate = parseDateFromString(Date);
		var theLowTemp = TemperatureInSelectedUnit( Low );
		var theHighTemp = TemperatureInSelectedUnit( High );
		// Code from 1 to 47 indicating Weather State (used to compute Icon to display)
		var theSkyCode = SkyCode;
		
		document.getElementById('SkyCodeImage'+ i).alt = SkyText; 
		setImage( 'SkyCodeImage'+ i, 'images/' + theSkyCode + '.png');
		document.getElementById('DayOfWeek' + i).innerText = Day; 
		document.getElementById('DayOfWeek' + i).href = cleanURL( oWeatherGadget.oMSNWeatherService.Url );
		document.getElementById('TemperatureHigh' + i).innerText = theHighTemp + "°";
		document.getElementById('TemperatureLow' + i).innerText = theLowTemp + "°";
		}
	}
	else
	{
		document.getElementById('SkyCodeImage'+ i).alt = ""; 
		setImage( 'SkyCodeImage'+ i, 'images/1px.gif');
		document.getElementById('DayOfWeek' + i).innerText = ""; 
		document.getElementById('DayOfWeek' + i).href = "javascript:void(0);";
		document.getElementById('TemperatureHigh' + i).innerText = "";
		document.getElementById('TemperatureLow' + i).innerText = "";
	}
 }
}
////////////////////////////////////////////////////////////////////////////////
//
// activeDisplayMode() - returns active display mode
//
////////////////////////////////////////////////////////////////////////////////
function activeDisplayMode() 
{
 retVal = gDefaultDisplayMode;

 if (gGadgetMode) 
 {
	if (System.Gadget.docked) 
	{
		retVal = "docked";
	} 
	else 
	{
		retVal = "undocked";	
	}
 } 
 return retVal;
}
////////////////////////////////////////////////////////////////////////////////
//
// parseDateFromString(string aString) - parses Date from a string extracted 
// from MSN Weather Feed. Returns a Date Object set to that time.
//
////////////////////////////////////////////////////////////////////////////////
function parseDateFromString(aString) 
{
 if (aString.length) {					
	// String received should be formatted like: "2006-01-10" (yyyy-mm-dd)
	var aDateTimeObject = new Date();
	aDateTimeObject.setFullYear(parseInt(aString.split('-')[0]));
	aDateTimeObject.setMonth(parseInt(aString.split('-')[1])-1);
	aDateTimeObject.setDate(parseInt(aString.split('-')[2]));
	return aDateTimeObject;
 }
}
////////////////////////////////////////////////////////////////////////////////
//
// showOrHideGettingDataMessage() - Display/Hide "Getting Data" Message 
//
////////////////////////////////////////////////////////////////////////////////
function showOrHideGettingDataMessage(bShow) 
{
 showOrHide('WeatherMessage', bShow);
 showOrHide('PleaseWaitLoadingSpinner', bShow);
 showOrHide(activeDisplayMode() + 'ModeDisplayArea', !bShow);

 var oMessage = document.getElementById('message');
 if (bShow) 
 { 
	if (activeDisplayMode()=="undocked")
	{
		document.getElementById("WeatherMessage").className = 'unDockedWeatherMessage';
	}
	else
	{
		document.getElementById("WeatherMessage").className = 'dockedWeatherMessage';
	}
	oMessage.innerHTML = getLocalizedString('GettingData');
	if (activeDisplayMode()=='docked')
	{
		setBackground( 'url(images/docked-loading.png)' );
	}
	else
	{
		setBackground( 'url(images/undocked-loading.png)' );
	}
 } 
 else 
 {
	oMessage.innerHTML = "";
 }
 showOrHide( document.getElementById('WeatherMessageIcon'), false );
}


////////////////////////////////////////////////////////////////////////////////
//
// showOrHideGettingLocationMessage() - Display/Hide "Getting current location" Message 
//
////////////////////////////////////////////////////////////////////////////////
function showOrHideGettingLocationMessage(bShow) 
{
 showOrHide('WeatherMessage', bShow);
 showOrHide('PleaseWaitLoadingSpinner', bShow);
 showOrHide(activeDisplayMode() + 'ModeDisplayArea', !bShow);

 var oMessage = document.getElementById('message');
 if (bShow) 
 { 
	if (activeDisplayMode()=="undocked")
	{
		document.getElementById("WeatherMessage").className = 'unDockedWeatherMessage';
	}
	else
	{
		document.getElementById("WeatherMessage").className = 'dockedWeatherMessage';
	}
	oMessage.innerHTML = getLocalizedString('GettingLocation');
	if (activeDisplayMode()=='docked')
	{
		setBackground( 'url(images/docked-loading.png)' );
	}
	else
	{
		setBackground( 'url(images/undocked-loading.png)' );
	}
 } 
 else 
 {
	oMessage.innerHTML = "";
 }
 showOrHide( document.getElementById('WeatherMessageIcon'), false );
}

////////////////////////////////////////////////////////////////////////////////
//
// showOrHideServiceError() - Display/Hide "Service Not Available" Message 
//
////////////////////////////////////////////////////////////////////////////////
function showOrHideServiceError(bShow, theStatusCode) 
{
 var theImage = "";
 var theMessage = "";
 showOrHide('WeatherMessage', bShow);
 showOrHide('PleaseWaitLoadingSpinner', false);
 var oMessage	= document.getElementById('message');
 if (bShow) 
 {
	if (activeDisplayMode()=="undocked")
	{
		document.getElementById("WeatherMessage").className = 'unDockedWeatherMessage';
	}
	else
	{
		document.getElementById("WeatherMessage").className = 'dockedWeatherMessage';
	}
	switch( theStatusCode ) 
	{
		case 1506: 
		// Forbidden
		if (activeDisplayMode()=="undocked") {
			theMessage = getLocalizedString( 'ServiceNotAvailableInYourArea' );
		} else {
			theMessage = getLocalizedString( 'ServiceNotAvailableInYourArea' );
		}
		document.getElementById('WeatherMessageIcon').alt = getLocalizedString( 'ServiceNotAvailableInYourArea' );
		oMessage.title = getLocalizedString( 'ServiceNotAvailableInYourArea' );
		break;
		
		
		case 404: default: 
		// Not Found
		theMessage = getLocalizedString( 'ServiceNotAvailable' );
		document.getElementById('WeatherMessageIcon').alt = getLocalizedString( 'ServiceNotAvailable' );
		break;
	}
	if (activeDisplayMode()=='docked')
	{
		setBackground( 'url(images/docked-loading.png)' );
	}
	else
	{
		setBackground( 'url(images/undocked-loading.png)' );
	}
 }
 showOrHide( document.getElementById('WeatherMessageIcon'), true );
 oMessage.innerHTML = '<span>' + theMessage + '<\/span>';
}
////////////////////////////////////////////////////////////////////////////////
//
// setBackground ( string theImageURL ) - sets the background image of our gadget
//
////////////////////////////////////////////////////////////////////////////////
function setBackground( theImageURL ) 
{
 // if you switch backgrounds on the fly, you must set the style size to zero
 // so it dynamically refreshes
 WeatherBG.style.width = 0;
 WeatherBG.style.height = 0;
 WeatherBG.src = theImageURL;
}

function calculateAge ( timestamp )
{
 var timestampDay = new Date( timestamp );
 var currentDay = new Date();
 
 var millisecondsTillTimestamp = Date.parse( timestamp );
 var millisecondsTillNow = (new Date()).getTime();
 var minutesDifference = parseInt( ( millisecondsTillNow - millisecondsTillTimestamp ) / ( 60 * 1000 ) );
 var hoursDifference = parseInt ( minutesDifference / 60 );
 var daysDifference = parseInt ( hoursDifference / 24 );
 
 this.minutesAge = 0;
 this.hoursAge = 0;
 this.daysAge = 0;
 this.dataExpired = false;
 
 if ( minutesDifference <= 0 )
 {
	return this;
 }

// shows hours and mins only for the same day
 if ( timestampDay.getDate() == currentDay.getDate()
	&& timestampDay.getMonth() == currentDay.getMonth()
	&& timestampDay.getFullYear() == currentDay.getFullYear() )
 {
	this.minutesAge = minutesDifference % 60;
	this.hoursAge = hoursDifference % 24;
	this.daysAge = daysDifference;
 }
 else // only calculate total no of days past
 {
	timestampDay.setHours(currentDay.getHours(),currentDay.getMinutes(),currentDay.getSeconds(),currentDay.getMilliseconds());
	this.daysAge = parseInt ( ( currentDay.getTime() - timestampDay.getTime() ) / ( 60 * 1000 * 60 * 24 ) );
	if ( this.daysAge < 0 )
	{
		this.daysAge = 0;
	}
 }

 if ( this.daysAge > 4 )
 {
	this.dataExpired = true;
 }
 
 return this;
}

function calculateAgeStampText( age )
{
 var isForecastedData = false;
 var ageStampText = "";
 var unitText = "";
 var durationText = "";

 if ( age.dataExpired )
 {
	return getLocalizedString('DataExpired');
 }

 if ( age.daysAge > 0 )
 {
	isForecastedData = true;
	durationText = getLocalizedString('day');
	if ( age.daysAge > 1 )
	{
		durationText = getLocalizedString('days');
	}
	unitText = age.daysAge;
 }
 else if ( age.hoursAge > 0 )
 {
	durationText = getLocalizedString('hr');
	if ( age.hoursAge > 1 )
	{
		durationText = getLocalizedString('hrs');
	}
	unitText = age.hoursAge;
 }
 else
 {
	if ( age.minutesAge == 0 )
	{
		return "";
	}
	else
	{
		durationText = getLocalizedString('min');
		unitText = age.minutesAge;
	}
 }
 
 if( isForecastedData )
 {
		ageStampText = getLocalizedString('ageStampMessageForecastedData')
		ageStampText = ageStampText.replace("%1", unitText);
		ageStampText = ageStampText.replace("%2", durationText);
 }
 else
 {
		ageStampText = getLocalizedString('ageStampMessage')
		ageStampText = ageStampText.replace("%1", unitText);
		ageStampText = ageStampText.replace("%2", durationText);
 }

 return ageStampText;
}
