// ZazouNICUpdate.cpp : définit le point d'entrée pour l'application DLL.
//

// TODO: Mettre les chaînes en ressources pour l'internationalisation

#include "stdafx.h"
#include <string>
#include <fstream>
#include "resource.h"
#include "ZazouNICUpdate.h"

// Le domaine sur lequel on travaille
#define NIC_DOMAIN "www.zmws.net"

// La base de l'uri où mettre à jour le nom de domaine
#define URI_BASE "/nic/update?hostname="

// La base de l'uri où récupérer l'IP
#define URI_IP "/nic/wsmyip"

// Le contexte
static struct _znucontext {
	char* hostname;
	char* login;
	char* password;
	char* serverMsg;
	std::string UIResult;
	HINSTANCE hInstance;
	unsigned long age;
	unsigned long ip; 
} gContext;

BOOL WriteConfigFileOnUnload;


/*************************/
/* Déclarations internes */
/*************************/

// Appelée lors du Chargement/Déchargement de la DLL
BOOL APIENTRY DllMain ( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved );

// Fonction utilitaire pour récupérer une chaîne
char* ZGetStringFromUrl (const char* domain, const char * url, const char* login, const char* password);

// Interprète la réponse du serveur
int ZParseServerResp (const char* serverResp);

// Fonction utilitaire pour récupérer une chaîne dans un contrôle
char* ZGetControlString (int ctlid);

// Fonction utilitaire pour affecter une chaîne à un contrôle
void ZSetControlString (HWND hDlg, int ctlid, char* buff);

int ZReadConfig ();
int ZWriteConfig ();

// Le callback pour la boîte de dialogue
LRESULT CALLBACK confDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);


/*******************/
/* Implémentations */
/*******************/

// Appelée lors du Chargement/Déchargement de la DLL
BOOL APIENTRY DllMain ( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) {
	switch (ul_reason_for_call) {
		case DLL_PROCESS_ATTACH:
			WriteConfigFileOnUnload = TRUE;
			memset (&gContext, 0, sizeof(gContext));
			gContext.hInstance = (HINSTANCE)hModule;
			ZReadConfig();
			break;

		case DLL_PROCESS_DETACH:
			if (WriteConfigFileOnUnload) {
				ZWriteConfig();
			}
			free (gContext.hostname);
			free (gContext.login);
			free (gContext.password);
			free (gContext.serverMsg);
			break;

		case DLL_THREAD_ATTACH:
		case DLL_THREAD_DETACH:
			break;
	}

    return TRUE;
}

// Informe la dll si elle doit ou non écrire la config lors du déchargement
void ZNICUpdateWriteConfigFileOnUnload(BOOL WriteIt) {
	WriteConfigFileOnUnload = WriteIt;
}

// Affiche la boîte de dialogue permettant la configuration
int ZNICUpdateOptionsDialog(void) {
	INT_PTR ret;

	ret = DialogBox (gContext.hInstance, (LPCTSTR)IDD_CONFIG_DLG, NULL, (DLGPROC)confDlgProc);
	if (ret) {
		return RETCODE_NOT_CONFIGURED;
	}
	return RETCODE_SUCCESS;
}

// Programme ZNICUpdateNoUI pour une exécution toutes les 5 minutes
int ZNICUpdateAuto(void) {
	// Porc inside (tm)
	// ToDo : UI Spécifique pour Status et Start/Stop
	static HANDLE thread_HANDLE = INVALID_HANDLE_VALUE;
	if (thread_HANDLE == INVALID_HANDLE_VALUE) {
		// Crée le thread s'il ne tourne pas
		DWORD tid;
		thread_HANDLE = (HANDLE)CreateThread(NULL, 0, ZNICUpdateAutoThread, NULL, 0, &tid);
	} else {
		// Supprime le thread s'il tourne
		// Remplacer ça par un signalement quelconque
		TerminateThread(thread_HANDLE, 0);
		CloseHandle (thread_HANDLE);
		thread_HANDLE = INVALID_HANDLE_VALUE;
	}
	return RETCODE_SUCCESS;
}

