/***************************************************************************
			metar_parser.cpp  -  Metar Parser
			Based on code originally in weatherlib.cpp.
			-------------------
begin                : Wed June 7 2004
copyright            : (C) 2004 by John Ratke
                     : (C) 2002-2004 Nadeem Hasan <nhasan@kde.org>
                     : (C) 2002-2004 Ian Geiser <geiseri@kde.org>
email                : jratke@comcast.net
***************************************************************************/

/***************************************************************************
*                                                                         *
*   This program is free software; you can redistribute it and/or modify  *
*   it under the terms of the GNU General Public License as published by  *
*   the Free Software Foundation; either version 2 of the License, or     *
*   (at your option) any later version.                                   *
*                                                                         *
***************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H

#include <tqdatetime.h>
#include <kdebug.h>
#include <math.h>

#include "metar_parser.h"
#include "stationdatabase.h"
#include "sun.h"

// Temperature conversion macros
#define TEMP_C_TO_F(x) ( ((x) * 9/5) + 32 )
#define TEMP_F_TO_C(x) ( ((x) - 32) * 5/9 )

MetarParser::MetarParser(StationDatabase *stationDB,
			 KLocale::MeasureSystem units,
			 TQDate date,
			 TQTime time,
			 unsigned int localUTCOffset) : 
	m_stationDb(stationDB), m_units(units), m_date(date), m_time(time), m_localUTCOffset(localUTCOffset)
{
	CoverRegExp    = TQRegExp("^(FEW|SCT|BKN|OVC|SKC|CLR|CAVOK)([0-9]{3})?(?:TCU|CB)?$");
	CurrentRegExp  = TQRegExp("^(\\+|-|VC)?([A-Z]{2,4})$");
	WindRegExp     = TQRegExp("^([0-9]{3}|VRB)([0-9]{2,3})(?:G([0-9]{2,3}))?(KT|KMH|MPS)$");
	VisRegExp      = TQRegExp("^([0-9]{1,2})SM$");
	VisFracRegExp  = TQRegExp("^1/(2|4)SM$");
	TempRegExp     = TQRegExp("^(M)?([0-9]{2})/(?:(M)?([0-9]{2}))?$");
	TimeRegExp     = TQRegExp("^([0-9]{2}:[0-9]{2})$");
	DateRegExp     = TQRegExp("^([0-9]{4}/[0-9]{2}/[0-9]{2})$");
	PressRegExp    = TQRegExp("^([AQ])([0-9]{4})$");
	TempTenRegExp  = TQRegExp("^T([01][0-9]{3})([01][0-9]{3})$");
}

void MetarParser::reset()
{
	// Initialize the WeatherInfo structure
	weatherInfo.theWeather = TQString();
	weatherInfo.clouds = 0;
	weatherInfo.windMPH = 0;
	weatherInfo.tempC = 0;
	weatherInfo.dewC  = 0;
	weatherInfo.heavy = false;
	weatherInfo.qsCoverList.clear();
	weatherInfo.qsCurrentList.clear();
	weatherInfo.qsDate = m_date;
	weatherInfo.qsTime = m_time;
	weatherInfo.qsPressure = TQString();
	weatherInfo.qsTemperature = TQString();
	weatherInfo.qsDewPoint = TQString();
	weatherInfo.qsRelHumidity = TQString();
	weatherInfo.qsVisibility = TQString();
	weatherInfo.qsWindSpeed = TQString();
	weatherInfo.qsWindChill = TQString();
	weatherInfo.qsHeatIndex = TQString();
	weatherInfo.qsWindDirection = TQString();
	weatherInfo.stationNeedsMaintenance = false;
}

struct WeatherInfo MetarParser::processData(const TQString &stationID, const TQString &metar)
{
	reset();
	
	weatherInfo.reportLocation = stationID;
	
	kdDebug(12006) << "Processing data: " << metar << endl;

	// Split at whitespace into tokens
	TQStringList dataList = TQStringList::split(TQRegExp("\\s+"), metar);
	bool found = false;
	bool beforeRemark = true;

	for (TQStringList::ConstIterator it = dataList.begin();
	     it != dataList.end(); ++it)
	{
		// Don't try to parse the ICAO location code
		if ((!found) && (*it == weatherInfo.reportLocation.upper().stripWhiteSpace()))
		{
			found = true;
			continue;
		}

		kdDebug(12006) << "Processing Token: " << *it << endl;
		
		if (*it == "RMK")
		{
			beforeRemark = false;
			continue;
		}
		
		if (beforeRemark)
		{
			if (parseDate(*it))
				continue;
			if (parseTime(*it))
				continue;
			if (parseWindSpeed(*it))
				continue;
			if (parseVisibility(it))  // Note, pass in iterator.
				continue;
			if (parseTemperature(*it))
				continue;
			if (parsePressure(*it))
				continue;
			if (parseCover(*it))
				continue;
			if (parseCurrent(*it))
				continue;
		}
		else
		{
			if (parseTemperatureTenths(*it))
				continue;
			if (parseStationNeedsMaintenance(*it))
				continue;
		}
	}

	calcTemperatureVariables();
	calcWindChill();
	calcCurrentIcon();
	
	return weatherInfo;
}

/** Parse the current cover type */
bool MetarParser::parseCover(const TQString &s)
{
	if (CoverRegExp.search(s) > -1)
	{
		kdDebug(12006) << "Cover: " << TQString(CoverRegExp.capturedTexts().join("-"))
				<< endl;

		TQString sCode = CoverRegExp.cap(1);
		float height = CoverRegExp.cap(2).toFloat();  // initially in 100's of feet
		TQString sClouds;
		TQString skycondition;

		height *= 100;
		if (m_units == KLocale::Metric)
		{
			height = height * 0.3048;
			// using plural i18n form for proper translations
			sClouds = i18n("1 meter", "%n meters", (int)height);
		}
		else
		{
			// using plural i18n form for proper translations
			sClouds = i18n("1 foot", "%n feet", (int)height);
		}

		if (sCode == "FEW")
		{
			skycondition = i18n( "Few clouds at %1" ).arg(sClouds);
			weatherInfo.clouds += 2;
		}
		else if (sCode == "SCT")
		{
			skycondition = i18n( "Scattered clouds at %1" ).arg(sClouds);
			weatherInfo.clouds += 4;
		}
		else if (sCode == "BKN")
		{
			skycondition = i18n( "Broken clouds at %1" ).arg(sClouds);
			weatherInfo.clouds += 8;
		}
		else if (sCode == "OVC")
		{
			skycondition = i18n( "Overcast clouds at %1" ).arg(sClouds);
			weatherInfo.clouds += 64;
		}
		else if ((sCode == "CLR") || (sCode == "SKC") || (sCode == "CAVOK"))
		{
			skycondition = i18n("Clear skies");
			weatherInfo.clouds = 0;
		}

		kdDebug(12006) << "*** Clouds: " << weatherInfo.clouds << endl;
		weatherInfo.qsCoverList << skycondition;

		return true;
	}
	return false;
}

