/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2009 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

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.

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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include "..\..\core\commonheaders.h"
#include "../srfile/file.h"

static bool bModuleInitialized = false;
static HANDLE hIniChangeNotification;

extern TCHAR mirandabootini[MAX_PATH];
extern bool dbCreated;

static INT_PTR CALLBACK InstallIniDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch(message) {
		case WM_INITDIALOG:
			TranslateDialogDefault(hwndDlg);
			SetDlgItemText(hwndDlg, IDC_ININAME, (TCHAR*)lParam);
			{	
                TCHAR szSecurity[11];
                const TCHAR *pszSecurityInfo;
			  	
                GetPrivateProfileString(_T("AutoExec"), _T("Warn"), _T("notsafe"), szSecurity, SIZEOF(szSecurity), mirandabootini);
				if ( !lstrcmpi(szSecurity, _T("all")))
					pszSecurityInfo = LPGENT("Security systems to prevent malicious changes are in place and you will be warned before every change that is made.");
				else if ( !lstrcmpi(szSecurity, _T("onlyunsafe")))
					pszSecurityInfo = LPGENT("Security systems to prevent malicious changes are in place and you will be warned before changes that are known to be unsafe.");
				else if ( !lstrcmpi(szSecurity, _T("none")))
					pszSecurityInfo = LPGENT("Security systems to prevent malicious changes have been disabled. You will receive no further warnings.");
				else pszSecurityInfo = NULL;
				if (pszSecurityInfo) SetDlgItemText(hwndDlg, IDC_SECURITYINFO, TranslateTS(pszSecurityInfo));
			}
			return TRUE;
		case WM_COMMAND:
			switch(LOWORD(wParam)) {
				case IDC_VIEWINI:
				{	TCHAR szPath[MAX_PATH];
					GetDlgItemText(hwndDlg, IDC_ININAME, szPath, SIZEOF(szPath));
					ShellExecute(hwndDlg, _T("open"), szPath, NULL, NULL, SW_SHOW);
					break;
				}
				case IDOK:
				case IDCANCEL:
				case IDC_NOTOALL:
					EndDialog(hwndDlg, LOWORD(wParam));
					break;
			}
			break;
	}
	return FALSE;
}

static bool IsInSpaceSeparatedList(const char *szWord, const char *szList)
{
	const char *szItem, *szEnd;
	int wordLen = lstrlenA(szWord);

	for (szItem = szList;;) {
		szEnd = strchr(szItem, ' ');
		if (szEnd == NULL)
			return !lstrcmpA(szItem, szWord);
		if (szEnd - szItem == wordLen) {
			if ( !strncmp(szItem, szWord, wordLen))
				return true;
		}
		szItem = szEnd+1;
}	}

struct warnSettingChangeInfo_t {
	TCHAR *szIniPath;
	char *szSection;
	char *szSafeSections;
	char *szUnsafeSections;
	char *szName;
	char *szValue;
	int warnNoMore, cancel;
};

static INT_PTR CALLBACK WarnIniChangeDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	static struct warnSettingChangeInfo_t *warnInfo;

	switch(message) {
		case WM_INITDIALOG:
		{	char szSettingName[256];
			const TCHAR *pszSecurityInfo;
			warnInfo = (warnSettingChangeInfo_t*)lParam;
			TranslateDialogDefault(hwndDlg);
			SetDlgItemText(hwndDlg, IDC_ININAME, warnInfo->szIniPath);
			lstrcpyA(szSettingName, warnInfo->szSection);
			lstrcatA(szSettingName, " / ");
			lstrcatA(szSettingName, warnInfo->szName);
			SetDlgItemTextA(hwndDlg, IDC_SETTINGNAME, szSettingName);
			SetDlgItemTextA(hwndDlg, IDC_NEWVALUE, warnInfo->szValue);
			if (IsInSpaceSeparatedList(warnInfo->szSection, warnInfo->szSafeSections))
				pszSecurityInfo=LPGENT("This change is known to be safe.");
			else if (IsInSpaceSeparatedList(warnInfo->szSection, warnInfo->szUnsafeSections))
				pszSecurityInfo=LPGENT("This change is known to be potentially hazardous.");
			else
				pszSecurityInfo=LPGENT("This change is not known to be safe.");
			SetDlgItemText(hwndDlg, IDC_SECURITYINFO, TranslateTS(pszSecurityInfo));
			return TRUE;
		}
		case WM_COMMAND:
			switch(LOWORD(wParam)) {
				case IDCANCEL:
					warnInfo->cancel=1;
				case IDYES:
				case IDNO:
					warnInfo->warnNoMore=IsDlgButtonChecked(hwndDlg, IDC_WARNNOMORE);
					EndDialog(hwndDlg, LOWORD(wParam));
					break;
			}
			break;
	}
	return FALSE;
}

