/*

IEView Plugin for Miranda IM
Copyright (C) 2005-2010  Piotr Piastucki

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 "HTMLBuilder.h"
#include "m_MathModule.h"
#include "m_metacontacts.h"
#include "Utils.h"
//#include "Smiley.h"
#include "Options.h"


int HTMLBuilder::mimFlags = 0;

HTMLBuilder::HTMLBuilder() {
	lastIEViewEvent.cbSize = sizeof (IEVIEWEVENT);
	lastIEViewEvent.iType = IEE_LOG_MEM_EVENTS;
	lastIEViewEvent.codepage = CP_ACP;
	lastIEViewEvent.pszProto = NULL;
	lastIEViewEvent.count = 0;
	lastIEViewEvent.dwFlags = 0;
	lastIEViewEvent.hContact = NULL;
	lastIEViewEvent.hwnd = NULL;
	lastIEViewEvent.eventData = NULL;
}

HTMLBuilder::~HTMLBuilder() {
	if (lastIEViewEvent.pszProto != NULL) {
		delete (char*)lastIEViewEvent.pszProto;
	}
}

bool HTMLBuilder::encode(HANDLE hContact, const char *proto, const wchar_t *text, wchar_t **output, int *outputSize,  int level, int flags, bool isSent) {
	TextToken *token = NULL, *token2;
	switch (level) {
	case 0:
		if (flags & ENF_CHAT_FORMATTING) {
			token = TextToken::tokenizeChatFormatting(text);
			break;
		}
		level++;
	case 1:
		if ((Options::getGeneralFlags()&Options::GENERAL_ENABLE_BBCODES) && (flags & ENF_BBCODES)) {
			token = TextToken::tokenizeBBCodes(text);
			break;
		}
		level++;
	case 2:
		if ((Options::getGeneralFlags()&Options::GENERAL_ENABLE_MATHMODULE) && Options::isMathModule()) {
			token = TextToken::tokenizeMath(text);
			break;
		}
		level++;
	case 3:
		token = TextToken::tokenizeLinks(text);
		break;
	case 4:
		if ((flags & ENF_SMILEYS) ||
				((Options::getGeneralFlags() & Options::GENERAL_SMILEYINNAMES) &&  (flags & ENF_NAMESMILEYS))) {
			token = TextToken::tokenizeSmileys(hContact, proto, text, isSent);
		}
		break;
	}
	if (token!=NULL) {
		for (token2 = token;token!=NULL;token=token2) {
			bool skip = false;
			token2 = token->getNext();
			if (token->getType() == TextToken::TEXT) {
				skip = encode(hContact, proto, token->getTextW(), output, outputSize, level+1, flags, isSent);
			}
			if (!skip) {
				token->toString(output, outputSize);
			}
			delete token;
		}
		return true;
	}
	return false;
}

wchar_t * HTMLBuilder::encode(HANDLE hContact, const char *proto, const wchar_t *text, int flags, bool isSent) {
	int outputSize;
	wchar_t *output = NULL;
	if (text != NULL) {
		encode(hContact, proto, text, &output, &outputSize, 0, flags, isSent);
	}
	return output;
}

char * HTMLBuilder::encodeUTF8(HANDLE hContact, const char *proto, const wchar_t *wtext, int flags, bool isSent) {
	char *outputStr = NULL;
	if (wtext != NULL) {
		wchar_t *output = encode(hContact, proto, wtext, flags, isSent);
		outputStr = Utils::UTF8Encode(output);
		if (output != NULL) {
			free(output);
		}
	}
	return outputStr;
}

char * HTMLBuilder::encodeUTF8(HANDLE hContact, const char *proto, const char *text, int flags, bool isSent) {
	char *outputStr = NULL;
	if (text != NULL) {
		wchar_t *wtext = Utils::convertToWCS(text);
		outputStr = encodeUTF8(hContact, proto, wtext, flags, isSent);
		delete wtext;
	}
	return outputStr;
}

char * HTMLBuilder::encodeUTF8(HANDLE hContact, const char *proto, const char *text, int cp, int flags, bool isSent) {
	char * outputStr = NULL;
	if (text != NULL) {
		wchar_t *wtext = Utils::convertToWCS(text, cp);
		outputStr = encodeUTF8(hContact, proto, wtext, flags, isSent);
		delete wtext;
	}
	return outputStr;
}

char *HTMLBuilder::getProto(HANDLE hContact) {
	return Utils::dupString((char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0));
}

char *HTMLBuilder::getProto(const char *proto, HANDLE hContact) {
	if (proto != NULL) {
		return Utils::dupString(proto);
	}
	return Utils::dupString((char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0));
}

char *HTMLBuilder::getRealProto(HANDLE hContact) {
	if (hContact != NULL) {
		char *szProto = Utils::dupString((char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0));
		if (szProto!=NULL && !strcmp(szProto,"MetaContacts")) {
			hContact = (HANDLE) CallService(MS_MC_GETMOSTONLINECONTACT, (WPARAM) hContact, 0);
			if (hContact!=NULL) {
				delete szProto;
				szProto = Utils::dupString((char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0));
			}
		}
		return szProto;
	}
	return NULL;
}

char *HTMLBuilder::getRealProto(HANDLE hContact, const char *szProto) {
	if (szProto!=NULL && !strcmp(szProto,"MetaContacts")) {
		hContact = (HANDLE) CallService(MS_MC_GETMOSTONLINECONTACT, (WPARAM) hContact, 0);
		if (hContact!=NULL) {
			return Utils::dupString((char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0));
		}
	}
	return Utils::dupString(szProto);
}

HANDLE HTMLBuilder::getRealContact(HANDLE hContact) {
	char *szProto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
	if (szProto != NULL && !strcmp(szProto,"MetaContacts")) {
		hContact = (HANDLE) CallService(MS_MC_GETMOSTONLINECONTACT, (WPARAM) hContact, 0);
	}
	return hContact;
}

int HTMLBuilder::getLastEventType() {
	return iLastEventType;
}

void HTMLBuilder::setLastEventType(int t) {
	iLastEventType = t;
}

DWORD HTMLBuilder::getLastEventTime() {
	return lastEventTime;
}

void HTMLBuilder::setLastEventTime(DWORD t) {
	lastEventTime = t;
}

bool HTMLBuilder::isSameDate(time_t time1, time_t time2) {
	struct tm tm_t1, tm_t2;
	tm_t1 = *localtime((time_t *)(&time1));
	tm_t2 = *localtime((time_t *)(&time2));
	if (tm_t1.tm_year == tm_t2.tm_year && tm_t1.tm_mon == tm_t2.tm_mon
		&& tm_t1.tm_mday == tm_t2.tm_mday) {
		return true;
	}
	return false;
}

void HTMLBuilder::getUINs(HANDLE hContact, char *&uinIn, char *&uinOut) {
	CONTACTINFO ci;
	char buf[128];
	char *szProto;
	hContact = getRealContact(hContact);
	szProto = getProto(hContact);
	ZeroMemory(&ci, sizeof(ci));
	ci.cbSize = sizeof(ci);
	ci.hContact = hContact;
	ci.szProto = szProto;
	ci.dwFlag = CNF_UNIQUEID;
	buf[0] = 0;
	if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM) & ci)) {
		switch (ci.type) {
		case CNFT_ASCIIZ:
			mir_snprintf(buf, sizeof(buf), "%s", ci.pszVal);
			miranda_sys_free(ci.pszVal);
			break;
		case CNFT_DWORD:
			mir_snprintf(buf, sizeof(buf), "%u", ci.dVal);
			break;
		}
	}
	uinIn = Utils::UTF8Encode(buf);
	ci.hContact = NULL;
	buf[0] = 0;
	if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM) & ci)) {
		switch (ci.type) {
		case CNFT_ASCIIZ:
			mir_snprintf(buf, sizeof(buf), "%s", ci.pszVal);
			miranda_sys_free(ci.pszVal);
			break;
		case CNFT_DWORD:
			mir_snprintf(buf, sizeof(buf), "%u", ci.dVal);
			break;
		}
	}
	uinOut = Utils::UTF8Encode(buf);
	delete szProto;
}

wchar_t *HTMLBuilder::getContactName(HANDLE hContact, const char* szProto) {
	CONTACTINFO ci;
	wchar_t *szName = NULL;
	ZeroMemory(&ci, sizeof(ci));
	ci.cbSize = sizeof(ci);
	ci.hContact = hContact;
	ci.szProto = (char *)szProto;
	ci.dwFlag = CNF_DISPLAY | CNF_UNICODE;
	if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM) & ci)) {
		if (ci.type == CNFT_ASCIIZ) {
			if (ci.pszVal) {
				if (!wcscmp((wchar_t *)ci.pszVal, TranslateW(L"'(Unknown Contact)'"))) {
					ci.dwFlag &= ~CNF_UNICODE;
					if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM) & ci)) {
						szName = Utils::convertToWCS((char *)ci.pszVal);
					}
				} else {
					szName = Utils::dupString((wchar_t *)ci.pszVal);
				}
				miranda_sys_free(ci.pszVal);
			}
		}
	}
	if (szName != NULL) return szName;
	ci.dwFlag = CNF_UNIQUEID;
	if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM) & ci)) {
		if (ci.type == CNFT_ASCIIZ) {
			if (ci.pszVal) {
				szName = Utils::convertToWCS((char *)ci.pszVal);
				miranda_sys_free(ci.pszVal);
			}
		}
	}
	if (szName != NULL) return szName;
	char *szNameStr = (char *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, 0);
	if (szNameStr != NULL) {
		return Utils::convertToWCS(szNameStr);
	}
	return Utils::dupString(TranslateT("(Unknown Contact)"));
}

char *HTMLBuilder::getEncodedContactName(HANDLE hContact, const char* szProto, const char* szSmileyProto) {
	char *szName = NULL;
	wchar_t *name = getContactName(hContact, szProto);
	if (name != NULL) {
		szName = encodeUTF8(hContact, szSmileyProto, name, ENF_NAMESMILEYS, true);
		delete name;
		return szName;
	}
	return encodeUTF8(hContact, szSmileyProto, TranslateT("(Unknown Contact)"), ENF_NAMESMILEYS, true);
}

void HTMLBuilder::appendEventNew(IEView *view, IEVIEWEVENT *event) {
	setLastIEViewEvent(event);
	appendEvent(view, event);
}

void HTMLBuilder::appendEventOld(IEView *view, IEVIEWEVENT *event) {
	IEVIEWEVENT newEvent;
	IEVIEWEVENTDATA* eventData;
	IEVIEWEVENTDATA* prevEventData = NULL;
	char *szProto = NULL;
	HANDLE hDbEvent = event->hDbEventFirst;
	event->hDbEventFirst = NULL;
	newEvent.cbSize = sizeof (IEVIEWEVENT);
	newEvent.iType = IEE_LOG_MEM_EVENTS;
	newEvent.codepage = CP_ACP;
	if (event->cbSize >= IEVIEWEVENT_SIZE_V2) {
		newEvent.codepage = event->codepage;
	}
	if (event->cbSize >= IEVIEWEVENT_SIZE_V3 && event->pszProto != NULL) {
		szProto = Utils::dupString(event->pszProto);
	} else {
		szProto = getProto(event->hContact);
	}
	newEvent.pszProto = szProto;
	newEvent.count = 0;
	newEvent.dwFlags = event->dwFlags;
	newEvent.hContact = event->hContact;
	newEvent.hwnd = event->hwnd;
	newEvent.eventData = NULL;
	for (int eventIdx = 0; hDbEvent!=NULL && (eventIdx < event->count || event->count==-1); eventIdx++) {
		DBEVENTINFO dbei = { 0 };
		dbei.cbSize = sizeof(dbei);
		dbei.cbBlob = CallService(MS_DB_EVENT_GETBLOBSIZE, (WPARAM) hDbEvent, 0);
		if (dbei.cbBlob == 0xFFFFFFFF) {
			hDbEvent = (HANDLE) CallService(MS_DB_EVENT_FINDNEXT, (WPARAM) hDbEvent, 0);
			continue;
		}
		dbei.pBlob = (PBYTE) malloc(dbei.cbBlob);
		CallService(MS_DB_EVENT_GET, (WPARAM)  hDbEvent, (LPARAM) & dbei);
		if (!(dbei.flags & DBEF_SENT) && (dbei.eventType == EVENTTYPE_MESSAGE || dbei.eventType == EVENTTYPE_URL)) {
			CallService(MS_DB_EVENT_MARKREAD, (WPARAM) event->hContact, (LPARAM) hDbEvent);
			CallService(MS_CLIST_REMOVEEVENT, (WPARAM) event->hContact, (LPARAM) hDbEvent);
		} else if (dbei.eventType == EVENTTYPE_STATUSCHANGE) {
			CallService(MS_DB_EVENT_MARKREAD, (WPARAM) event->hContact, (LPARAM) hDbEvent);
		}
		if (!isDbEventShown(&dbei)) {
			free(dbei.pBlob);
			hDbEvent = (HANDLE) CallService(MS_DB_EVENT_FINDNEXT, (WPARAM) hDbEvent, 0);
			continue;
		}
		eventData = new IEVIEWEVENTDATA;
		eventData->cbSize = sizeof(IEVIEWEVENTDATA);
		eventData->dwFlags = IEEDF_UNICODE_TEXT | IEEDF_UNICODE_NICK | IEEDF_UNICODE_TEXT2 |
							(dbei.flags & DBEF_READ ? IEEDF_READ : 0) | (dbei.flags & DBEF_SENT ? IEEDF_SENT : 0) | (dbei.flags & DBEF_RTL ? IEEDF_RTL : 0);
		if (event->dwFlags & IEEF_RTL) {
			eventData->dwFlags  |= IEEDF_RTL;
		}
		eventData->time = dbei.timestamp;
		eventData->pszNickW = NULL;
		eventData->pszTextW = NULL;
		eventData->pszText2W = NULL;
		if (dbei.flags & DBEF_SENT) {
			eventData->pszNickW = getContactName(NULL, szProto);
			eventData->bIsMe = TRUE;
		} else {
			eventData->pszNickW = getContactName(event->hContact, szProto);
			eventData->bIsMe = FALSE;
		}
		if (dbei.eventType == EVENTTYPE_MESSAGE || dbei.eventType == EVENTTYPE_URL || dbei.eventType == EVENTTYPE_STATUSCHANGE || dbei.eventType == EVENTTYPE_JABBER_CHATSTATES) {
			DBEVENTGETTEXT temp = { &dbei, DBVT_WCHAR + ((event->dwFlags & IEEF_NO_UNICODE) ? DBVTF_DENYUNICODE : 0), newEvent.codepage  };
			WCHAR* pwszEventText = (WCHAR*)CallService(MS_DB_EVENT_GETTEXT,0,(LPARAM)&temp);
			eventData->pszTextW = Utils::dupString( pwszEventText );
			mir_free( pwszEventText );
			if (dbei.eventType == EVENTTYPE_MESSAGE) {
				eventData->iType = IEED_EVENT_MESSAGE;
			} else if (dbei.eventType == EVENTTYPE_URL) {
				eventData->iType = IEED_EVENT_URL;
			} else {
				eventData->iType = IEED_EVENT_STATUSCHANGE;
			}
		} else if (dbei.eventType == EVENTTYPE_FILE) {
			//blob is: sequenceid(DWORD),filename(ASCIIZ),description(ASCIIZ)
			char* filename = ((char *)dbei.pBlob) + sizeof(DWORD);
			char* descr = filename + lstrlenA(filename) + 1;
			TCHAR *tStr = DbGetEventStringT(&dbei, filename);
			eventData->ptszText = Utils::dupString(tStr);
			mir_free(tStr);
			if (*descr != '\0') {
				tStr = DbGetEventStringT(&dbei, descr);
				eventData->ptszText2 = Utils::dupString(tStr);
				mir_free(tStr);
			}
			eventData->iType = IEED_EVENT_FILE;
		} else if (dbei.eventType == EVENTTYPE_AUTHREQUEST) {
			//blob is: uin(DWORD), hContact(DWORD), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ)
			eventData->ptszText = Utils::dupString(TranslateT(" requested authorisation"));
			TCHAR *tStr = DbGetEventStringT(&dbei, (char *)dbei.pBlob + 8);
			eventData->ptszNick = Utils::dupString(tStr);
			mir_free(tStr);
			eventData->iType = IEED_EVENT_SYSTEM;
		} else if (dbei.eventType == EVENTTYPE_ADDED) {
			//blob is: uin(DWORD), hContact(DWORD), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ)
			eventData->ptszText = Utils::dupString(TranslateT(" was added."));
			TCHAR *tStr = DbGetEventStringT(&dbei, (char *)dbei.pBlob + 8);
			eventData->ptszNick = Utils::dupString(tStr);
			mir_free(tStr);
			eventData->iType = IEED_EVENT_SYSTEM;
		}
		free(dbei.pBlob);
		eventData->next = NULL;
		if (prevEventData != NULL) {
			prevEventData->next = eventData;
		} else {
			newEvent.eventData = eventData;
		}
		prevEventData = eventData;
		newEvent.count++;
		event->hDbEventFirst = hDbEvent;
		hDbEvent = (HANDLE) CallService(MS_DB_EVENT_FINDNEXT, (WPARAM) hDbEvent, 0);
	}
	appendEventNew(view, &newEvent);
	for ( IEVIEWEVENTDATA* eventData2 = newEvent.eventData; eventData2 != NULL; eventData2 = eventData) {
		eventData = eventData2->next;
		if (eventData2->pszTextW != NULL) {
			delete (wchar_t*)eventData2->pszTextW;
		}
		if (eventData2->pszText2W != NULL) {
			delete (wchar_t*)eventData2->pszText2W;
		}
		if (eventData2->pszNickW != NULL) {
			delete (wchar_t*)eventData2->pszNickW;
		}
		delete eventData2;
	}
	if (szProto != NULL) {
		delete szProto;
	}
}

ProtocolSettings* HTMLBuilder::getSRMMProtocolSettings(const char *protocolName) {
	ProtocolSettings *protoSettings =  Options::getProtocolSettings(protocolName);
	if (protoSettings == NULL || !protoSettings->isSRMMEnable()) {
		protoSettings =  Options::getProtocolSettings();
	}
	return protoSettings;
}

ProtocolSettings* HTMLBuilder::getSRMMProtocolSettings(HANDLE hContact) {
	char *szRealProto = getRealProto(hContact);
	ProtocolSettings *protoSettings =  getSRMMProtocolSettings(szRealProto);
	delete szRealProto;
	return protoSettings;
}

ProtocolSettings* HTMLBuilder::getHistoryProtocolSettings(const char *protocolName) {
	ProtocolSettings *protoSettings =  Options::getProtocolSettings(protocolName);
	if (protoSettings == NULL || !protoSettings->isHistoryEnable()) {
		protoSettings =  Options::getProtocolSettings();
	}
	return protoSettings;
}

ProtocolSettings* HTMLBuilder::getHistoryProtocolSettings(HANDLE hContact) {
	ProtocolSettings *protoSettings;
	if (hContact != NULL) {
		char *szRealProto = getRealProto(hContact);
		protoSettings =  getHistoryProtocolSettings(szRealProto);
		delete szRealProto;
	} else {
		protoSettings =  Options::getProtocolSettings();
	}
	return protoSettings;
}

ProtocolSettings* HTMLBuilder::getChatProtocolSettings(const char *protocolName) {
	ProtocolSettings *protoSettings =  Options::getProtocolSettings(protocolName);
	if (protoSettings == NULL || !protoSettings->isChatEnable()) {
		protoSettings =  Options::getProtocolSettings();
	}
	return protoSettings;
}

ProtocolSettings* HTMLBuilder::getChatProtocolSettings(HANDLE hContact) {
	char *szRealProto = getRealProto(hContact);
	ProtocolSettings *protoSettings =  getChatProtocolSettings(szRealProto);
	delete szRealProto;
	return protoSettings;
}

void HTMLBuilder::setLastIEViewEvent(IEVIEWEVENT *event) {
	lastIEViewEvent.cbSize = sizeof (IEVIEWEVENT);
	lastIEViewEvent.iType = event->iType;
	lastIEViewEvent.codepage = CP_ACP;
	if (event->cbSize >= IEVIEWEVENT_SIZE_V2) {
		lastIEViewEvent.codepage = event->codepage;
	}
	lastIEViewEvent.count = 0;
	lastIEViewEvent.dwFlags = event->dwFlags;
	lastIEViewEvent.hContact = event->hContact;
	lastIEViewEvent.hwnd = event->hwnd;
	lastIEViewEvent.eventData = NULL;
	if (lastIEViewEvent.pszProto != NULL) {
		delete (char *)lastIEViewEvent.pszProto ;
	}
	if (event->cbSize >= IEVIEWEVENT_SIZE_V3 && event->pszProto != NULL) {
		lastIEViewEvent.pszProto = Utils::dupString(event->pszProto);
	} else {
		lastIEViewEvent.pszProto = getProto(event->hContact);
	}
}

void HTMLBuilder::clear(IEView *view, IEVIEWEVENT *event) {
	if (event != NULL) {
		setLastIEViewEvent(event);
	}
	if (lastIEViewEvent.pszProto != NULL || event->hContact == NULL) {
		buildHead(view, &lastIEViewEvent);
	}
}