/** Parse the current weather conditions */
bool MetarParser::parseCurrent(const TQString &s)
{
	if (CurrentRegExp.search(s) > -1)
	{
		TQString sIntensity = CurrentRegExp.cap(1);
		TQString sCode = CurrentRegExp.cap(2);
		TQString intensity, descriptor, phenomena, currentWeather;

		kdDebug(12006) << "Current: " << TQString(CurrentRegExp.capturedTexts().join("-")) << endl;

		// Decode the intensity
		if (sIntensity == "+")
		{
			intensity = i18n("Heavy");
			weatherInfo.heavy = true;
		}
		else if (sIntensity == "-")
		{
			intensity = i18n("Light");
			weatherInfo.heavy = false;
		}

		// Decode the descriptor
		if (sCode.contains("MI"))
			descriptor = i18n("Shallow");
		else if (sCode.contains("PR"))
			descriptor = i18n("Partial");
		else if (sCode.contains("BC"))
			descriptor = i18n("Patches");
		else if (sCode.contains("DR"))
			descriptor = i18n("Low Drifting");
		else if (sCode.contains("BL"))
			descriptor = i18n("Blowing");
		else if (sCode.contains("SH"))
		{
			descriptor = i18n("Showers");
			weatherInfo.theWeather = "shower";
		}
		else if (sCode.contains("TS"))
		{
			descriptor = i18n("Thunder Storm");
			weatherInfo.theWeather = "tstorm";
		}
		else if (sCode.contains("FZ"))
		{
			descriptor = i18n("Freezing");
		}

		// Decode weather phenomena
		if (sCode.contains("DZ"))
		{
			phenomena = i18n("Drizzle");
			weatherInfo.theWeather = "light_rain";
		}
		else if (sCode.contains("RA"))
		{
			phenomena = i18n("Rain");
			weatherInfo.theWeather = "shower";
		}
		else if (sCode.contains("SN"))
		{
			phenomena = i18n("Snow");
			weatherInfo.theWeather = "snow";
		}
		else if (sCode.contains("SG"))
		{
			phenomena = i18n("Snow Grains");
			weatherInfo.theWeather = "snow4";
		}
		else if (sCode.contains("IC"))
		{
			phenomena = i18n("Ice Crystals");
			weatherInfo.theWeather = "hail";
		}
		else if (sCode.contains("PE"))
		{
			phenomena = i18n("Ice Pellets");
			weatherInfo.theWeather = "hail";
		}
		else if (s.contains("GR"))
		{
			phenomena = i18n("Hail");
			weatherInfo.theWeather = "hail";
		}
		else if (sCode.contains("GS"))
		{
			phenomena = i18n("Small Hail Pellets");
			weatherInfo.theWeather = "hail";
		}
		else if (s.contains("UP"))
		{
			phenomena = i18n("Unknown Precipitation");
			weatherInfo.theWeather = iconName("shower1");
		}
		else if (sCode.contains("BR"))
		{
			phenomena = i18n("Mist");
			// Mist has lower priority than say rain or snow
			if ( weatherInfo.theWeather.isEmpty() )
			{
				weatherInfo.theWeather = "mist";
			}
		}
		else if (sCode.contains("FG"))
		{
			phenomena = i18n("Fog");
			// Fog has lower priority than say rain or snow
			if ( weatherInfo.theWeather.isEmpty() )
			{
				weatherInfo.theWeather = "fog";
			}
		}
		else if (sCode.contains("FU"))
			phenomena = i18n("Smoke");
		else if (sCode.contains("VA"))
			phenomena = i18n("Volcanic Ash");
		else if (sCode.contains("DU"))
			phenomena = i18n("Widespread Dust");
		else if (sCode.contains("SA"))
			phenomena = i18n("Sand");
		else if (sCode.contains("HZ"))
			phenomena = i18n("Haze");
		else if (sCode.contains("PY"))
			phenomena = i18n("Spray");
		else if (sCode.contains("PO"))
			phenomena = i18n("Dust/Sand Swirls");
		else if (sCode.contains("SQ"))
			phenomena = i18n("Sudden Winds");
		else if (sCode.contains("FC"))
		{
			if (sIntensity == "+")
				currentWeather = i18n("Tornado");
			else
				phenomena = i18n("Funnel Cloud");
		}
		else if (sCode.contains("SS"))
			phenomena = i18n("Sand Storm");
		else if (sCode.contains("DS"))
			phenomena = i18n("Dust Storm");
		
		if (currentWeather.isEmpty()) currentWeather = i18n("%1 is the intensity, %2 is the descriptor and %3 is the phenomena", "%1 %2 %3").arg(intensity).arg(descriptor).arg(phenomena);

		if (!currentWeather.isEmpty())
			weatherInfo.qsCurrentList << currentWeather;

		return true;
	}
	return false;
}