static INT_PTR CALLBACK IniImportDoneDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch(message) {
		case WM_INITDIALOG:
			TranslateDialogDefault(hwndDlg);
			SetDlgItemText(hwndDlg, IDC_ININAME, (TCHAR*)lParam);
			SetDlgItemText(hwndDlg, IDC_NEWNAME, (TCHAR*)lParam);
			return TRUE;
		case WM_COMMAND:
		{	TCHAR szIniPath[MAX_PATH];
			GetDlgItemText(hwndDlg, IDC_ININAME, szIniPath, SIZEOF(szIniPath));
			switch(LOWORD(wParam)) {
				case IDC_DELETE:
					DeleteFile(szIniPath);
				case IDC_LEAVE:
					EndDialog(hwndDlg, LOWORD(wParam));
					break;
				case IDC_RECYCLE:
					{	SHFILEOPSTRUCT shfo={0};
						shfo.wFunc=FO_DELETE;
						shfo.pFrom=szIniPath;
						szIniPath[lstrlen(szIniPath)+1]='\0';
						shfo.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOF_ALLOWUNDO;
						SHFileOperation(&shfo);
					}
					EndDialog(hwndDlg, LOWORD(wParam));
					break;
				case IDC_MOVE:
					{	TCHAR szNewPath[MAX_PATH];
						GetDlgItemText(hwndDlg, IDC_NEWNAME, szNewPath, SIZEOF(szNewPath));
						MoveFile(szIniPath, szNewPath);
					}
					EndDialog(hwndDlg, LOWORD(wParam));
					break;
			}
			break;
		}
	}
	return FALSE;
}

// settings:
struct SettingsList
{
	char *name;
	SettingsList *next;
} *setting_items = NULL;

int SettingsEnumProc(const char *szSetting, LPARAM lParam)
{
	SettingsList *newItem =  (SettingsList *)mir_alloc(sizeof(SettingsList));
	newItem->name = mir_strdup(szSetting);
	newItem->next = setting_items;
	setting_items = newItem;
	return 0;
}

static void ConvertBackslashes(char *str, UINT fileCp)
{
	char *pstr;
	for (pstr = str; *pstr; pstr = CharNextExA(fileCp, pstr, 0)) {
		if (*pstr == '\\') {
			switch(pstr[1]) {
			case 'n': *pstr = '\n'; break;
			case 't': *pstr = '\t'; break;
			case 'r': *pstr = '\r'; break;
			default:  *pstr = pstr[1]; break;
			}
			memmove(pstr+1, pstr+2, strlen(pstr+2) + 1);
}	}	}

