// -----------------------------------------------------------------------------
// ICQ plugin for Miranda NG
// -----------------------------------------------------------------------------
// Copyright � 2018-19 Miranda NG team
// 
// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------------

#include "stdafx.h"

void CIcqProto::InitContactCache()
{
	mir_cslock l(m_csCache);
	for (auto &it : AccContacts()) {
		if (isChatRoom(it))
			continue;

		DWORD dwUin = getDword(it, "UIN", -1);
		if (dwUin != -1) {
			delSetting(it, "UIN");

			wchar_t buf[100];
			_itow(dwUin, buf, 10);
			setWString(it, DB_KEY_ID, buf);
		}
		else {
			CMStringW wszEmail(getMStringW(it, "e-mail"));
			if (!wszEmail.IsEmpty()) {
				delSetting(it, "e-mail");
				setWString(it, DB_KEY_ID, wszEmail);
			}
		}

		m_arCache.insert(new IcqCacheItem(GetUserId(it), it));
	}
}

IcqCacheItem* CIcqProto::FindContactByUIN(const CMStringW &wszId)
{
	IcqCacheItem tmp(wszId, -1);

	mir_cslock l(m_csCache);
	return m_arCache.find(&tmp);
}

MCONTACT CIcqProto::CreateContact(const CMStringW &wszId, bool bTemporary)
{
	auto *pCache = FindContactByUIN(wszId);
	if (pCache != nullptr)
		return pCache->m_hContact;

	MCONTACT hContact = db_add_contact();
	Proto_AddToContact(hContact, m_szModuleName);
	setWString(hContact, DB_KEY_ID, wszId);
	pCache = new IcqCacheItem(wszId, hContact);
	{
		mir_cslock l(m_csCache);
		m_arCache.insert(pCache);
	}
	RetrieveUserInfo(hContact);

	if (bTemporary)
		db_set_b(hContact, "CList", "NotOnList", 1);

	return hContact;
}

/////////////////////////////////////////////////////////////////////////////////////////

void CIcqProto::CalcHash(AsyncHttpRequest *pReq)
{
	CMStringA hashData(FORMAT, "%s&%s&%s", 
		pReq->requestType == REQUEST_POST ? "POST" : "GET",
		mir_urlEncode(pReq->m_szUrl).c_str(), mir_urlEncode(pReq->m_szParam).c_str());

	unsigned int len;
	BYTE hashOut[MIR_SHA256_HASH_SIZE];
	HMAC(EVP_sha256(), m_szSessionKey, m_szSessionKey.GetLength(), (BYTE*)hashData.c_str(), hashData.GetLength(), hashOut, &len);
	pReq << CHAR_PARAM("sig_sha256", ptrA(mir_base64_encode(hashOut, sizeof(hashOut))));
}

/////////////////////////////////////////////////////////////////////////////////////////

void CIcqProto::Json2int(MCONTACT hContact, const JSONNode &node, const char *szJson, const char *szSetting)
{
	const JSONNode &var = node[szJson];
	if (var)
		setDword(hContact, szSetting, var.as_int());
	else
		delSetting(hContact, szSetting);
}

void CIcqProto::Json2string(MCONTACT hContact, const JSONNode &node, const char *szJson, const char *szSetting)
{
	const JSONNode &var = node[szJson];
	if (var)
		setWString(hContact, szSetting, var.as_mstring());
	else
		delSetting(hContact, szSetting);
}

/////////////////////////////////////////////////////////////////////////////////////////
// Avatars