/** Parse out the current temperature */
bool MetarParser::parseTemperature(const TQString &s)
{
	if (TempRegExp.search(s) > -1)
	{
		kdDebug(12006) << "Temp: " << TQString(TempRegExp.capturedTexts().join("-"))
				<< endl;

		float fTemp = TempRegExp.cap(2).toFloat();
		if (TempRegExp.cap(1) == "M" && fTemp != 0 )
			fTemp *= -1;
		float fDew = TempRegExp.cap(4).toFloat();
		if (TempRegExp.cap(3) == "M" && fDew != 0 )
			fDew *= -1;
		
		weatherInfo.tempC = fTemp;
		weatherInfo.dewC = fDew;
		return true;
	}
	return false;
}

bool MetarParser::parseTemperatureTenths(const TQString &s)
{
	if (TempTenRegExp.search(s) > -1)
	{
		kdDebug(12006) << "Temp Tenths: " << TQString(TempTenRegExp.capturedTexts().join("-"))
				<< endl;
		
		float temperature = TempTenRegExp.cap( 1 ).toFloat() / 10;
		float dewPoint    = TempTenRegExp.cap( 2 ).toFloat() / 10;

		if ( temperature >= 100 )
		{
			temperature -= 100;
			temperature *= -1;
		}
		if ( dewPoint >= 100 )
		{
			dewPoint -= 100;
			dewPoint *= -1;
		}

		weatherInfo.tempC = temperature;
		weatherInfo.dewC = dewPoint;
		
		return true;
	}
	return false;
}

