/*
Weather Protocol plugin for Miranda IM
Copyright (C) 2005-2011 Boris Krasnovskiy All Rights Reserved
Copyright (C) 2002-2005 Calvin Che
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; version 2
of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.  If not, see .
*/
/* 
This file contain the source related loading, obtaining, and
saving individual weather data for a weather contact.
*/
#include "weather.h"
// temporary counter and storage for removing weather data
typedef struct 
{
	char *value[1024];
	int current;
} WCOUNTER;
//============  LOAD WEATHER INFO FROM A CONTACT  ============
// get station ID from DB
// hContact = the current contact handle
// return value = the string for station ID
void GetStationID(HANDLE hContact, char* id, size_t idlen) 
{
	// accessing the database
	if (DBGetStaticString(hContact, WEATHERPROTONAME, "ID", id, idlen))
		id[0] = 0;
}
// initialize weather info by loading values from database
// Change = current contact handle
// return value = the current weather information in WEATHERINFO struct
WEATHERINFO LoadWeatherInfo(HANDLE Change) 
{
	WEATHERINFO winfo;
	winfo.hContact = Change;
	// obtaining values from the DB
	// assuming station ID must exist at all time, but others does not have to
	// if the string is not found in database, a value of "N/A" is stored in the field
	GetStationID(Change, winfo.id, sizeof(winfo.id));
	if (DBGetStaticString(Change, WEATHERPROTONAME, "Nick", winfo.city, sizeof(winfo.city)))
		strcpy(winfo.city, NODATA);
	if (DBGetStaticString(Change, WEATHERCONDITION, "Update", winfo.update, sizeof(winfo.update)))
		strcpy(winfo.update, NODATA);
	if (DBGetStaticString(Change, WEATHERCONDITION, "Condition", winfo.cond, sizeof(winfo.cond)))
		strcpy(winfo.cond, NODATA);
	if (DBGetStaticString(Change, WEATHERCONDITION, "Temperature", winfo.temp, sizeof(winfo.temp)))
		strcpy(winfo.temp, NODATA);
	if (DBGetStaticString(Change, WEATHERCONDITION, "High", winfo.high, sizeof(winfo.high)))
		strcpy(winfo.high, NODATA);
	if (DBGetStaticString(Change, WEATHERCONDITION, "Low", winfo.low, sizeof(winfo.low)))
		strcpy(winfo.low, NODATA);
	if (DBGetStaticString(Change, WEATHERCONDITION, "Sunset", winfo.sunset, sizeof(winfo.sunset)))
		strcpy(winfo.sunset, NODATA);
	if (DBGetStaticString(Change, WEATHERCONDITION, "Sunrise", winfo.sunrise, sizeof(winfo.sunrise)))
		strcpy(winfo.sunrise, NODATA);
	if (DBGetStaticString(Change, WEATHERCONDITION, "Wind Speed", winfo.wind, sizeof(winfo.wind)))
		strcpy(winfo.wind, NODATA);
	if (DBGetStaticString(Change, WEATHERCONDITION, "Wind Direction", winfo.winddir, sizeof(winfo.winddir)))
		strcpy(winfo.winddir, NODATA);
	if (DBGetStaticString(Change, WEATHERCONDITION, "Dewpoint", winfo.dewpoint, sizeof(winfo.dewpoint)))
		strcpy(winfo.dewpoint, NODATA);
	if (DBGetStaticString(Change, WEATHERCONDITION, "Pressure", winfo.pressure, sizeof(winfo.pressure)))
		strcpy(winfo.pressure, NODATA);
	if (DBGetStaticString(Change, WEATHERCONDITION, "Visibility", winfo.vis, sizeof(winfo.vis)))
		strcpy(winfo.vis, NODATA);
	if (DBGetStaticString(Change, WEATHERCONDITION, "Humidity", winfo.humid, sizeof(winfo.humid)))
		strcpy(winfo.humid, NODATA);
	if (DBGetStaticString(Change, WEATHERCONDITION, "Feel", winfo.feel, sizeof(winfo.feel)))
		strcpy(winfo.feel, NODATA);
	winfo.status = (WORD)DBGetContactSettingWord(Change, WEATHERPROTONAME, "StatusIcon", ID_STATUS_OFFLINE);
	return winfo;
}
// getting weather setting from database
// return 0 on success
int DBGetData(HANDLE hContact, char *setting, DBVARIANT *dbv) 
{
	if (DBGetContactSettingString(hContact, WEATHERCONDITION, setting, dbv)) 
	{
		size_t len = strlen(setting) + 1;
		char *set = (char*)alloca(len + 1);
		*set = '#';
		memcpy(set + 1, setting, len);
		if (DBGetContactSettingString(hContact, WEATHERCONDITION, set, dbv))
			return 1;
	}
	return 0;
}
int DBGetStaticString(HANDLE hContact, const char *szModule, const char *valueName, char *dest, size_t dest_len)
{
	DBVARIANT dbv;
	DBCONTACTGETSETTING sVal;
	dbv.pszVal = dest;
	dbv.cchVal = (WORD)dest_len;
	dbv.type = DBVT_ASCIIZ;
	sVal.pValue = &dbv;
	sVal.szModule = szModule;
	sVal.szSetting = valueName;
	if ( CallService( MS_DB_CONTACT_GETSETTINGSTATIC, ( WPARAM )hContact, ( LPARAM )&sVal ) != 0 )
		return 1;
	return ( dbv.type != DBVT_ASCIIZ );
}
//============  ERASE OLD SETTINGS  ============
// erase all current weather information from database
// lastver = the last used version number in dword (using PLUGIN_MAKE_VERSION)
void EraseAllInfo(DWORD lastver) 
{
	char str[255];
	int ContactCount = 0;
	HANDLE hContact, LastContact = NULL;
	DBVARIANT dbv;
	// loop through all contacts
	hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
	while(hContact) 
	{
		// see if the contact is a weather contact
		if(IsMyContact(hContact)) 
		{
			// check for upgrade
			if (lastver < __VERSION_DWORD)	UpgradeContact(lastver, hContact);
			DBWriteContactSettingWord(hContact,WEATHERPROTONAME,"Status",ID_STATUS_OFFLINE);
			DBWriteContactSettingWord(hContact,WEATHERPROTONAME,"StatusIcon",ID_STATUS_OFFLINE);
			DBDeleteContactSetting(hContact,"CList","MyHandle");
			// clear all data
			if (DBGetContactSettingString(hContact, WEATHERPROTONAME, "Nick", &dbv)) {
				DBWriteContactSettingString(hContact, WEATHERPROTONAME, "Nick", Translate(""));
				DBWriteContactSettingString(hContact, WEATHERPROTONAME, "LastLog", "never");
				DBWriteContactSettingString(hContact, WEATHERPROTONAME, "LastCondition", "None");
				DBWriteContactSettingString(hContact, WEATHERPROTONAME, "LastTemperature", "None");
			}
			else
				DBFreeVariant(&dbv);
/*
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"Update", NODATA);
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"Condition", NODATA);
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"Temperature", NODATA);
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"High", NODATA);
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"Low", NODATA);
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"Humidity", NODATA);
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"Wind Speed", NODATA);
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"Wind Direction", NODATA);
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"Pressure", NODATA);
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"Visibility", NODATA);
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"Dewpoint", NODATA);
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"Feel", NODATA);
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"Heat Index", NODATA);
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"Sunrise", NODATA);
			DBWriteContactSettingString(hContact,WEATHERCONDITION,"Sunset", NODATA);
*/
			DBDataManage(hContact, WDBM_REMOVE, 0, 0);
			DBWriteContactSettingString(hContact, "UserInfo", "MyNotes", "");
			// reset update tag
			DBWriteContactSettingByte(hContact,WEATHERPROTONAME,"IsUpdated",FALSE);
			// reset logging settings
			if (!DBGetContactSettingString(hContact,WEATHERPROTONAME,"Log",&dbv))
			{
				DBWriteContactSettingByte(hContact,WEATHERPROTONAME,"File",(BYTE)(dbv.pszVal[0] != 0));
				DBFreeVariant(&dbv);
			}
			else
				DBWriteContactSettingByte(hContact,WEATHERPROTONAME,"File",FALSE);
			// if no default station find, assign a new one
			if (opt.Default[0] == 0) 
			{
				GetStationID(hContact, opt.Default, sizeof(opt.Default));
				opt.DefStn = hContact;
				if (!DBGetContactSettingString(hContact,WEATHERPROTONAME,"Nick",&dbv))
				{
					wsprintf(str, Translate("%s is now the default weather station"), dbv.pszVal);
					DBFreeVariant(&dbv);
					MessageBox(NULL, str, Translate("Weather Protocol"), MB_OK|MB_ICONINFORMATION);
				}
			}
			// get the handle of the default station
			if (opt.DefStn == NULL) {
				if (!DBGetContactSettingString(hContact,WEATHERPROTONAME,"ID",&dbv))
				{
					if (!strcmp(dbv.pszVal, opt.Default))	opt.DefStn = hContact;
					DBFreeVariant(&dbv);
				}
			}
			ContactCount++;		// increment counter
			LastContact = hContact;
		}
		hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0);
	}
	// if weather contact exists, set the status to online so it is ready for update
	// if (ContactCount != 0) status = ONLINE;
	// in case where the default station is missing
	if (opt.DefStn == NULL && ContactCount != 0) 
	{
		if (!DBGetContactSettingString(LastContact,WEATHERPROTONAME,"ID",&dbv))
		{
			strcpy(opt.Default, dbv.pszVal);
			DBFreeVariant(&dbv);
		}
		opt.DefStn = LastContact;
		if (!DBGetContactSettingString(LastContact,WEATHERPROTONAME,"Nick",&dbv))
		{
			wsprintf(str, Translate("%s is now the default weather station"), dbv.pszVal);
			DBFreeVariant(&dbv);
			MessageBox(NULL, str, Translate("Weather Protocol"), MB_OK|MB_ICONINFORMATION);
		}
	}
	// save option in case of default station changed
	DBWriteContactSettingString(NULL, WEATHERPROTONAME, "Default", opt.Default);
}
void ConvertDataValue(WIDATAITEM *UpdateData, char *Data) 
{
	char str[MAX_DATA_LEN];
	// convert the unit
	if (strcmp(Data, Translate("")) && strcmp(Data, NODATA) && strcmp(Data, Translate(NODATA))) 
	{
		// temperature
		if (!strcmp(UpdateData->Name, "Temperature") || !strcmp(UpdateData->Name, "High") || 
			!strcmp(UpdateData->Name, "Low") || !strcmp(UpdateData->Name, "Feel") || 
			!strcmp(UpdateData->Name, "Dewpoint") ||
			!_stricmp(UpdateData->Unit, "C") || !_stricmp(UpdateData->Unit, "F") || 
			!_stricmp(UpdateData->Unit, "K"))
		{
			GetTemp(Data, UpdateData->Unit, str);
			strcpy(Data, str);
		}
		// pressure
		else if (!strcmp(UpdateData->Name, "Pressure") || !_stricmp(UpdateData->Unit, "HPA") || 
			!_stricmp(UpdateData->Unit, "KPA") || !_stricmp(UpdateData->Unit, "MB") ||
			!_stricmp(UpdateData->Unit, "TORR") || !_stricmp(UpdateData->Unit, "IN") || 
			!_stricmp(UpdateData->Unit, "MM"))
		{
			GetPressure(Data, UpdateData->Unit, str);
			strcpy(Data, str);
		}
		// speed
		else if (!strcmp(UpdateData->Name, "Wind Speed") || !_stricmp(UpdateData->Unit, "KM/H") || 
			!_stricmp(UpdateData->Unit, "M/S") || !_stricmp(UpdateData->Unit, "MPH") || 
			!_stricmp(UpdateData->Unit, "KNOTS"))
		{
			GetSpeed(Data, UpdateData->Unit, str);
			strcpy(Data, str);
		}
		// visibility
		else if (!strcmp(UpdateData->Name, "Visibility") || !_stricmp(UpdateData->Unit, "KM") || 
			!_stricmp(UpdateData->Unit, "MILES"))
		{
			GetDist(Data, UpdateData->Unit, str);
			strcpy(Data, str);
		}
		// elevation
		else if (!strcmp(UpdateData->Name, "Elevation") || !_stricmp(UpdateData->Unit, "FT") || 
			!_stricmp(UpdateData->Unit, "M"))
		{
			GetElev(Data, UpdateData->Unit, str);
			strcpy(Data, str);
		}
		// converting case for condition to the upper+lower format
		else if (!_stricmp(UpdateData->Unit, "COND"))
			CaseConv(Data);
		// degree sign
		else if (!_stricmp(UpdateData->Unit, "DEG"))
		{
			if (!opt.DoNotAppendUnit) strcat(Data, opt.DegreeSign);
		}
		// percent sign
		else if (!_stricmp(UpdateData->Unit, "%"))
		{
			if (!opt.DoNotAppendUnit) strcat(Data, "%");
		}
		// truncating strings for day/month to 2 or 3 characters
		else if (!_stricmp(UpdateData->Unit, "DAY") || !_stricmp(UpdateData->Unit, "MONTH"))
			if (opt.dUnit > 1 && strlen(Data) > opt.dUnit)		Data[opt.dUnit] = '\0';
	}
}
//============  GET THE VALUE OF A DATAITEM  ============
// get the value of the data using the start, end strings
// UpdateData = the WIDATAITEM struct containing start, end, unit
// Data = the string containing weather data obtained from UpdateData
// global var. used: szInfo = the downloaded string
void GetDataValue(WIDATAITEM *UpdateData, char *Data, char** szData) 
{
	char last = 0, current, *start, *end;
	unsigned startloc = 0, endloc = 0, respos = 0;
	BOOL tag = FALSE, symb = FALSE;
	char *szInfo = *szData;
	Data[0] = 0;
	// parse the data if available
	if (UpdateData->Start[0] == 0 && UpdateData->End[0] == 0) return;
	start = szInfo;
	// the start string must be found
	if (UpdateData->Start[0] != 0) {
		start = strstr(szInfo, UpdateData->Start);
		if (start != NULL) {
			// set the starting location for getting data
			start += strlen(UpdateData->Start);
			szInfo = start;
		}
	}
	// the end string must be found too
	if (UpdateData->End[0] != 0)
		end = strstr(szInfo, UpdateData->End);
	else end = strstr(szInfo, " ");
	if (end != NULL) {
		// set the ending location
		startloc = 0;
		endloc = end - szInfo;
		end += strlen(UpdateData->End);
		last = '\n';
	}
	// ignore if not both of the string found - this prevent crashes
	if (start != NULL && end != NULL) 
	{
		// begin reading the data from start location to end location
		// remove all HTML tag in between, as well as leading space, ending space,
		// multiple spaces, tabs, and return key
		while (startloc < endloc) 
		{
			if (szInfo[startloc] == '<')	tag = TRUE;
			else if (szInfo[startloc] == '&' &&
				(szInfo[startloc+1] == ';' || szInfo[startloc+2] == ';' || szInfo[startloc+3] == ';' || 
				szInfo[startloc+4] == ';' || szInfo[startloc+5] == ';' || szInfo[startloc+6] == ';'))
				symb = TRUE;
			else if (szInfo[startloc] == '>')	tag = FALSE;
			else if (szInfo[startloc] == ';')	symb = FALSE;
			else {
				if (!tag && !symb) {
					current = szInfo[startloc];
					if (current == '\n' || current == '\t' || current == '	' || current == '\r')
						current = ' ';
					if (current != ' ' || last != ' ') {
						if (last != '\n' && (respos != 0 || (respos == 0 && last != ' ')))
							Data[respos++] = last;
						last = current;
					}
				}
			}
			++startloc;
			// prevent crashes if the string go over maximun length -> generate an error
			if (respos >= MAX_DATA_LEN) {
				if (opt.ShowWarnings && UpdateData->Name[0] != 0 && strcmp(UpdateData->Name, "Ignore")) {
					mir_snprintf(Data, MAX_DATA_LEN, Translate("Error when obtaining data: %s"), UpdateData->Name);
					WPShowMessage(Data, SM_WARNING);
				}
				strncpy(Data, Translate(""), MAX_DATA_LEN);
				last = ' ';
				respos = MAX_DATA_LEN - 1;
				break;
			}
		}
		// get the last character
		if (last != ' ') 
			Data[respos++] = last;
		// null terminate the string
		Data[respos] = 0;
		// write raw data for debug
		Netlib_Logf(hNetlibUser, "%s: %s", UpdateData->Name, Data);
		// convert the unit
		ConvertDataValue(UpdateData, Data);
		// remove the string before the data from szInfo
		szInfo = end;
	}
	*szData = szInfo;
}
//============ ALLOCATE SPACE AND COPY STRING ============
// copy a string into a new memory location
// Data = the field the data is copied to
// Value = the original string, the string where data is copied from
void wSetData(char **Data, const char *Value) 
{
	if (Value[0] != 0)
	{
		char *newData = (char*)mir_alloc(strlen(Value)+3);
		strcpy(newData, Value);
		*Data = newData;
	}
	else
		*Data = "";
}
// A safer free function that free memory for a string
// Data = the string occuping the data to be freed
void wfree(char **Data) 
{
	if (*Data && strlen(*Data) > 0)	mir_free(*Data);
	*Data = NULL;
}
//============ MANAGE THE ITEMS STORED IN DB ============
// remove or display the weatehr information for a contact
// hContact - the contact in which the info is going to be removed
void DBDataManage(HANDLE hContact, WORD Mode, WPARAM wParam, LPARAM lParam) 
{
	DBCONTACTENUMSETTINGS dbces;
	DBVARIANT dbv;
	WCOUNTER wc;
	wc.current = 0;
	dbces.lParam=(LPARAM)&wc;
	dbces.pfnEnumProc=GetWeatherDataFromDB;
	dbces.szModule=WEATHERCONDITION;
	// get all the settings and stored them in a temporary list
	if(CallService(MS_DB_CONTACT_ENUMSETTINGS,(WPARAM)hContact,(LPARAM)&dbces)==-1)
		wc.current--;
	// begin deleting settings
	for (; --wc.current>-1;) 
	{
		if (!DBGetContactSettingString(hContact, WEATHERCONDITION, wc.value[wc.current], &dbv)) 
		{
			switch (Mode) 
			{
			case WDBM_REMOVE:
				DBDeleteContactSetting(hContact, WEATHERCONDITION, wc.value[wc.current]);
				break;
			case WDBM_DETAILDISPLAY: 
				{
					HWND hList = GetDlgItem((HWND)wParam, IDC_DATALIST);
					LV_ITEM lvi = { 0 };
					// skip the "WeatherInfo" variable
					if (strcmp(wc.value[wc.current], "WeatherInfo") == 0 ||
						strcmp(wc.value[wc.current], "Ignore") == 0 ||
						wc.value[wc.current][0] == '#')
					{
						wfree(&wc.value[wc.current]);
						DBFreeVariant(&dbv);
						continue;
					}
					lvi.mask = LVIF_TEXT | LVIF_PARAM;
					lvi.iItem = 0;
					lvi.iSubItem = 0;
					lvi.lParam = (LPARAM)wc.current;
					lvi.pszText = (LPSTR)Translate(wc.value[wc.current]);
					lvi.iItem = ListView_InsertItemWth(hList, &lvi);
					lvi.pszText = dbv.pszVal;
					ListView_SetItemTextWth(hList, lvi.iItem, 1, lvi.pszText);
					break;
				}
			}
			DBFreeVariant(&dbv);
		}
		wfree(&wc.value[wc.current]);
	}
}
// get single setting that is found
// szSetting = the setting name
// lparam = the counter
int GetWeatherDataFromDB(const char *szSetting, LPARAM lparam) 
{
	WCOUNTER *wc = (WCOUNTER*)lparam;
	wc->value[wc->current] = (char *)mir_alloc(strlen(szSetting) + 1);
	strcpy(wc->value[wc->current], szSetting);
	wc->current++;
	return 0;
}