void CIcqProto::GetAvatarFileName(MCONTACT hContact, wchar_t* pszDest, size_t cbLen)
{
	int tPathLen = mir_snwprintf(pszDest, cbLen, L"%s\\%S", VARSW(L"%miranda_avatarcache%"), m_szModuleName);

	DWORD dwAttributes = GetFileAttributes(pszDest);
	if (dwAttributes == 0xffffffff || (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
		CreateDirectoryTreeW(pszDest);

	pszDest[tPathLen++] = '\\';

	CMStringW wszFileName(getMStringW(hContact, "IconId"));
	const wchar_t* szFileType = ProtoGetAvatarExtension(getByte(hContact, "AvatarType", PA_FORMAT_PNG));
	mir_snwprintf(pszDest + tPathLen, MAX_PATH - tPathLen, L"%s%s", wszFileName.c_str(), szFileType);
}

INT_PTR __cdecl CIcqProto::GetAvatar(WPARAM wParam, LPARAM lParam)
{
	wchar_t *buf = (wchar_t*)wParam;
	int size = (int)lParam;
	if (buf == nullptr || size <= 0)
		return -1;

	GetAvatarFileName(0, buf, size);
	return 0;
}

INT_PTR __cdecl CIcqProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam)
{
	switch (wParam) {
	case AF_MAXSIZE:
		((POINT*)lParam)->x = -1;
		((POINT*)lParam)->y = -1;
		return 0;

	case AF_FORMATSUPPORTED: // nobody
		return 1;

	case AF_DELAYAFTERFAIL:
		return 10 * 60 * 1000;

	case AF_ENABLED:
	case AF_FETCHIFPROTONOTVISIBLE:
	case AF_FETCHIFCONTACTOFFLINE:
		return 1;
	}
	return 0;
}

INT_PTR __cdecl CIcqProto::GetAvatarInfo(WPARAM, LPARAM lParam)
{
	PROTO_AVATAR_INFORMATION* pai = (PROTO_AVATAR_INFORMATION*)lParam;

	ptrW szIconId(getWStringA(pai->hContact, "IconId"));
	if (szIconId == nullptr) {
		debugLogA("No avatar");
		return GAIR_NOAVATAR;
	}

	GetAvatarFileName(pai->hContact, pai->filename, _countof(pai->filename));
	pai->format = getByte(pai->hContact, "AvatarType", 0);

	if (::_waccess(pai->filename, 0) == 0)
		return GAIR_SUCCESS;

	debugLogA("No avatar");
	return GAIR_NOAVATAR;
}

INT_PTR __cdecl CIcqProto::SetAvatar(WPARAM, LPARAM lParam)
{
	wchar_t* pwszFileName = (wchar_t*)lParam;

	wchar_t wszOldName[MAX_PATH];
	GetAvatarFileName(0, wszOldName, _countof(wszOldName));
	_wremove(wszOldName);

	auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, ICQ_API_SERVER "/expressions/upload");
	pReq->m_szUrl.AppendFormat("?f=json&aimsid=%s&r=%s&type=largeBuddyIcon", mir_urlEncode(m_aimsid.c_str()).c_str(), pReq->m_reqId);

	if (pwszFileName == nullptr)
		delSetting("AvatarHash");
	else {
		int fileId = _wopen(pwszFileName, _O_RDONLY | _O_BINARY, _S_IREAD);
		if (fileId < 0) {
			delete pReq;
			return 1;
		}

		unsigned dwSize = (unsigned)_filelengthi64(fileId);
		char* pData = (char*)mir_alloc(dwSize);
		if (pData == nullptr) {
			_close(fileId);
			delete pReq;
			return 2;
		}

		_read(fileId, pData, dwSize);
		_close(fileId);

		pReq->pData = pData;
		pReq->dataLength = dwSize;
		
		int iAvatarType = ProtoGetBufferFormat(pData);
		if (iAvatarType == PA_FORMAT_UNKNOWN) {
			delete pReq;
			delete pData;
			return 3;
		}
		
		pReq->AddHeader("Content-Type", ProtoGetAvatarMimeType(iAvatarType));
	}
	Push(pReq);

	return 0;   // TODO
}

/////////////////////////////////////////////////////////////////////////////////////////