static void ProcessIniFile(TCHAR* szIniPath, char *szSafeSections, char *szUnsafeSections, int secur, bool secFN)
{
	FILE *fp = _tfopen(szIniPath, _T("rt"));
	if (fp == NULL)
		return;

	bool warnThisSection = false;
	char szSection[128]; szSection[0] = 0;

	while ( !feof(fp)) {
		char szLine[2048];
		if (fgets(szLine, sizeof(szLine), fp) == NULL) 
			break;

		int lineLength = lstrlenA(szLine);
		while (lineLength && (BYTE)(szLine[lineLength-1])<=' ')
			szLine[--lineLength]='\0';

		if (szLine[0] == ';' || szLine[0]<=' ') 
			continue;

		if (szLine[0] == '[') {
			char *szEnd = strchr(szLine+1, ']');
			if (szEnd == NULL)
				continue;

			if (szLine[1] == '!')
				szSection[0] = '\0';
			else {
				lstrcpynA(szSection, szLine+1, min(sizeof(szSection), (int)(szEnd-szLine)));
				switch (secur) {
				case 0:
					warnThisSection = false;
					break;

				case 1:
					warnThisSection = !IsInSpaceSeparatedList(szSection, szSafeSections);
					break;

				case 2:
					warnThisSection = IsInSpaceSeparatedList(szSection, szUnsafeSections);
					break;

				default:
					warnThisSection = true;
					break;
				}
				if (secFN) warnThisSection=0;
			}
			if (szLine[1] == '?') {
				DBCONTACTENUMSETTINGS dbces;
				dbces.pfnEnumProc=SettingsEnumProc;
				lstrcpynA(szSection, szLine+2, min(sizeof(szSection), (int)(szEnd-szLine-1)));
				dbces.szModule=szSection;
				dbces.ofsSettings=0;
				CallService(MS_DB_CONTACT_ENUMSETTINGS, 0, (LPARAM)&dbces);
				while (setting_items) {
					SettingsList *next = setting_items->next;

					DBCONTACTGETSETTING dbcgs;
					dbcgs.szModule = szSection;
					dbcgs.szSetting = setting_items->name;
					CallService(MS_DB_CONTACT_DELETESETTING, 0, (LPARAM)&dbcgs);

					mir_free(setting_items->name);
					mir_free(setting_items);
					setting_items = next;
				}
			}
			continue;
		}

		if (szSection[0] == '\0')
			continue;

		char *szValue=strchr(szLine, '=');
		if (szValue == NULL)
			continue;

		char szName[128];
		lstrcpynA(szName, szLine, min(sizeof(szName), (int)(szValue-szLine+1)));
		szValue++;
		{
			warnSettingChangeInfo_t warnInfo;
			warnInfo.szIniPath=szIniPath;
			warnInfo.szName=szName;
			warnInfo.szSafeSections=szSafeSections;
			warnInfo.szSection=szSection;
			warnInfo.szUnsafeSections=szUnsafeSections;
			warnInfo.szValue=szValue;
			warnInfo.warnNoMore=0;
			warnInfo.cancel=0;
			if (warnThisSection && IDNO == DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_WARNINICHANGE), NULL, WarnIniChangeDlgProc, (LPARAM)&warnInfo))
				continue;
			if (warnInfo.cancel)
				break;
			if (warnInfo.warnNoMore)
				warnThisSection=0;
		}

		switch(szValue[0]) {
		case 'b':
		case 'B':
			DBWriteContactSettingByte(NULL, szSection, szName, (BYTE)strtol(szValue+1, NULL, 0));
			break;
		case 'w':
		case 'W':
			DBWriteContactSettingWord(NULL, szSection, szName, (WORD)strtol(szValue+1, NULL, 0));
			break;
		case 'd':
		case 'D':
			DBWriteContactSettingDword(NULL, szSection, szName, (DWORD)strtoul(szValue+1, NULL, 0));
			break;
		case 'l':
		case 'L':
			DBDeleteContactSetting(NULL, szSection, szName);
			break;
		case 'e':
		case 'E':
			ConvertBackslashes(szValue+1, LangPackGetDefaultCodePage());
		case 's':
		case 'S':
			DBWriteContactSettingString(NULL, szSection, szName, szValue+1);
			break;
		case 'g':
		case 'G':
			{	char *pstr;
				for (pstr=szValue+1;*pstr;pstr++) {
					if (*pstr == '\\') {
						switch(pstr[1]) {
						case 'n': *pstr='\n'; break;
						case 't': *pstr='\t'; break;
						case 'r': *pstr='\r'; break;
						default:  *pstr=pstr[1]; break;
						}
						MoveMemory(pstr+1, pstr+2, lstrlenA(pstr+2)+1);
			}	}	}
		case 'u':
		case 'U':
			DBWriteContactSettingStringUtf(NULL, szSection, szName, szValue+1);
			break;
		case 'n':
		case 'h':
		case 'N':
		case 'H':
			{	PBYTE buf;
				int len;
				char *pszValue, *pszEnd;
				DBCONTACTWRITESETTING cws;

				buf=(PBYTE)mir_alloc(lstrlenA(szValue+1));
				for (len=0, pszValue=szValue+1;;len++) {
					buf[len]=(BYTE)strtol(pszValue, &pszEnd, 0x10);
					if (pszValue == pszEnd) break;
					pszValue=pszEnd;
				}
				cws.szModule=szSection;
				cws.szSetting=szName;
				cws.value.type=DBVT_BLOB;
				cws.value.pbVal=buf;
				cws.value.cpbVal=len;
				CallService(MS_DB_CONTACT_WRITESETTING, (WPARAM)(HANDLE)NULL, (LPARAM)&cws);
				mir_free(buf);
			}
			break;
		default:
			MessageBox(NULL, TranslateT("Invalid setting type. The first character of every value must be b, w, d, l, s, e, u, g, h or n."), TranslateT("Install Database Settings"), MB_OK);
			break;
		}
	}
	fclose(fp);
}

