////////////////////////////////////////////////////////////////////////////////
// Gadu-Gadu Plugin for Miranda IM
//
// Copyright (c) 2009-2011 Bartosz Białek
//
// 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 "gg.h"
#include <io.h>
#include <fcntl.h>
#include "protocol.h"

//////////////////////////////////////////////////////////
// Avatars support
void gg_getavatarfilename(GGPROTO *gg, HANDLE hContact, char *pszDest, int cbLen)
{
	int tPathLen;
	char *path = (char *)alloca(cbLen);
	char *avatartype = NULL;

	if (gg->hAvatarsFolder == NULL || FoldersGetCustomPath(gg->hAvatarsFolder, path, cbLen, "")) {
		char *tmpPath = Utils_ReplaceVars("%miranda_avatarcache%");
		tPathLen = mir_snprintf(pszDest, cbLen, "%s\\%s", tmpPath, GG_PROTO);
		mir_free(tmpPath);
	}
	else {
		strcpy(pszDest, path);
		tPathLen = (int)strlen(pszDest);
	}

	if (_access(pszDest, 0))
		CallService(MS_UTILS_CREATEDIRTREE, 0, (LPARAM)pszDest);

	switch (DBGetContactSettingByte(hContact, GG_PROTO, GG_KEY_AVATARTYPE, GG_KEYDEF_AVATARTYPE)) {
		case PA_FORMAT_JPEG: avatartype = "jpg"; break;
		case PA_FORMAT_GIF: avatartype = "gif"; break;
		case PA_FORMAT_PNG: avatartype = "png"; break;
	}

	if (hContact != NULL) {
		DBVARIANT dbv;
		if (!DBGetContactSettingString(hContact, GG_PROTO, GG_KEY_AVATARHASH, &dbv)) {
			mir_snprintf(pszDest + tPathLen, cbLen - tPathLen, "\\%s.%s", dbv.pszVal, avatartype);
			DBFreeVariant(&dbv);
		}
	}
	else
		mir_snprintf(pszDest + tPathLen, cbLen - tPathLen, "\\%s avatar.%s", GG_PROTO, avatartype);
}

void gg_getavatarfileinfo(GGPROTO *gg, uin_t uin, char **avatarurl, int *type)
{
	NETLIBHTTPREQUEST req = {0};
	NETLIBHTTPREQUEST *resp;
	char szUrl[128];
	*avatarurl = NULL;
	*type = PA_FORMAT_UNKNOWN;

	req.cbSize = sizeof(req);
	req.requestType = REQUEST_GET;
	req.szUrl = szUrl;
	mir_snprintf(szUrl, 128, "http://api.gadu-gadu.pl/avatars/%d/0.xml", uin);
	req.flags = NLHRF_NODUMP | NLHRF_HTTP11 | NLHRF_REDIRECT;
	resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)gg->netlib, (LPARAM)&req);
	if (resp) {
		if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) {
			HXML hXml;
			TCHAR *xmlAction;
			TCHAR *tag;

			xmlAction = gg_a2t(resp->pData);
			tag = gg_a2t("result");
			hXml = xi.parseString(xmlAction, 0, tag);

			if (hXml != NULL) {
				HXML node;
				char *blank;

				mir_free(tag); tag = gg_a2t("users/user/avatars/avatar");
				node = xi.getChildByPath(hXml, tag, 0);
				mir_free(tag); tag = gg_a2t("blank");
				blank = node != NULL ? gg_t2a(xi.getAttrValue(node, tag)) : NULL;

				if (blank != NULL && strcmp(blank, "1")) {
					mir_free(tag); tag = gg_a2t("users/user/avatars/avatar/bigAvatar");
					node = xi.getChildByPath(hXml, tag, 0);
					*avatarurl = node != NULL ? gg_t2a(xi.getText(node)) : NULL;

					mir_free(tag); tag = gg_a2t("users/user/avatars/avatar/originBigAvatar");
					node = xi.getChildByPath(hXml, tag, 0);
					if (node != NULL) {
						char *orgavurl = gg_t2a(xi.getText(node));
						char *avtype = strrchr(orgavurl, '.');
						avtype++;
						if (!_stricmp(avtype, "jpg"))
							*type = PA_FORMAT_JPEG;
						else if (!_stricmp(avtype, "gif"))
							*type = PA_FORMAT_GIF;
						else if (!_stricmp(avtype, "png"))
							*type = PA_FORMAT_PNG;
						mir_free(orgavurl);
					}
				}
				else *avatarurl = mir_strdup("");
				mir_free(blank);
				xi.destroyNode(hXml);
			}
			mir_free(tag);
			mir_free(xmlAction);
		}
		else gg_netlog(gg, "gg_getavatarfileinfo(): Invalid response code from HTTP request");
		CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp);
	}
	else gg_netlog(gg, "gg_getavatarfileinfo(): No response from HTTP request");
}