void MetarParser::calcTemperatureVariables()
{
#define E(t) ::pow(10, 7.5*t/(237.7+t))
	float fRelHumidity = E(weatherInfo.dewC)/E(weatherInfo.tempC) * 100;
        if (fRelHumidity > 100.0) fRelHumidity = 100.0; 

	weatherInfo.qsRelHumidity.sprintf("%.1f", fRelHumidity);
	removeTrailingDotZero(weatherInfo.qsRelHumidity);
	weatherInfo.qsRelHumidity += "%";

	float fHeatIndex = 0;
	float tempF = TEMP_C_TO_F(weatherInfo.tempC);
	if (tempF >= 80)
	{
#define SQR(a)  ((a)*(a))
		fHeatIndex = -42.379 + (2.04901523*tempF)
			+ (10.14333127*fRelHumidity)
			- (0.22475541*tempF*fRelHumidity)
			- (0.00683783*SQR(tempF))
			- (0.05481717*SQR(fRelHumidity))
			+ (0.00122874*SQR(tempF)*fRelHumidity)
			+ (0.00085282*tempF*SQR(fRelHumidity))
			- (0.00000199*SQR(tempF)*SQR(fRelHumidity));

		if ( fHeatIndex <= tempF )
			fHeatIndex = 0;
	}

	TQString unit;
	if (m_units == KLocale::Metric)
	{
		unit = i18n("°C");
		weatherInfo.qsTemperature.sprintf("%.1f", weatherInfo.tempC);
		weatherInfo.qsDewPoint.sprintf("%.1f", weatherInfo.dewC);
		if (fHeatIndex >= 80)
			weatherInfo.qsHeatIndex.sprintf("%.1f", TEMP_F_TO_C(fHeatIndex));
	}
	else
	{
		unit = i18n("°F");
		weatherInfo.qsTemperature.sprintf("%.1f", tempF);
		weatherInfo.qsDewPoint.sprintf("%.1f", TEMP_C_TO_F(weatherInfo.dewC));
		if (fHeatIndex >= 80)
			weatherInfo.qsHeatIndex.sprintf("%.1f", (fHeatIndex));
	}
	
	removeTrailingDotZero(weatherInfo.qsTemperature);
	removeTrailingDotZero(weatherInfo.qsDewPoint);
	removeTrailingDotZero(weatherInfo.qsHeatIndex);
	
	weatherInfo.qsTemperature += unit;
	weatherInfo.qsDewPoint += unit;
	if (!weatherInfo.qsHeatIndex.isEmpty())
		weatherInfo.qsHeatIndex += unit;
}

void MetarParser::removeTrailingDotZero(TQString &string)
{
	if ( string.right( 2 ) == ".0" )
	{
		string = string.left( string.length() - 2 );
	}
}

/** Parse out the current date. */
bool MetarParser::parseDate(const TQString &s)
{
	if (DateRegExp.search(s) > -1)
	{
		kdDebug(12006) << "Date: " << TQString(DateRegExp.capturedTexts().join("-"))
				<< endl;
		TQString dateString = DateRegExp.cap(1);
		TQString day, month, year;

		day = dateString.mid(8,2);
		month = dateString.mid(5,2);
		year = dateString.mid(0,4);

		TQDate theDate(year.toInt(), month.toInt(), day.toInt());


		weatherInfo.qsDate = theDate;
		return true;
	}
	return false;
}