DWORD WINAPI ZNICUpdateAutoThread(LPVOID dummy) {
	int ret;
	unsigned long ip;
	// Porc inside (tm)
	// Remplacer ça par un signalement quelconque
	while (1) {
		ip = ZNICUpdateGetCurrentIP();
		if (ip != gContext.ip) {
			ret = ZNICUpdateNoUI();
			if (ret == RETCODE_NOT_CONFIGURED) {
				MessageBox (NULL, "ZazouNICUpdate non configuré", "ZazouNICUpdate", MB_ICONERROR);
				ret = ZNICUpdateOptionsDialog ();
				if (ret != RETCODE_SUCCESS) {
					return ret;
				}
				ZNICUpdateNoUI();
			}
		}
		Sleep(300000);
	}
	return 0;
}

// Fait la mise à jour d'un sous domaine de zmws.net
int ZNICUpdate(void) {
	INT_PTR ret;
	int retcode = RETCODE_UNKNOWN;
	char* uri = NULL;
	char* servResp = NULL;

	if (!( gContext.hostname && gContext.login && gContext.password )) {
		MessageBox (NULL, "ZazouNICUpdate non configuré", "ZazouNICUpdate", MB_ICONERROR);
		ret = ZNICUpdateOptionsDialog ();
		if (ret != RETCODE_SUCCESS) {
			return retcode;
		}
	}

	if (!( gContext.hostname && gContext.login && gContext.password )) {
		return retcode;
	}

	uri = (char*)malloc(strlen(URI_BASE)+strlen(gContext.hostname)+1);
	if (!uri) {
		return RETCODE_MEMFULL_ERR;
	}

	strcpy (uri, URI_BASE);
	strcat (uri, gContext.hostname);

	servResp = ZGetStringFromUrl (NIC_DOMAIN, uri, gContext.login, gContext.password);
	if (servResp) {
		retcode = ZParseServerResp (servResp);
		free (servResp);
	}

	switch (retcode) {
		case RETCODE_UNKNOWN:
			MessageBox (NULL, "Erreur inconnue ...", "ZazouNICUpdate", MB_ICONERROR);
			break;
		case RETCODE_MEMFULL_ERR:
			MessageBox (NULL, "Mémoire insuffisante ...", "ZazouNICUpdate", MB_ICONERROR);
			break;
		default:
			MessageBox (NULL,
				gContext.UIResult.c_str(),
				"ZazouNICUpdate",
				(retcode == RETCODE_SUCCESS) ? MB_ICONINFORMATION : MB_ICONERROR);
			break;
	}

	return retcode;
}

// Fait la mise à jour d'un sous domaine de zmws.net sans interface
int ZNICUpdateNoUI(void) {
	int retcode = RETCODE_UNKNOWN;
	char* uri = NULL;
	char* servResp = NULL;

	if (!( gContext.hostname && gContext.login && gContext.password )) {
		return RETCODE_NOT_CONFIGURED;
	}

	uri = (char*)malloc(strlen(URI_BASE)+strlen(gContext.hostname)+1);
	if (!uri) {
		return RETCODE_MEMFULL_ERR;
	}

	strcpy (uri, URI_BASE);
	strcat (uri, gContext.hostname);

	servResp = ZGetStringFromUrl (NIC_DOMAIN, uri, gContext.login, gContext.password);
	if (servResp) {
		retcode = ZParseServerResp (servResp);
		free (servResp);
	}

	return retcode;
}

// Récupère l'IP externe connue
unsigned long ZNICUpdateGetCurrentIP (void) {
	INT_PTR ret;
	char* uri = NULL;
	char* servResp = NULL;
	unsigned long ip = 0;

	if (!( gContext.hostname && gContext.login && gContext.password )) {
		MessageBox (NULL, "ZazouNICUpdate non configuré", "ZazouNICUpdate", MB_ICONERROR);
		ret = DialogBox (gContext.hInstance, (LPCTSTR)IDD_CONFIG_DLG, NULL, (DLGPROC)confDlgProc);
		if (ret) {
			return ip;
		}
	}

	if (!( gContext.hostname && gContext.login && gContext.password )) {
		return ip;
	}

	uri = (char*)malloc(strlen(URI_IP)+1);
	if (!uri) {
		return ip;
	}

	strcpy (uri, URI_IP);

	servResp = ZGetStringFromUrl (NIC_DOMAIN, uri, gContext.login, gContext.password);
	if (servResp) {
		ip = inet_addr (servResp);
		free (servResp);
	}

	return ip;
}