char *gg_avatarhash(char *param)
{
	mir_sha1_byte_t digest[MIR_SHA1_HASH_SIZE];
	char *result;
	int i;

	if (param == NULL || (result = (char *)mir_alloc(MIR_SHA1_HASH_SIZE * 2 + 1)) == NULL)
		return NULL;

	mir_sha1_hash(param, (int)strlen(param), digest);
	for (i = 0; i < MIR_SHA1_HASH_SIZE; i++)
		sprintf(result + (i<<1), "%02x", digest[i]);

	return result;
}

typedef struct
{
	HANDLE hContact;
	char *AvatarURL;
} GGGETAVATARDATA;

void gg_getavatar(GGPROTO *gg, HANDLE hContact, char *szAvatarURL)
{
	if (gg->pth_avatar.dwThreadId) {
		GGGETAVATARDATA *data = mir_alloc(sizeof(GGGETAVATARDATA));
		data->hContact = hContact;
		data->AvatarURL = mir_strdup(szAvatarURL);
		EnterCriticalSection(&gg->avatar_mutex);
		list_add(&gg->avatar_transfers, data, 0);
		LeaveCriticalSection(&gg->avatar_mutex);
	}
}

typedef struct
{
	HANDLE hContact;
	int iWaitFor;
} GGREQUESTAVATARDATA;

void gg_requestavatar(GGPROTO *gg, HANDLE hContact, int iWaitFor)
{
	if (DBGetContactSettingByte(NULL, GG_PROTO, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS)
		&& gg->pth_avatar.dwThreadId) {
		GGREQUESTAVATARDATA *data = mir_alloc(sizeof(GGREQUESTAVATARDATA));
		data->hContact = hContact;
		data->iWaitFor = iWaitFor;
		EnterCriticalSection(&gg->avatar_mutex);
		list_add(&gg->avatar_requests, data, 0);
		LeaveCriticalSection(&gg->avatar_mutex);
	}
}