/** Parse out the current time. */
bool MetarParser::parseTime(const TQString &s)
{
	if (TimeRegExp.search(s) > -1)
	{
		kdDebug(12006) << "Time: " << TQString(TimeRegExp.capturedTexts().join("-"))
				<< endl;

		TQString hour, minute, dateString;

		dateString = TimeRegExp.cap(1);
		hour = dateString.mid(0,2);
		minute = dateString.mid(3,2);
		TQTime theTime(hour.toInt(), minute.toInt());

		weatherInfo.qsTime = theTime;
		return true;
	}
	return false;
}

/** Parse out the current visibility */
bool MetarParser::parseVisibility(TQStringList::ConstIterator it)
{
	float fVisibility = 0;
	
	if (VisRegExp.search(*it) > -1)
	{
		fVisibility = VisRegExp.cap(1).toFloat();

		kdDebug(12006) << "Visibility: " << TQString(VisRegExp.capturedTexts().join("-"))
				<< endl;

	}
	else if (VisFracRegExp.search(*it) > -1)
	{
		// got a fractional visibility, go back to previous string in the list
		// and get the whole part.
		fVisibility = (*(it--)).toFloat();
		// shouldn't be necessary?  
		//it++;
		fVisibility += ( 1 / VisFracRegExp.cap(1).toFloat() );
	}
	
	if (fVisibility > 0)
	{
		if (m_units == KLocale::Metric)
		{
			fVisibility *= 1.6;
			weatherInfo.qsVisibility.setNum(fVisibility);
			weatherInfo.qsVisibility += i18n("km");
		}
		else
		{
			weatherInfo.qsVisibility.setNum(fVisibility);
			weatherInfo.qsVisibility += i18n("m");
		}
		return true;
	}
	else
	{
		return false;
	}
}

/** Parse out the current pressure. */
bool MetarParser::parsePressure( const TQString &s)
{
	if (PressRegExp.search(s) > -1)
	{
		TQString type = PressRegExp.cap(1);
		float fPressure = PressRegExp.cap(2).toFloat();

		kdDebug(12006) << "Pressure: " << TQString(PressRegExp.capturedTexts().join("-"))
				<< endl;

		if (m_units == KLocale::Metric)
		{
			if (type == "A")
				fPressure *= (33.8639/100);
			weatherInfo.qsPressure.setNum(fPressure, 'f', 0);
			weatherInfo.qsPressure += i18n(" hPa");
		}
		else
		{
			if (type == "Q")
				fPressure /= 33.8639;
			else
				fPressure /= 100;
			weatherInfo.qsPressure.setNum(fPressure, 'f', 2);
			weatherInfo.qsPressure += i18n("\" Hg");
		}
		return true;
	}
	return false;
}

struct wind_info
{
	unsigned int   number;
	TQString        name;
};

static const struct wind_info wind_direction[] = 
{
	{ 0,   i18n("N")   },   // North is 0 to 11, and so on
	{ 12,  i18n("NNE") },
	{ 33,  i18n("NE")  },
	{ 57,  i18n("ENE") },
	{ 79,  i18n("E")   }, 
	{ 102, i18n("ESE") },
	{ 124, i18n("SE")  },
	{ 147, i18n("SSE") },
	{ 169, i18n("S")   },
	{ 192, i18n("SSW") },
	{ 214, i18n("SW")  },
	{ 237, i18n("WSW") },
	{ 259, i18n("W")   },
	{ 282, i18n("WNW") },
	{ 304, i18n("NW")  },
	{ 327, i18n("NNW") },
	{ 349, i18n("N")   },
	{ 360, i18n("N")   }
};


TQString MetarParser::parseWindDirection(const unsigned int direction)
{
	unsigned int i = 0;
	
	for (i = 0; i < (sizeof(wind_direction) / sizeof(wind_info)) - 1; i++)
	{
		if (direction >= wind_direction[i].number &&
		    direction < wind_direction[i + 1].number)
		{
			break;
		}
	}
	
	return wind_direction[i].name;
}