// Récupère l'IP externe connue
unsigned long ZNICUpdateGetLastIP (void) {
	return (gContext.ip);
}

// Récupère l'heure de dernière mise à jour sur serveur
unsigned long ZNICUpdateGetLastIPAge (void) {
	// si age == 0 renvoie la valeur de time (NULL)
	return (unsigned long)(time(NULL) - gContext.age);
}

const char* ZNICUpdateGetServerMessage (void) {
	if (gContext.serverMsg)
		return (gContext.serverMsg);
	else
		return ("");
}


// Récupère le sous domaine actuel
const char* ZNICUpdateGetConfiguredDomain (void) {
	if (gContext.hostname)
		return (gContext.hostname);
	else
		return ("");
}

// Récupère le login actuel
const char* ZNICUpdateGetConfiguredLogin (void) {
	if (gContext.login)
		return (gContext.login);
	else
		return ("");
}


// Récupère le mot de passe actuel
const char* ZNICUpdateGetConfiguredPassword (void) {
	if (gContext.password)
		return (gContext.password);
	else
		return ("");
}

// Configure ZazouNICUpdate
// Retourne -1 en cas d'erreur, 0 sinon
int ZNICUpdateConfigure(const char* domain, const char* login, const char* passwd) {
	int ret = 0;
	if (domain) {
		if (gContext.hostname) {
			free (gContext.hostname);
			gContext.hostname = NULL;
		}
		gContext.hostname = (char*)malloc (strlen(domain)+1);
		if (gContext.hostname) {
			strcpy (gContext.hostname, domain);
		} else {
			ret = -1;
		}
	}

	if (login) {
		if (gContext.login) {
			free (gContext.login);
			gContext.login = NULL;
		}
		gContext.login = (char*)malloc (strlen(login)+1);
		if (gContext.login) {
			strcpy (gContext.login, login);
		} else {
			ret = -1;
		}
	}

	if (passwd) {
		if (gContext.password) {
			free (gContext.password);
			gContext.password = NULL;
		}
		gContext.password = (char*)malloc (strlen(passwd)+1);
		if (gContext.password) {
			strcpy (gContext.password, passwd);
		} else {
			ret = -1;
		}
	}
	return ret;
}



// Fonction utilitaire pour récupérer une chaîne
char* ZGetStringFromUrl(const char* domain, const char* uri, const char* login, const char* password) {
	char* buff = NULL;
	
		// Initialisation
	HINTERNET hInternet = InternetOpen(
		"ZazouNicUpdate/1.1",
		INTERNET_OPEN_TYPE_PRECONFIG,
		NULL,
		NULL,
		0);

	if (!hInternet) return buff;

	// Initialisation session
	HINTERNET hSession = InternetConnect(
		hInternet,
		domain,
		INTERNET_DEFAULT_HTTP_PORT,
		login,
		password,
		INTERNET_SERVICE_HTTP,
		0,
		NULL);

	if (!hSession) {
		InternetCloseHandle (hInternet);
		return buff;
	}

	// 
	HINTERNET hRequest = HttpOpenRequest(
		hSession,
		"GET",
		uri,
		NULL,
		"ZazouNicUpdate Plugin v1.1",
		NULL,
		0,
		NULL
	);

	if (!hSession) {
		InternetCloseHandle (hSession);
		InternetCloseHandle (hInternet);
		return buff;
	}

	BOOL reqRC = HttpSendRequest(
		hRequest,
		NULL,
		0,
		NULL,
		0
	);

	if (reqRC) {
		DWORD dwSize;
		if (InternetQueryDataAvailable(hRequest,&dwSize,0,0)) {
			buff = (char*)malloc (dwSize+1);
			if (buff) {
				DWORD bytesRead;
				memset (buff, 0, dwSize+1);
				InternetReadFile (
					hRequest,
					buff,
					dwSize,
					&bytesRead
					);
				if (bytesRead != dwSize) {
					free (buff);
					buff = NULL;
				}
			}
		}
	}

	// Cleanup
	InternetCloseHandle (hRequest);
	InternetCloseHandle (hSession);
	InternetCloseHandle (hInternet);

	return buff;
}