void __cdecl gg_avatarrequestthread(GGPROTO *gg, void *empty)
{
	list_t l;

	gg_netlog(gg, "gg_avatarrequestthread(): Avatar Request Thread Starting");
	while (gg->pth_avatar.dwThreadId)
	{
		EnterCriticalSection(&gg->avatar_mutex);
		if (gg->avatar_requests) {
			GGREQUESTAVATARDATA *data = (GGREQUESTAVATARDATA *)gg->avatar_requests->data;
			char *AvatarURL;
			int AvatarType, iWaitFor = data->iWaitFor;
			HANDLE hContact = data->hContact;

			list_remove(&gg->avatar_requests, data, 0);
			mir_free(data);
			LeaveCriticalSection(&gg->avatar_mutex);

			gg_getavatarfileinfo(gg, DBGetContactSettingDword(hContact, GG_PROTO, GG_KEY_UIN, 0), &AvatarURL, &AvatarType);
			if (AvatarURL != NULL && strlen(AvatarURL) > 0)
				DBWriteContactSettingString(hContact, GG_PROTO, GG_KEY_AVATARURL, AvatarURL);
			else
				DBDeleteContactSetting(hContact, GG_PROTO, GG_KEY_AVATARURL);
			DBWriteContactSettingByte(hContact, GG_PROTO, GG_KEY_AVATARTYPE, (BYTE)AvatarType);
			DBWriteContactSettingByte(hContact, GG_PROTO, GG_KEY_AVATARREQUESTED, 1);

			if (iWaitFor) {
				PROTO_AVATAR_INFORMATION pai = {0};
				pai.cbSize = sizeof(pai);
				pai.hContact = hContact;
				if (gg_getavatarinfo(gg, (WPARAM)GAIF_FORCE, (LPARAM)&pai) != GAIR_WAITFOR)
					ProtoBroadcastAck(GG_PROTO, hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, (HANDLE)&pai, 0);
			}
			else ProtoBroadcastAck(GG_PROTO, hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, 0, 0);
		}
		else LeaveCriticalSection(&gg->avatar_mutex);

		EnterCriticalSection(&gg->avatar_mutex);
		if (gg->avatar_transfers) {
			GGGETAVATARDATA *data = (GGGETAVATARDATA *)gg->avatar_transfers->data;
			NETLIBHTTPREQUEST req = {0};
			NETLIBHTTPREQUEST *resp;
			PROTO_AVATAR_INFORMATION pai = {0};
			int result = 0;

			pai.cbSize = sizeof(pai);
			pai.hContact = data->hContact;
			pai.format = DBGetContactSettingByte(pai.hContact, GG_PROTO, GG_KEY_AVATARTYPE, GG_KEYDEF_AVATARTYPE);

			req.cbSize = sizeof(req);
			req.requestType = REQUEST_GET;
			req.szUrl = data->AvatarURL;
			req.flags = NLHRF_NODUMP | NLHRF_HTTP11 | NLHRF_REDIRECT;
			resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)gg->netlib, (LPARAM)&req);
			if (resp) {
				if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) {
					int file_fd;

					gg_getavatarfilename(gg, pai.hContact, pai.filename, sizeof(pai.filename));
					file_fd = _open(pai.filename, _O_WRONLY | _O_TRUNC | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE);
					if (file_fd != -1) {
						_write(file_fd, resp->pData, resp->dataLength);
						_close(file_fd);
						result = 1;
					}
				}
				else gg_netlog(gg, "gg_avatarrequestthread(): Invalid response code from HTTP request");
				CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp);
			}
			else gg_netlog(gg, "gg_avatarrequestthread(): No response from HTTP request");

			ProtoBroadcastAck(GG_PROTO, pai.hContact, ACKTYPE_AVATAR,
				result ? ACKRESULT_SUCCESS : ACKRESULT_FAILED, (HANDLE)&pai, 0);

			if (!pai.hContact)
				CallService(MS_AV_REPORTMYAVATARCHANGED, (WPARAM)GG_PROTO, 0);

			list_remove(&gg->avatar_transfers, data, 0);
			mir_free(data->AvatarURL);
			mir_free(data);
		}
		LeaveCriticalSection(&gg->avatar_mutex);
		SleepEx(100, FALSE);
	}

	for (l = gg->avatar_requests; l; l = l->next) {
		GGREQUESTAVATARDATA *data = (GGREQUESTAVATARDATA *)l->data;
		mir_free(data);
	}
	for (l = gg->avatar_transfers; l; l = l->next) {
		GGGETAVATARDATA *data = (GGGETAVATARDATA *)l->data;
		mir_free(data->AvatarURL);
		mir_free(data);
	}
	list_destroy(gg->avatar_requests, 0);
	list_destroy(gg->avatar_transfers, 0);
	gg_netlog(gg, "gg_avatarrequestthread(): Avatar Request Thread Ending");
}