static void DoAutoExec(void)
{
	TCHAR szUse[7], szIniPath[MAX_PATH], szFindPath[MAX_PATH];
	TCHAR *str2;
	TCHAR buf[2048], szSecurity[11], szOverrideSecurityFilename[MAX_PATH], szOnCreateFilename[MAX_PATH];
	char *szSafeSections, *szUnsafeSections;
	int secur;

	GetPrivateProfileString(_T("AutoExec"), _T("Use"), _T("prompt"), szUse, SIZEOF(szUse), mirandabootini);
	if ( !lstrcmpi(szUse, _T("no"))) return;
	GetPrivateProfileString(_T("AutoExec"), _T("Safe"), _T("CLC Icons CLUI CList SkinSounds"), buf, SIZEOF(buf), mirandabootini);
	szSafeSections = mir_t2a(buf);
	GetPrivateProfileString(_T("AutoExec"), _T("Unsafe"), _T("ICQ MSN"), buf, SIZEOF(buf), mirandabootini);
	szUnsafeSections = mir_t2a(buf);
	GetPrivateProfileString(_T("AutoExec"), _T("Warn"), _T("notsafe"), szSecurity, SIZEOF(szSecurity), mirandabootini);
	if ( !lstrcmpi(szSecurity, _T("none"))) secur = 0;
	else if ( !lstrcmpi(szSecurity, _T("notsafe"))) secur = 1;
	else if ( !lstrcmpi(szSecurity, _T("onlyunsafe"))) secur = 2;

	GetPrivateProfileString(_T("AutoExec"), _T("OverrideSecurityFilename"), _T(""), szOverrideSecurityFilename, SIZEOF(szOverrideSecurityFilename), mirandabootini);
	GetPrivateProfileString(_T("AutoExec"), _T("OnCreateFilename"), _T(""), szOnCreateFilename, SIZEOF(szOnCreateFilename), mirandabootini);
	GetPrivateProfileString(_T("AutoExec"), _T("Glob"), _T("autoexec_*.ini"), szFindPath, SIZEOF(szFindPath), mirandabootini);
    
	if (dbCreated && szOnCreateFilename[0]) {
		str2 = Utils_ReplaceVarsT(szOnCreateFilename);
		PathToAbsoluteT(str2, szIniPath, NULL);
		mir_free(str2);

		ProcessIniFile(szIniPath, szSafeSections, szUnsafeSections, 0, 1);
	}

	str2 = Utils_ReplaceVarsT(szFindPath);
	PathToAbsoluteT(str2, szFindPath, NULL);
	mir_free(str2);

	WIN32_FIND_DATA fd;
	HANDLE hFind = FindFirstFile(szFindPath, &fd);
	if (hFind == INVALID_HANDLE_VALUE) {
		mir_free(szSafeSections);
		mir_free(szUnsafeSections);
		return;
	}

	str2 = _tcsrchr(szFindPath, '\\');
	if (str2 == NULL) szFindPath[0] = 0;
	else str2[1] = 0;

	do {
		bool secFN = lstrcmpi(fd.cFileName, szOverrideSecurityFilename) == 0;

		mir_sntprintf(szIniPath, SIZEOF(szIniPath), _T("%s%s"), szFindPath, fd.cFileName);
		if ( !lstrcmpi(szUse, _T("prompt")) && !secFN) {
			int result=DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_INSTALLINI), NULL, InstallIniDlgProc, (LPARAM)szIniPath);
			if (result == IDC_NOTOALL) break;
			if (result == IDCANCEL) continue;
		}

		ProcessIniFile(szIniPath, szSafeSections, szUnsafeSections, secur, secFN);

		if (secFN)
			DeleteFile(szIniPath);
		else {
			TCHAR szOnCompletion[8];
			GetPrivateProfileString(_T("AutoExec"), _T("OnCompletion"), _T("recycle"), szOnCompletion, SIZEOF(szOnCompletion), mirandabootini);
			if ( !lstrcmpi(szOnCompletion, _T("delete")))
				DeleteFile(szIniPath);
			else if ( !lstrcmpi(szOnCompletion, _T("recycle"))) {
				SHFILEOPSTRUCT shfo={0};
				shfo.wFunc=FO_DELETE;
				shfo.pFrom=szIniPath;
				szIniPath[lstrlen(szIniPath)+1]=0;
				shfo.fFlags=FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOF_ALLOWUNDO;
				SHFileOperation(&shfo);
			}
			else if ( !lstrcmpi(szOnCompletion, _T("rename"))) {
				TCHAR szRenamePrefix[MAX_PATH];
				TCHAR szNewPath[MAX_PATH];
				GetPrivateProfileString(_T("AutoExec"), _T("RenamePrefix"), _T("done_"), szRenamePrefix, SIZEOF(szRenamePrefix), mirandabootini);
				lstrcpy(szNewPath, szFindPath);
				lstrcat(szNewPath, szRenamePrefix);
				lstrcat(szNewPath, fd.cFileName);
				MoveFile(szIniPath, szNewPath);
			}
			else if ( !lstrcmpi(szOnCompletion, _T("ask")))
				DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_INIIMPORTDONE), NULL, IniImportDoneDlgProc, (LPARAM)szIniPath);
		}
	} while (FindNextFile(hFind, &fd));
	FindClose(hFind);
	mir_free(szSafeSections);
	mir_free(szUnsafeSections);
}

static INT_PTR CheckIniImportNow(WPARAM, LPARAM)
{
	DoAutoExec();
	FindNextChangeNotification(hIniChangeNotification);
	return 0;
}

int InitIni(void)
{
	TCHAR szMirandaDir[MAX_PATH];

	bModuleInitialized = true;

	DoAutoExec();
	PathToAbsoluteT(_T("."), szMirandaDir, NULL);
	hIniChangeNotification=FindFirstChangeNotification(szMirandaDir, 0, FILE_NOTIFY_CHANGE_FILE_NAME);
	if (hIniChangeNotification != INVALID_HANDLE_VALUE) {
		CreateServiceFunction("DB/Ini/CheckImportNow", CheckIniImportNow);
		CallService(MS_SYSTEM_WAITONHANDLE, (WPARAM)hIniChangeNotification, (LPARAM)"DB/Ini/CheckImportNow");
	}
	return 0;
}

void UninitIni(void)
{
	if ( !bModuleInitialized) return;
	CallService(MS_SYSTEM_REMOVEWAIT, (WPARAM)hIniChangeNotification, 0);
	FindCloseChangeNotification(hIniChangeNotification);
}