// Interprète la réponse du serveur
int ZParseServerResp (const char* serverResp) {
	int retcode = RETCODE_UNKNOWN;

	if (gContext.serverMsg) free (gContext.serverMsg);
	gContext.serverMsg = (char *)malloc (strlen(serverResp)+1);
	if (!gContext.serverMsg) return RETCODE_MEMFULL_ERR;
	strcpy(gContext.serverMsg, serverResp);

	if (!strcmp(serverResp, "badauth")) {
		gContext.UIResult = "Erreur d'authentication.\nVérifiez votre login et votre mot de passe.\n";
		return RETCODE_BADAUTH;
	}

	if (!strcmp(serverResp, "badagent")) {
		gContext.UIResult = "Un problème technique (badagent) empêche le fonctionnement de ce plugin.\n";
		return RETCODE_BADAGENT;
	}

	if (!strcmp(serverResp, "!donator")) {
		gContext.UIResult = "Cette fonctionnalité est réservée aux donateurs.\n";
		return RETCODE_NOT_DONATOR;
	}

	if (!strcmp(serverResp, "notfqdn")) {
		gContext.UIResult = "Le nom de domaine semble incorrect.\n";
		return RETCODE_NOT_FQDN;
	}

	if (!strcmp(serverResp, "nohost")) {
		gContext.UIResult = "Le nom de domaine n'a pas été transmis.\n";
		return RETCODE_NO_HOST;
	}

	if (!strcmp(serverResp, "!yours")) {
		gContext.UIResult = "Ce domaine ne semble pas être à vous.\n";
		return RETCODE_NOT_YOURS;
	}

	if (!strcmp(serverResp, "numhost")) {
		gContext.UIResult = "Trop de domaines à mettre à jour.\n";
		return RETCODE_NUMHOST;
	}

	if (!strcmp(serverResp, "abuse")) {
		gContext.UIResult = "Bloqué pour utilisation abusive.\n";
		return RETCODE_ABUSE;
	}

	if (!strcmp(serverResp, "dnserr")) {
		gContext.UIResult = "Erreur de DNS.\n";
		return RETCODE_DNSERR;
	}

	if (!strcmp(serverResp, "911")) {
		gContext.UIResult = "Erreur interne du serveur.\n";
		return RETCODE_911;
	}

	if (!strncmp(serverResp, "nochg ", 6) && strlen(serverResp)>6) {
		gContext.ip = inet_addr(serverResp+6);
		gContext.age = (unsigned long)time (NULL);
		gContext.UIResult  = "Pas de changement pour ce domaine.\nIP: ";
		gContext.UIResult += (char*)(serverResp+6);
		gContext.UIResult += "\n";
		return RETCODE_SUCCESS;
	}

	if (!strncmp(serverResp, "good ", 5) && strlen(serverResp)>5) {
		gContext.ip = inet_addr(serverResp+5);
		gContext.age = (unsigned long)time (NULL);
		gContext.UIResult  = "Le domaine a été correctement mis à jour.\nIP: ";
		gContext.UIResult += (char*)(serverResp+5);
		gContext.UIResult += "\n";
		return RETCODE_SUCCESS;
	}

	return retcode;
}