void gg_initavatarrequestthread(GGPROTO *gg)
{
	DWORD exitCode = 0;

	GetExitCodeThread(gg->pth_avatar.hThread, &exitCode);
	if (exitCode != STILL_ACTIVE) {
		gg->avatar_requests = gg->avatar_transfers = NULL;
		gg->pth_avatar.hThread = gg_forkthreadex(gg, gg_avatarrequestthread, NULL, &gg->pth_avatar.dwThreadId);
	}
}

void gg_uninitavatarrequestthread(GGPROTO *gg)
{
	gg->pth_avatar.dwThreadId = 0;
#ifdef DEBUGMODE
	gg_netlog(gg, "gg_uninitavatarrequestthread(): Waiting until Avatar Request Thread finished, if needed.");
#endif
	gg_threadwait(gg, &gg->pth_avatar);
}

void __cdecl gg_getuseravatarthread(GGPROTO *gg, void *empty)
{
	PROTO_AVATAR_INFORMATION pai = {0};
	char *AvatarURL;
	int AvatarType;

	gg_getavatarfileinfo(gg, DBGetContactSettingDword(NULL, GG_PROTO, GG_KEY_UIN, 0), &AvatarURL, &AvatarType);
	if (AvatarURL != NULL && strlen(AvatarURL) > 0)
		DBWriteContactSettingString(NULL, GG_PROTO, GG_KEY_AVATARURL, AvatarURL);
	else
		DBDeleteContactSetting(NULL, GG_PROTO, GG_KEY_AVATARURL);
	DBWriteContactSettingByte(NULL, GG_PROTO, GG_KEY_AVATARTYPE, (BYTE)AvatarType);
	DBWriteContactSettingByte(NULL, GG_PROTO, GG_KEY_AVATARREQUESTED, 1);

	pai.cbSize = sizeof(pai);
	gg_getavatarinfo(gg, (WPARAM)GAIF_FORCE, (LPARAM)&pai);
}

void gg_getuseravatar(GGPROTO *gg)
{
	if (DBGetContactSettingByte(NULL, GG_PROTO, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS)
		&& DBGetContactSettingDword(NULL, GG_PROTO, GG_KEY_UIN, 0))
		gg_forkthread(gg, gg_getuseravatarthread, NULL);
}