/** Parse the wind speed */
bool MetarParser::parseWindSpeed(const TQString &s)
{
	if (WindRegExp.search(s) > -1)
	{
		unsigned int direction = WindRegExp.cap(1).toInt();
		float windSpeed = WindRegExp.cap(2).toFloat();
		float gustSpeed = WindRegExp.cap(3).toFloat();
		TQString sWindUnit = WindRegExp.cap(4);

		kdDebug(12006) << "Wind: " << WindRegExp.capturedTexts().join("-")
				<< endl;

		if (m_units == KLocale::Metric)
		{
			if (sWindUnit == "KT")
			{
				windSpeed = (windSpeed * 3.6 / 1.94);
				gustSpeed = (gustSpeed * 3.6 / 1.94);
			}
			else if (sWindUnit == "MPS")
			{
				windSpeed = (windSpeed * 3.6);
				gustSpeed = (gustSpeed * 3.6);
			}
			weatherInfo.windMPH = (windSpeed / 1.61);
			weatherInfo.qsWindSpeed = i18n("1 km/h", "%n km/h", (int) windSpeed);
		}
		else
		{
			if (sWindUnit == "KT")
			{
				windSpeed = (windSpeed * 2.24 / 1.94);
				gustSpeed = (gustSpeed * 2.24 / 1.94);
			}
			else if (sWindUnit == "KMH")
			{
				windSpeed = (windSpeed / 1.61);
				gustSpeed = (gustSpeed / 1.61);
			}
			else if (sWindUnit == "MPS")
			{
				windSpeed = (windSpeed * 2.24);
				gustSpeed = (gustSpeed * 2.24);
			}
			weatherInfo.windMPH = windSpeed;
			weatherInfo.qsWindSpeed = i18n("1 MPH", "%n MPH", (int) windSpeed);
		}

		if (gustSpeed >= 1)
		{
			if (m_units == KLocale::Metric)
			{
				weatherInfo.qsCurrentList << i18n("Wind gusts up to 1 km/h", 
			     "Wind gusts up to %n km/h", (int) gustSpeed);
			}
			else
			{
				weatherInfo.qsCurrentList << i18n("Wind gusts up to 1 MPH", 
			     "Wind gusts up to %n MPH", (int) gustSpeed);
			}
		}

		if ((WindRegExp.cap(1) != "VRB") && (windSpeed >= 1))
		{
			weatherInfo.qsWindDirection = parseWindDirection(direction);
		}
		return true;
	}
	return false;
}

bool MetarParser::parseStationNeedsMaintenance(const TQString &s)
{
	if (s == "$")
	{
		weatherInfo.stationNeedsMaintenance = true;
		kdDebug(12006) << "Station Needs Maintenance" << endl;
		return true;
	}
	
	return false;
}

void MetarParser::calcCurrentIcon()
{
	// Default to overcast clouds
	if ( weatherInfo.clouds == -1 )
		weatherInfo.clouds = 64;

	if (weatherInfo.theWeather.isEmpty())
	{
		if (weatherInfo.clouds == 0)
			weatherInfo.theWeather = iconName("sunny");
		else if (weatherInfo.clouds > 0 && weatherInfo.clouds <= 2)
			weatherInfo.theWeather = iconName("cloudy1");
		else if ( weatherInfo.clouds > 2 && weatherInfo.clouds <= 4)
			weatherInfo.theWeather = iconName("cloudy2");
		else if ( weatherInfo.clouds > 4 && weatherInfo.clouds <= 8)
			weatherInfo.theWeather = iconName("cloudy3");
		else if ( weatherInfo.clouds > 8 && weatherInfo.clouds < 63)
			weatherInfo.theWeather = iconName( "cloudy4" );
		else
			weatherInfo.theWeather = "cloudy5";
	}
	else if (weatherInfo.theWeather == "tstorm")
	{
		if ( weatherInfo.heavy )
			weatherInfo.clouds = 30;

		if (weatherInfo.clouds >= 0 && weatherInfo.clouds <= 10)
			weatherInfo.theWeather = iconName("tstorm1");
		else if ( weatherInfo.clouds > 10 && weatherInfo.clouds <= 20)
			weatherInfo.theWeather = iconName("tstorm2");
		else
			weatherInfo.theWeather = "tstorm3";
	}
	else if (weatherInfo.theWeather == "shower")
	{
		if ( weatherInfo.heavy )
			weatherInfo.clouds = 30;

		if (weatherInfo.clouds >= 0 && weatherInfo.clouds <= 10)
			weatherInfo.theWeather = iconName("shower1");
		else if ( weatherInfo.clouds > 10 && weatherInfo.clouds <= 20)
			weatherInfo.theWeather = iconName("shower2");
		else
			weatherInfo.theWeather = "shower3";
	}
	else if (weatherInfo.theWeather == "snow")
	{
		if ( weatherInfo.heavy )
			weatherInfo.clouds = 30;

		if (weatherInfo.clouds >= 0 && weatherInfo.clouds <= 8)
			weatherInfo.theWeather = iconName("snow1");
		else if ( weatherInfo.clouds > 8 && weatherInfo.clouds <= 16)
			weatherInfo.theWeather = iconName("snow2");
		else if (weatherInfo.clouds > 16 && weatherInfo.clouds <= 24)
			weatherInfo.theWeather = iconName("snow3");
		else
			weatherInfo.theWeather = "snow5";
	}
	else if (isNight(weatherInfo.reportLocation) && weatherInfo.theWeather == "mist")
		weatherInfo.theWeather = "mist_night";
	else if (isNight(weatherInfo.reportLocation) && weatherInfo.theWeather == "fog")
		weatherInfo.theWeather = "fog_night";
	else if ( weatherInfo.theWeather == "mist" || weatherInfo.theWeather == "fog" )
	{
		if ( weatherInfo.clouds >= 63 )
			weatherInfo.theWeather = "cloudy5";
	}

	kdDebug(12006) << "Clouds: " << weatherInfo.clouds << ", Icon: "
			<< weatherInfo.theWeather << endl;
}