CMStringW CIcqProto::GetUserId(MCONTACT hContact)
{
	if (isChatRoom(hContact))
		return getMStringW(hContact, "ChatRoomID");

	return getMStringW(hContact, DB_KEY_ID);
}

bool IsChat(const CMStringW &aimid)
{
	return aimid.Right(11) == "@chat.agent";
}

int StatusFromString(const CMStringW &wszStatus)
{
	if (wszStatus == L"online")
		return ID_STATUS_ONLINE;
	if (wszStatus == L"n/a")
		return ID_STATUS_NA;
	if (wszStatus == L"away")
		return ID_STATUS_AWAY;
	if (wszStatus == L"occupied")
		return ID_STATUS_OCCUPIED;
	if (wszStatus == L"dnd")
		return ID_STATUS_DND;

	return ID_STATUS_OFFLINE;
}

/////////////////////////////////////////////////////////////////////////////////////////

__int64 CIcqProto::getId(MCONTACT hContact, const char *szSetting)
{
	DBVARIANT dbv;
	dbv.type = DBVT_BLOB;
	if (db_get(hContact, m_szModuleName, szSetting, &dbv))
		return 0;

	__int64 result = (dbv.cpbVal == sizeof(__int64)) ? *(__int64*)dbv.pbVal : 0;
	db_free(&dbv);
	return result;
}

void CIcqProto::setId(MCONTACT hContact, const char *szSetting, __int64 iValue)
{
	__int64 oldVal = getId(hContact, szSetting);
	if (oldVal != iValue)
		db_set_blob(hContact, m_szModuleName, szSetting, &iValue, sizeof(iValue));
}

/////////////////////////////////////////////////////////////////////////////////////////

char* time2text(time_t time)
{
	if (time == 0)
		return "";

	tm *local = localtime(&time);
	if (local) {
		char *str = asctime(local);
		str[24] = '\0'; // remove new line
		return str;
	}

	return "<invalid>";
}

/////////////////////////////////////////////////////////////////////////////////////////

static LRESULT CALLBACK PopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) {
	case WM_CONTEXTMENU:
		PUDeletePopup(hWnd);
		break;

	case WM_COMMAND:
		CIcqProto *ppro = (CIcqProto*)PUGetPluginData(hWnd);
		CallProtoService(ppro->m_szModuleName, PS_GOTO_INBOX);
		PUDeletePopup(hWnd);
		break;
	}

	return DefWindowProc(hWnd, message, wParam, lParam);
}

void CIcqProto::EmailNotification(const wchar_t *pwszText)
{
	if (g_bPopupService) {
		POPUPDATAW Popup = {};
		Popup.lchIcon = IcoLib_GetIconByHandle(iconList[0].hIcolib);
		wcsncpy_s(Popup.lpwzText, pwszText, _TRUNCATE);
		wcsncpy_s(Popup.lpwzContactName, m_tszUserName, _TRUNCATE);
		Popup.iSeconds = 20;
		Popup.PluginData = this;
		Popup.PluginWindowProc = PopupDlgProc;
		PUAddPopupW(&Popup);
	}

	if (m_bUseTrayIcon) {
		char szServiceFunction[MAX_PATH];
		if (m_bLaunchMailbox)
			mir_snprintf(szServiceFunction, "%s%s", m_szModuleName, PS_GOTO_INBOX);
		else
			mir_snprintf(szServiceFunction, "%s%s", m_szModuleName, PS_DUMMY);

		int i = 0;
		while (CLISTEVENT *pcle = g_clistApi.pfnGetEvent(-1, i++))
			if (!mir_strcmp(pcle->pszService, szServiceFunction))
				return;

		CLISTEVENT cle = {};
		cle.hDbEvent = 1;
		cle.hIcon = IcoLib_GetIconByHandle(iconList[0].hIcolib);
		cle.flags = CLEF_UNICODE;
		cle.pszService = szServiceFunction;
		cle.szTooltip.w = pwszText;
		g_clistApi.pfnAddEvent(&cle);
	}
}