void __cdecl gg_setavatarthread(GGPROTO *gg, void *param)
{
	NETLIBHTTPHEADER httpHeaders[4];
	NETLIBHTTPREQUEST req = {0};
	NETLIBHTTPREQUEST *resp;
	char *szFilename = (char *)param;
	const char *contentend = "\r\n--AaB03x--\r\n";
	char szUrl[128], uin[32], *authHeader, *data, *avatardata, content[256],
		*fileext, image_ext[4], image_type[11];
	int file_fd, avatardatalen, datalen, contentlen, contentendlen, res = 0, repeat = 0;

	gg_netlog(gg, "gg_setavatar(): Trying to set user avatar using %s...", szFilename);
	UIN2ID(DBGetContactSettingDword(NULL, GG_PROTO, GG_KEY_UIN, 0), uin);

	file_fd = _open(szFilename, _O_RDONLY | _O_BINARY, _S_IREAD);
	if (file_fd == -1) {
		gg_netlog(gg, "gg_setavatar(): Failed to open avatar file (%s).", strerror(errno));
		mir_free(szFilename);
		gg_getuseravatar(gg);
		return;
	}
	avatardatalen = _filelength(file_fd);
	avatardata = (char *)mir_alloc(avatardatalen);

	_read(file_fd, avatardata, avatardatalen);
	_close(file_fd);

	fileext = strrchr(szFilename, '.');
	fileext++;
	if (!_stricmp(fileext, "jpg")) {
		strcpy(image_ext, "jpg");
		strcpy(image_type, "image/jpeg");
	}
	else if (!_stricmp(fileext, "gif")) {
		strcpy(image_ext, "gif");
		strcpy(image_type, "image/gif");
	}
	else /*if (!_stricmp(fileext, "png"))*/ {
		strcpy(image_ext, "png");
		strcpy(image_type, "image/png");
	}

	mir_snprintf(content, 256, "--AaB03x\r\nContent-Disposition: form-data; name=\"_method\"\r\n\r\nPUT\r\n--AaB03x\r\nContent-Disposition: form-data; name=\"avatar\"; filename=\"%s.%s\"\r\nContent-Type: %s\r\n\r\n",
		uin, image_ext, image_type);
	contentlen = (int)strlen(content);
	contentendlen = (int)strlen(contentend);

	datalen = contentlen + avatardatalen + contentendlen;
	data = (char *)mir_alloc(datalen);
	memcpy(data, content, contentlen);
	memcpy(data + contentlen, avatardata, avatardatalen);
	memcpy(data + contentlen + avatardatalen, contentend, contentendlen);

	mir_snprintf(szUrl, 128, "http://api.gadu-gadu.pl/avatars/%s/0.xml", uin);
	gg_oauth_checktoken(gg, 0);
	authHeader = gg_oauth_header(gg, "PUT", szUrl);

	req.cbSize = sizeof(req);
	req.requestType = REQUEST_POST;
	req.szUrl = szUrl;
	req.flags = NLHRF_NODUMP | NLHRF_HTTP11;
	req.headersCount = 4;
	req.headers = httpHeaders;
	httpHeaders[0].szName   = "User-Agent";
	httpHeaders[0].szValue = GG8_VERSION;
	httpHeaders[1].szName  = "Authorization";
	httpHeaders[1].szValue = authHeader;
	httpHeaders[2].szName  = "Accept";
	httpHeaders[2].szValue = "*/*";
	httpHeaders[3].szName  = "Content-Type";
	httpHeaders[3].szValue = "multipart/form-data; boundary=AaB03x";
	req.pData = data;
	req.dataLength = datalen;

	resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)gg->netlib, (LPARAM)&req);
	if (resp) {
		if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) {
#ifdef DEBUGMODE
			gg_netlog(gg, "%s", resp->pData);
#endif
			res = 1;
		}
		else gg_netlog(gg, "gg_setavatar(): Invalid response code from HTTP request");
		if (resp->resultCode == 403 || resp->resultCode == 401)
			repeat = 1;
		CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp);
	}
	else gg_netlog(gg, "gg_setavatar(): No response from HTTP request");

	if (repeat) { // Access Token expired - we need to obtain new
		mir_free(authHeader);
		gg_oauth_checktoken(gg, 1);
		authHeader = gg_oauth_header(gg, "PUT", szUrl);

		ZeroMemory(&req, sizeof(req));
		req.cbSize = sizeof(req);
		req.requestType = REQUEST_POST;
		req.szUrl = szUrl;
		req.flags = NLHRF_NODUMP | NLHRF_HTTP11;
		req.headersCount = 4;
		req.headers = httpHeaders;
		req.pData = data;
		req.dataLength = datalen;

		resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)gg->netlib, (LPARAM)&req);
		if (resp) {
			if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) {
#ifdef DEBUGMODE
				gg_netlog(gg, "%s", resp->pData);
#endif
				res = 1;
			}
			else gg_netlog(gg, "gg_setavatar(): Invalid response code from HTTP request");
			CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp);
		}
		else gg_netlog(gg, "gg_setavatar(): No response from HTTP request");
	}

	mir_free(authHeader);
	mir_free(avatardata);
	mir_free(data);

	if (res)
		gg_netlog(gg, "gg_setavatar(): User avatar set successfully.");
	else
		gg_netlog(gg, "gg_setavatar(): Failed to set user avatar.");

	mir_free(szFilename);
	gg_getuseravatar(gg);
}

void gg_setavatar(GGPROTO *gg, const char *szFilename)
{
	gg_forkthread(gg, gg_setavatarthread, (void*)mir_strdup(szFilename));
}