void MetarParser::calcWindChill()
{
	float windChill = 35.74 + (0.6215 * TEMP_C_TO_F(weatherInfo.tempC))
			- (35.75 * ::pow(weatherInfo.windMPH, 0.16))
			+ (0.4275 * TEMP_C_TO_F(weatherInfo.tempC) * ::pow(weatherInfo.windMPH, 0.16));

	kdDebug(12006) << "Wind Chill: " << windChill << endl;

	if (windChill < 48)
	{
		if (m_units == KLocale::Metric)
		{
			weatherInfo.qsWindChill.setNum(TEMP_F_TO_C(windChill), 'f', 1);
			removeTrailingDotZero(weatherInfo.qsWindChill);
			weatherInfo.qsWindChill += i18n("°C");
		}
		else
		{
			weatherInfo.qsWindChill.setNum(windChill, 'f', 1);
			removeTrailingDotZero(weatherInfo.qsWindChill);
			weatherInfo.qsWindChill += i18n("°F");
		}
	}
}

bool MetarParser::isNight(const TQString &stationID) const
{
	TQString upperStationID = stationID.upper();
	TQString latitude  = m_stationDb->stationLatitudeFromID(upperStationID);
	TQString longitude = m_stationDb->stationLongitudeFromID(upperStationID);

	if ( latitude.compare(  i18n("Unknown Station" ) ) == 0  ||
	     longitude.compare( i18n("Unknown Station" ) ) == 0 )
	{
		return false;
	}
	else
	{
		Sun theSun( latitude, longitude , m_date, m_localUTCOffset );
		
		TQTime currently = m_time;

		TQTime civilStart = theSun.computeCivilTwilightStart();
		TQTime civilEnd   = theSun.computeCivilTwilightEnd();

		kdDebug (12006) << "station, current, lat, lon, start, end, offset: " << 
				upperStationID << " " << currently << " " << latitude << " " << 
				longitude << " " << civilStart << " " << civilEnd << " " <<
				m_localUTCOffset << endl;

		if (civilStart != civilEnd)
		{
			if (civilEnd < civilStart)
				/* Handle daylight past midnight in local time     */
				/* for weather stations located at other timezones */
				return (currently < civilStart && currently > civilEnd);
			else
				return (currently < civilStart || currently > civilEnd);
		}
		else
		{
			// Midnight Sun & Polar Night - In summer, the Sun is always 
			// over the horizon line ... so use latitude & today date to 
			// set isNight() value. 
			return ((m_date.daysInYear() >= 80 || m_date.daysInYear() <= 264) && latitude.contains("S")); 
		}
	}
}

TQString MetarParser::iconName( const TQString &icon ) const
{
	TQString _iconName = icon;

	if ( isNight( weatherInfo.reportLocation ) )
		_iconName += "_night";

	return _iconName;
}