// Fonction utilitaire pour récupérer une chaîne dans un contrôle
char* ZGetControlString (HWND hDlg, int ctlid) {
    HWND ctl;
	int txtlen;
	char* buff = NULL;

	ctl = GetDlgItem (hDlg, ctlid);
	if (ctl) {
		txtlen = GetWindowTextLength(ctl);
		buff = (char*)malloc (txtlen+1);
		if (buff) {
			memset (buff, 0, txtlen+1);
			GetWindowText (ctl, buff, txtlen+1);
		}
	}
	return buff;
}

// Fonction utilitaire pour affecter une chaîne à un contrôle
void ZSetControlString(HWND hDlg, int ctlid, char* buff) {
	HWND ctl;

	if (buff) {
		ctl = GetDlgItem (hDlg, ctlid);
		if (ctl) {
			SetWindowText (ctl, buff);
		}
	}
}

int ZReadConfig () {
	// On cherche le fichier de config dans le répertoire _plugins.zmwsc
	std::ifstream ifs("_plugins.zmwsc/ZazouNICUpdate.zpc");
	if (!ifs) {
		ifs.clear();
		// S'il n'y est pas on tente le répertoire courant
		ifs.open ("ZazouNICUpdate.zpc");
		if (!ifs.is_open()) {
			return 0;
		}
	}

	// On lit login, pass, domaine
	// A raison d'une entrée par ligne
	char tmp_char;
	int nlines = 0;
	std::string login;
	std::string password;
	std::string domain;
	while ((tmp_char = ifs.get()) != -1) {
		if ((tmp_char != '\r') && (tmp_char != '\n')) {
			if (tmp_char != ' ') {
				switch (nlines) {
					case 0:
						login += tmp_char;
						break;
					case 1:
						password += tmp_char;
						break;
					case 2:
						domain += tmp_char;
						break;
				}
			}
		} else {
			++nlines;
		}
	}
	ifs.close();

	if (login.length() && password.length() && domain.length()) {
		// Si on a login, pass et domaine, on configure le plugin.
		ZNICUpdateConfigure(domain.c_str(), login.c_str(), password.c_str());
	}

	return 0;
}

int ZWriteConfig () {
	if (!(gContext.login && gContext.password && gContext.hostname))
		return 0;

	std::ofstream ofs("_plugins.zmwsc/ZazouNICUpdate.zpc");
	if (!ofs) {
		ofs.clear();
		// S'il n'y est pas on tente le répertoire courant
		ofs.open ("ZazouNICUpdate.zpc");
		if (!ofs.is_open()) {
			return 0;
		}
	}

	ofs << gContext.login << "\n";
	ofs << gContext.password << "\n";
	ofs << gContext.hostname << "\n";
	ofs.close();
	return 0;
}

// Le callback pour la boîte de dialogue
LRESULT CALLBACK confDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
	int wmId, wmEvent;

	switch (message) {
		case WM_INITDIALOG:
					if ( gContext.hostname )
						ZSetControlString (hDlg, IDC_DOMAINE, gContext.hostname);
					if ( gContext.login )
						ZSetControlString (hDlg, IDC_LOGIN, gContext.login);
					if ( gContext.password )
						ZSetControlString (hDlg, IDC_PASSWORD, gContext.password);
			break;
		case WM_COMMAND: {
			wmEvent = HIWORD(wParam); 
			wmId    = LOWORD(wParam); 
			switch (wmId) {
				case IDOK: {
					// Nettoie le contexte ...
					if ( gContext.hostname )
						free (gContext.hostname);
					if ( gContext.login )
						free (gContext.login);
					if ( gContext.password )
						free (gContext.password);

					// Récupère le contenu des champs texte ...
					gContext.hostname = ZGetControlString (hDlg, IDC_DOMAINE);
					gContext.login = ZGetControlString (hDlg, IDC_LOGIN);
					gContext.password = ZGetControlString (hDlg, IDC_PASSWORD);

					// Ferme le dialogue
					EndDialog(hDlg, 0);
				}
				break;
				case IDCANCEL:
					EndDialog(hDlg, 1);
					break;
				default:
					return DefWindowProc(hDlg, message, wParam, lParam);
			}
		}
	}
	return FALSE;
}