#include "stdafx.h"
#include "CConfig.h"
#include "CAppletManager.h"
#include "m_system.h"

// specifies how long contact status notifications are ignored after signon
#define PROTOCOL_NOTIFY_DELAY 1500

//########################################################################
// functions
//########################################################################

//************************************************************************
// returns the AppletManager's instance
//************************************************************************
CAppletManager *CAppletManager::GetInstance()
{
	return (CAppletManager*)CLCDOutputManager::GetInstance();
}

//************************************************************************
// Constructor
//************************************************************************
CAppletManager::CAppletManager()
{
	m_uiTimer = NULL;
	m_pLastScreen = NULL;


}

//************************************************************************
// Destructor
//************************************************************************
CAppletManager::~CAppletManager()
{

}

//************************************************************************
// Initializes the AppletManager
//************************************************************************
bool CAppletManager::Initialize(tstring strAppletName)
{
	if (!CLCDOutputManager::Initialize(strAppletName))
		return false;

	GetLCDConnection()->Connect(CConfig::GetIntSetting(DEVICE));

	// set the volumewheel hook
	SetVolumeWheelHook();

	// initialize the screens
	m_NotificationScreen.Initialize();
	m_EventScreen.Initialize();
	m_ContactlistScreen.Initialize();
	m_ChatScreen.Initialize();
	m_CreditsScreen.Initialize();
	m_ScreensaverScreen.Initialize();

	// add the screens to the list
	AddScreen(&m_NotificationScreen);
	AddScreen(&m_EventScreen);
	AddScreen(&m_ContactlistScreen);
	AddScreen(&m_ChatScreen);
	AddScreen(&m_CreditsScreen);
	AddScreen(&m_ScreensaverScreen);

	// activate the event screen
	ActivateScreen(&m_EventScreen);

	// hook the neccessary events
	m_hMIHookMessageWindowEvent = HookEvent(ME_MSG_WINDOWEVENT, CAppletManager::HookMessageWindowEvent);
	m_hMIHookEventAdded = HookEvent(ME_DB_EVENT_ADDED, CAppletManager::HookEventAdded);
	m_hMIHookStatusChanged = HookEvent(ME_DB_CONTACT_SETTINGCHANGED, CAppletManager::HookStatusChanged);
	m_hMIHookProtoAck = HookEvent(ME_PROTO_ACK, CAppletManager::HookProtoAck);
	m_hMIHookContactDeleted = HookEvent(ME_DB_CONTACT_DELETED, CAppletManager::HookContactDeleted);
	m_hMIHookContactAdded = HookEvent(ME_DB_CONTACT_ADDED, CAppletManager::HookContactAdded);
	m_hMIHookSettingChanged = HookEvent(ME_DB_CONTACT_SETTINGCHANGED, CAppletManager::HookSettingChanged);
	m_hMIHookContactIsTyping = HookEvent(ME_PROTO_CONTACTISTYPING, CAppletManager::HookContactIsTyping);
	m_hMIHookChatEvent = HookEvent(ME_GC_HOOK_EVENT, CAppletManager::HookChatInbound);

	// enumerate protocols
	int iCount;
	int iProtoCount = 0;
	PROTOACCOUNT **ppAccounts;
	CProtocolData *pProtoData = NULL;
	CIRCConnection *pIRCConnection = NULL;

	Proto_EnumAccounts(&iCount, &ppAccounts);
	for (int i = 0; i < iCount; i++) {
		/**if(ppProtocolDescriptor[i]->type != PROTOTYPE_PROTOCOL)
			continue;**/
		if (ppAccounts[i]->bIsEnabled == 0)
			continue;

		iProtoCount++;
		pProtoData = new CProtocolData();
		pProtoData->iStatus = ID_STATUS_OFFLINE;
		pProtoData->strProtocol = toTstring(ppAccounts[i]->szModuleName);
		pProtoData->lTimeStamp = 0;

		// try to create an irc connection for that protocol (will fail if it is no irc protocol)
		pIRCConnection = CreateIRCConnection(pProtoData->strProtocol);

		m_vProtocolData.push_back(pProtoData);
	}

	// load status bitmaps
	m_ahStatusBitmaps[0] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_STATUS_OFFLINE), IMAGE_BITMAP, 5, 5, LR_MONOCHROME);
	m_ahStatusBitmaps[1] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_STATUS_ONLINE), IMAGE_BITMAP, 5, 5, LR_MONOCHROME);
	m_ahStatusBitmaps[2] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_STATUS_AWAY), IMAGE_BITMAP, 5, 5, LR_MONOCHROME);
	m_ahStatusBitmaps[3] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_STATUS_NA), IMAGE_BITMAP, 5, 5, LR_MONOCHROME);
	m_ahStatusBitmaps[4] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_STATUS_OCCUPIED), IMAGE_BITMAP, 5, 5, LR_MONOCHROME);
	m_ahStatusBitmaps[5] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_STATUS_DND), IMAGE_BITMAP, 5, 5, LR_MONOCHROME);
	m_ahStatusBitmaps[6] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_STATUS_INVISIBLE), IMAGE_BITMAP, 5, 5, LR_MONOCHROME);
	m_ahStatusBitmaps[7] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_STATUS_FFC), IMAGE_BITMAP, 5, 5, LR_MONOCHROME);
	// Load event bitmaps
	m_ahEventBitmaps[0] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_EVENT_MSG), IMAGE_BITMAP, 6, 6, LR_MONOCHROME);
	m_ahEventBitmaps[1] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_EVENT_CON), IMAGE_BITMAP, 6, 6, LR_MONOCHROME);
	m_ahEventBitmaps[2] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_EVENT_USER), IMAGE_BITMAP, 6, 6, LR_MONOCHROME);
	m_ahEventBitmaps[3] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_EVENT_INFO), IMAGE_BITMAP, 6, 6, LR_MONOCHROME);

	m_ahLargeEventBitmaps[0] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_EVENT_MSG_LARGE), IMAGE_BITMAP, 8, 8, LR_MONOCHROME);
	m_ahLargeEventBitmaps[1] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_EVENT_CON_LARGE), IMAGE_BITMAP, 8, 8, LR_MONOCHROME);
	m_ahLargeEventBitmaps[2] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_EVENT_USER_LARGE), IMAGE_BITMAP, 8, 8, LR_MONOCHROME);
	m_ahLargeEventBitmaps[3] = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDB_EVENT_INFO_LARGE), IMAGE_BITMAP, 8, 8, LR_MONOCHROME);

	// start the update timer
	m_uiTimer = SetTimer(0, 0, 1000 / 10, CAppletManager::UpdateTimer);

	return true;
}

//************************************************************************
// Deinitializes the AppletManager
//************************************************************************
bool CAppletManager::Shutdown()
{
	if (!IsInitialized())
		return false;

	// stop the update timer
	KillTimer(0, m_uiTimer);

	// delete status bitmaps
	for (int i = 0; i < 8; i++)
		DeleteObject(m_ahStatusBitmaps[i]);

	// delete event bitmaps
	for (int i = 0; i < 4; i++) {
		DeleteObject(m_ahLargeEventBitmaps[i]);
		DeleteObject(m_ahEventBitmaps[i]);
	}

	// unhook the events	
	UnhookEvent(m_hMIHookMessageWindowEvent);
	UnhookEvent(m_hMIHookEventAdded);
	UnhookEvent(m_hMIHookStatusChanged);
	UnhookEvent(m_hMIHookProtoAck);
	UnhookEvent(m_hMIHookContactDeleted);
	UnhookEvent(m_hMIHookContactAdded);
	UnhookEvent(m_hMIHookSettingChanged);
	UnhookEvent(m_hMIHookChatEvent);

	// unhook all irc protocols, and delete the classes
	vector<CIRCConnection*>::iterator iter = m_vIRCConnections.begin();
	while (iter != m_vIRCConnections.end()) {
		delete *iter;
		iter++;
	}
	m_vIRCConnections.clear();

	// Deinitialize the screens
	m_NotificationScreen.Shutdown();
	m_EventScreen.Shutdown();
	m_ContactlistScreen.Shutdown();
	m_ChatScreen.Shutdown();
	m_CreditsScreen.Shutdown();
	m_ScreensaverScreen.Shutdown();

	// deinitialize the configuration manager
	CConfig::Shutdown();

	// delete the protocol information
	CProtocolData *pProtoData;
	for (vector<CProtocolData*>::size_type i = 0; i < m_vProtocolData.size(); i++) {
		pProtoData = m_vProtocolData[i];
		delete pProtoData;
	}
	m_vProtocolData.clear();

	// deinitialize the outputmanager
	if (!CLCDOutputManager::Shutdown())
		return false;
	return true;
}

//************************************************************************
// Translates the specified string, and inserts the parameters
//************************************************************************
tstring CAppletManager::TranslateString(wchar_t *szString, ...)
{
	wchar_t out[1024];
	wchar_t *szTranslatedString = TranslateW(szString);

	va_list body;
	va_start(body, szString);
	vswprintf_s(out, _countof(out), szTranslatedString, body);
	va_end(body);
	return out;
}

//************************************************************************
// checks if the patched IRC protocol is in place
//************************************************************************
bool CAppletManager::IsIRCHookEnabled()
{
	if (m_vIRCConnections.size() == NULL)
		return false;
	return true;
}

//************************************************************************
// returns the informations structure for the specified protocol
//************************************************************************
CProtocolData* CAppletManager::GetProtocolData(tstring strProtocol)
{
	for (vector<CProtocolData*>::size_type i = 0; i < m_vProtocolData.size(); i++) {
		if (m_vProtocolData[i]->strProtocol == strProtocol)
			return m_vProtocolData[i];
	}
	return NULL;
}

//************************************************************************
// Updates the AppletManager
//************************************************************************
bool CAppletManager::Update()
{
	if (!CLCDOutputManager::Update())
		return false;

	// Update Messagejobs
	UpdateMessageJobs();

	// Screensaver detection
	BOOL bActive = false;
	SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &bActive, 0);
	if (bActive != (BOOL)m_bScreensaver) {
		if (CConfig::GetBoolSetting(SCREENSAVER_LOCK)) {
			if (!m_bScreensaver)
				ActivateScreensaverScreen();
			else
				ActivateEventScreen();
		}
		if (CConfig::GetBoolSetting(CONTROL_BACKLIGHTS)) {
			if (GetLCDConnection() &&
				GetLCDConnection()->GetConnectionType() == TYPE_LOGITECH) {
				CLCDConnectionLogitech *pLCDConnection = (CLCDConnectionLogitech*)GetLCDConnection();

				// Screensaver starts
				if (!m_bScreensaver) {
					m_G15LightStatus = pLCDConnection->GetLightStatus();
					pLCDConnection->SetLCDBacklight(LCD_OFF);
					pLCDConnection->SetKBDBacklight(KBD_OFF);
					pLCDConnection->SetMKeyLight(0, 0, 0, 0);
				}
				// Screensaver ends
				else {
					SG15LightStatus currentStatus = pLCDConnection->GetLightStatus();

					if (currentStatus.eLCDBrightness == LCD_OFF)
						pLCDConnection->SetLCDBacklight(m_G15LightStatus.eLCDBrightness);
					if (currentStatus.eKBDBrightness == KBD_OFF)
						pLCDConnection->SetKBDBacklight(m_G15LightStatus.eKBDBrightness);
					if (!currentStatus.bMRKey && !currentStatus.bMKey[0] && !currentStatus.bMKey[1]
						&& !currentStatus.bMKey[2])
						pLCDConnection->SetMKeyLight(m_G15LightStatus.bMKey[0], m_G15LightStatus.bMKey[1], m_G15LightStatus.bMKey[2], m_G15LightStatus.bMRKey);
				}
			}
		}
		m_bScreensaver = bActive != 0;
	}

	return true;
}

//************************************************************************
// Called when the active screen has expired
//************************************************************************
void CAppletManager::OnScreenExpired(CLCDScreen *pScreen)
{
	// If the notification screen has expired, activate the last active screen
	if (pScreen == (CLCDScreen*)&m_NotificationScreen) {
		ActivateScreen(m_pLastScreen);
		if (CConfig::GetBoolSetting(TRANSITIONS))
			m_pGfx->StartTransition();
	}
}

//************************************************************************
// the update timer's callback function
//************************************************************************
VOID CALLBACK CAppletManager::UpdateTimer(HWND, UINT, UINT_PTR, DWORD)
{
	CAppletManager::GetInstance()->Update();
}

//************************************************************************
// applies the volumewheel setting
//************************************************************************
void CAppletManager::SetVolumeWheelHook()
{
	// Set the volumewheel hook
	if (GetLCDConnection() && GetLCDConnection()->GetConnectionType() == TYPE_LOGITECH) {
		CLCDConnectionLogitech *pLCDConnection = (CLCDConnectionLogitech*)GetLCDConnection();
		if (pLCDConnection->GetConnectionState() == CONNECTED)
			pLCDConnection->SetVolumeWheelHook(CConfig::GetBoolSetting(HOOK_VOLUMEWHEEL));
	}
}

//************************************************************************
// Called when the connection state has changed
//************************************************************************
void CAppletManager::OnConnectionChanged(int iConnectionState)
{
	if (iConnectionState == CONNECTED) {
		SetVolumeWheelHook();
	}
	CConfig::OnConnectionChanged();
}

//************************************************************************
// called when the plugin's configuration has changed
//************************************************************************
void CAppletManager::OnConfigChanged()
{
	GetLCDConnection()->Connect(CConfig::GetIntSetting(DEVICE));

	m_pGfx->StartTransition(TRANSITION_MORPH);

	// Set the volumewheel hook
	SetVolumeWheelHook();
	// send the event to all screens
	m_NotificationScreen.OnConfigChanged();
	m_ChatScreen.OnConfigChanged();
	m_EventScreen.OnConfigChanged();
	m_ContactlistScreen.OnConfigChanged();
	m_CreditsScreen.OnConfigChanged();
}
//************************************************************************
// activate a screen
//************************************************************************
void CAppletManager::ActivateScreen(CScreen *pScreen)
{
	if (GetActiveScreen() && GetActiveScreen() != &m_NotificationScreen) {
		m_pLastScreen = (CScreen*)GetActiveScreen();
	}

	CLCDOutputManager::ActivateScreen(pScreen);
}

//************************************************************************
// activates the previous screen
//************************************************************************
void CAppletManager::ActivatePreviousScreen()
{
	if (m_pLastScreen) {
		ActivateScreen(m_pLastScreen);
	}
}

//************************************************************************
// activates the credits screen
//************************************************************************
void CAppletManager::ActivateScreensaverScreen()
{
	m_ScreensaverScreen.Reset();
	ActivateScreen(&m_ScreensaverScreen);
}

//************************************************************************
// activates the credits screen
//************************************************************************
void CAppletManager::ActivateCreditsScreen()
{
	m_CreditsScreen.Reset();
	ActivateScreen(&m_CreditsScreen);
}

//************************************************************************
// activates the event screen
//************************************************************************
void CAppletManager::ActivateEventScreen()
{
	m_ChatScreen.SetContact(NULL);
	ActivateScreen(&m_EventScreen);

	if (CConfig::GetBoolSetting(TRANSITIONS))
		m_pGfx->StartTransition();
}

//************************************************************************
// activates the contactlist screen
//************************************************************************
void CAppletManager::ActivateCListScreen()
{
	m_ChatScreen.SetContact(NULL);
	m_ContactlistScreen.ResetPosition();
	ActivateScreen(&m_ContactlistScreen);

	if (CConfig::GetBoolSetting(TRANSITIONS))
		m_pGfx->StartTransition();
}

//************************************************************************
// activates the chat screen
//************************************************************************
bool CAppletManager::ActivateChatScreen(MCONTACT hContact)
{
	if (!m_ChatScreen.SetContact(hContact))
		return false;

	m_ContactlistScreen.OnSessionOpened(hContact);
	ActivateScreen(&m_ChatScreen);

	if (CConfig::GetBoolSetting(TRANSITIONS))
		m_pGfx->StartTransition();
	return true;
}

//************************************************************************
// returns the contacts displayname
//************************************************************************
tstring CAppletManager::GetContactDisplayname(MCONTACT hContact, bool bShortened)
{
	if (!bShortened || !CConfig::GetBoolSetting(NOTIFY_NICKCUTOFF))
		return pcli->pfnGetContactDisplayName(hContact, 0);

	tstring strNick = GetContactDisplayname(hContact, false);
	if (strNick.length() > (tstring::size_type)CConfig::GetIntSetting(NOTIFY_NICKCUTOFF_OFFSET))
		return strNick.erase(CConfig::GetIntSetting(NOTIFY_NICKCUTOFF_OFFSET)) + L"...";

	return strNick;
}

//************************************************************************
// returns the contacts group
//************************************************************************
tstring CAppletManager::GetContactGroup(MCONTACT hContact)
{
	DBVARIANT dbv;
	int res = db_get_ws(hContact, "CList", "Group", &dbv);

	tstring strGroup = L"";
	if (!res)
		strGroup = dbv.ptszVal;

	db_free(&dbv);
	return strGroup;
}

//************************************************************************
// returns the bitmap for the specified event
//************************************************************************
HBITMAP CAppletManager::GetEventBitmap(EventType eType, bool bLarge)
{
	switch (eType) {
	case EVENT_MSG_RECEIVED:
	case EVENT_MSG_SENT:
	case EVENT_IRC_RECEIVED:
	case EVENT_IRC_SENT:
		if (bLarge)
			return m_ahLargeEventBitmaps[0];
		else
			return m_ahEventBitmaps[0];
	case EVENT_PROTO_STATUS:
	case EVENT_PROTO_CONNECTED:
	case EVENT_PROTO_DISCONNECTED:
		if (bLarge)
			return m_ahLargeEventBitmaps[1];
		else
			return m_ahEventBitmaps[1];
	case EVENT_STATUS:
	case EVENT_SIGNED_ON:
	case EVENT_SIGNED_OFF:
		if (bLarge)
			return m_ahLargeEventBitmaps[2];
		else
			return m_ahEventBitmaps[2];
	default:
		if (bLarge)
			return m_ahLargeEventBitmaps[3];
		else
			return m_ahEventBitmaps[3];
	}
}

//************************************************************************
// returns the bitmap for the specified status
//************************************************************************
HBITMAP CAppletManager::GetStatusBitmap(int iStatus)
{
	switch (iStatus) {
	case ID_STATUS_OFFLINE:
		return m_ahStatusBitmaps[0];
	case ID_STATUS_ONLINE:
		return m_ahStatusBitmaps[1];
	case ID_STATUS_NA:
		return m_ahStatusBitmaps[3];
	case ID_STATUS_OCCUPIED:
		return m_ahStatusBitmaps[4];
	case ID_STATUS_DND:
		return m_ahStatusBitmaps[5];
	case ID_STATUS_INVISIBLE:
		return m_ahStatusBitmaps[6];
	case ID_STATUS_FREECHAT:
		return m_ahStatusBitmaps[7];
	case ID_STATUS_AWAY:
	default:
		return m_ahStatusBitmaps[2];
	}
}
//************************************************************************
// returns a formatted timestamp string
//************************************************************************
tstring CAppletManager::GetFormattedTimestamp(tm *tm_time)
{
	time_t now;
	tm tm_now;
	time(&now);
	localtime_s(&tm_now, &now);

	wchar_t buffer[128];

	if (tm_time->tm_mday != tm_now.tm_mday || tm_time->tm_mon != tm_now.tm_mon) {
		if (CConfig::GetBoolSetting(TIMESTAMP_SECONDS))
			wcsftime(buffer, 128, L"[%x %H:%M:%S]", tm_time);
		else
			wcsftime(buffer, 128, L"[%x %H:%M]", tm_time);
	}
	else {
		if (CConfig::GetBoolSetting(TIMESTAMP_SECONDS))
			wcsftime(buffer, 128, L"[%H:%M:%S]", tm_time);
		else
			wcsftime(buffer, 128, L"[%H:%M]", tm_time);
	}

	return toTstring(buffer);
}

//************************************************************************
// called to process the specified event
//************************************************************************
void CAppletManager::HandleEvent(CEvent *pEvent)
{
	TRACE(L"<< Event: %i\n", (int)pEvent->eType);

	// check if the event's timestamp needs to be set
	if (!pEvent->bTime) {
		time_t now;
		time(&now);
		localtime_s(&pEvent->Time, &now);
	}
	// check wether the event needs notification

	// check for protocol filters
	if (pEvent->hContact != NULL && pEvent->eType != EVENT_CONTACT_ADDED) {
		char *szProto = GetContactProto(pEvent->hContact);
		if (szProto == NULL || !CConfig::GetProtocolNotificationFilter(toTstring(szProto)))
			pEvent->bNotification = false;
	}
	pEvent->bLog = pEvent->bNotification;

	if (db_mc_isSub(pEvent->hContact)) {
		pEvent->bLog = false;
		pEvent->bNotification = false;
	}

	// if the applet is in foreground, skip notifications for the chatsession contact
	if (pEvent->hContact && GetLCDConnection()->IsForeground() && pEvent->hContact == m_ChatScreen.GetContact() &&
		(!m_ChatScreen.IsInputActive() || !CConfig::GetBoolSetting(NOTIFY_NO_SKIP_REPLY))) {
		if (pEvent->eType == EVENT_STATUS  && CConfig::GetBoolSetting(NOTIFY_SKIP_STATUS))
			pEvent->bNotification = false;
		if (pEvent->eType == EVENT_SIGNED_ON && CConfig::GetBoolSetting(NOTIFY_SKIP_SIGNON))
			pEvent->bNotification = false;
		if (pEvent->eType == EVENT_SIGNED_OFF && CConfig::GetBoolSetting(NOTIFY_SKIP_SIGNOFF))
			pEvent->bNotification = false;
		if ((pEvent->eType == EVENT_IRC_RECEIVED || pEvent->eType == EVENT_MSG_RECEIVED) && CConfig::GetBoolSetting(NOTIFY_SKIP_MESSAGES))
			pEvent->bNotification = false;
	}

	// send the event to all screens
	m_NotificationScreen.OnEventReceived(pEvent);
	m_ChatScreen.OnEventReceived(pEvent);
	m_EventScreen.OnEventReceived(pEvent);
	m_ContactlistScreen.OnEventReceived(pEvent);

	// activate notification screen if neccessary (and screensaverscreen is not active)
	if (pEvent->bNotification) {
		if (GetActiveScreen() != (CLCDScreen*)&m_NotificationScreen && GetActiveScreen() != (CLCDScreen*)&m_ScreensaverScreen) {
			m_NotificationScreen.SetAlert(true);
			m_NotificationScreen.SetExpiration(CConfig::GetIntSetting(NOTIFY_DURATION) * 1000);
			ActivateScreen(&m_NotificationScreen);

			if (GetLCDConnection()->IsForeground() && CConfig::GetBoolSetting(TRANSITIONS))
				m_pGfx->StartTransition();
		}
	}
}

//************************************************************************
// updates all pending message jobs
//************************************************************************
void CAppletManager::UpdateMessageJobs()
{
	list<SMessageJob*>::iterator iter = m_MessageJobs.begin();
	while (iter != m_MessageJobs.end()) {
		// TODO: Fertigstellen
		if ((*iter)->dwTimestamp + 15 * 1000 < GetTickCount()) {
			CEvent Event;

			Event.eType = EVENT_MESSAGE_ACK;
			Event.hValue = (*iter)->hEvent;
			Event.hContact = (*iter)->hContact;
			Event.iValue = ACKRESULT_FAILED;
			Event.strValue = TranslateString(L"Timeout: No response from contact/server");

			HandleEvent(&Event);

			SMessageJob *pJob = *iter;
			m_MessageJobs.erase(iter);
			free(pJob->pcBuffer);
			delete(pJob);
			break;
		}
		iter++;
	}
}

//************************************************************************
// adds a message job to the list
//************************************************************************
void CAppletManager::AddMessageJob(SMessageJob *pJob)
{
	m_MessageJobs.push_back(pJob);
}

//************************************************************************
// finishes a message job
//************************************************************************
void CAppletManager::FinishMessageJob(SMessageJob *pJob)
{
	list<SMessageJob*>::iterator iter = m_MessageJobs.begin();
	while (iter != m_MessageJobs.end()) {
		if ((*iter) == pJob) {
			char *szProto = GetContactProto(pJob->hContact);
			tstring strProto = toTstring(szProto);
			CIRCConnection *pIRCCon = GetIRCConnection(strProto);

			// Only add the message to the history if the contact isn't an irc chatroom
			if (!(pIRCCon && db_get_b(pJob->hContact, szProto, "ChatRoom", 0) != 0)) {
				// Add the message to the database
				DBEVENTINFO dbei = {};
				dbei.eventType = EVENTTYPE_MESSAGE;
				dbei.flags = DBEF_SENT | DBEF_UTF;
				dbei.szModule = szProto;
				dbei.timestamp = time(NULL);
				// Check if protocoll is valid
				if (dbei.szModule == NULL)
					return;

				dbei.cbBlob = pJob->iBufferSize;
				dbei.pBlob = (PBYTE)pJob->pcBuffer;

				db_event_add(pJob->hContact, &dbei);
			}

			pJob = *iter;
			m_MessageJobs.erase(iter);
			free(pJob->pcBuffer);
			delete(pJob);
			return;
		}
	}
}

//************************************************************************
// cancels a message job
//************************************************************************
void CAppletManager::CancelMessageJob(SMessageJob *pJob)
{
	list<SMessageJob*>::iterator iter = m_MessageJobs.begin();
	while (iter != m_MessageJobs.end()) {
		if ((*iter) == pJob) {
			pJob = *iter;
			m_MessageJobs.erase(iter);
			free(pJob->pcBuffer);
			delete(pJob);
			return;
		}
	}
}

//************************************************************************
// sends typing notifications to the specified contact
//************************************************************************
void CAppletManager::SendTypingNotification(MCONTACT hContact, bool bEnable)
{
	if (!hContact)
		return;

	// Don't send to protocols who don't support typing
	// Don't send to users who are unchecked in the typing notification options
	// Don't send to protocols that are offline
	// Don't send to users who are not visible and
	// Don't send to users who are not on the visible list when you are in invisible mode.
	if (!db_get_b(hContact, "SRMsg", "SupportTyping", db_get_b(NULL, "SRMsg", "DefaultTyping", 1)))
		return;

	char *szProto = GetContactProto(hContact);
	if (!szProto)
		return;

	DWORD typeCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0);
	if (!(typeCaps & PF4_SUPPORTTYPING))
		return;

	DWORD protoStatus = CallProtoService(szProto, PS_GETSTATUS, 0, 0);
	if (protoStatus < ID_STATUS_ONLINE)
		return;

	DWORD protoCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0);
	if (protoCaps & PF1_VISLIST && db_get_w(hContact, szProto, "ApparentMode", 0) == ID_STATUS_OFFLINE)
		return;
	if (protoCaps & PF1_INVISLIST && protoStatus == ID_STATUS_INVISIBLE && db_get_w(hContact, szProto, "ApparentMode", 0) != ID_STATUS_ONLINE)
		return;
	if (db_get_b(hContact, "CList", "NotOnList", 0)
		&& !db_get_b(NULL, "SRMsg", "UnknownTyping", 1))
		return;
	// End user check
	CallService(MS_PROTO_SELFISTYPING, hContact, bEnable ? PROTOTYPE_SELFTYPING_ON : PROTOTYPE_SELFTYPING_OFF);
}

//************************************************************************
// sends a message to the specified contact
//************************************************************************
MEVENT CAppletManager::SendMessageToContact(MCONTACT hContact, tstring strMessage)
{
	tstring strAscii = _A2T(toNarrowString(strMessage).c_str());

	char *szProto = GetContactProto(hContact);
	tstring strProto = toTstring(szProto);

	CIRCConnection *pIRCCon = CAppletManager::GetInstance()->GetIRCConnection(strProto);

	if (pIRCCon && db_get_b(hContact, szProto, "ChatRoom", 0) != 0) {
		ptrW wszNick(db_get_wsa(hContact, szProto, "Nick"));
		if (wszNick == NULL)
			return NULL;

		tstring strID = tstring(wszNick) + L" - " + tstring(_A2T(toNarrowString(pIRCCon->strNetwork).c_str()));
		Chat_SendUserMessage(szProto, strID.c_str(), strAscii.c_str());
		return 0;
	}

	SMessageJob *pJob = new SMessageJob();
	pJob->dwTimestamp = GetTickCount();
	pJob->hContact = hContact;

	char* szMsgUtf = mir_utf8encodeW(strMessage.c_str());

	pJob->iBufferSize = (int)mir_strlen(szMsgUtf) + 1;
	pJob->pcBuffer = (char *)malloc(pJob->iBufferSize);
	pJob->dwFlags = 0;

	memcpy(pJob->pcBuffer, szMsgUtf, pJob->iBufferSize);
	mir_free(szMsgUtf);

	pJob->hEvent = (MEVENT)ProtoChainSend(pJob->hContact, PSS_MESSAGE, 0, (LPARAM)pJob->pcBuffer);
	CAppletManager::GetInstance()->AddMessageJob(pJob);
	return pJob->hEvent;
}

//************************************************************************
// check if a contacts message window is opened
//************************************************************************
bool CAppletManager::IsMessageWindowOpen(MCONTACT hContact)
{
	MessageWindowInputData mwid;
	mwid.cbSize = sizeof(MessageWindowInputData);
	mwid.hContact = hContact;
	mwid.uFlags = MSG_WINDOW_UFLAG_MSG_BOTH;

	MessageWindowData mwd;
	mwd.cbSize = sizeof(MessageWindowData);
	CallService(MS_MSG_GETWINDOWDATA, (WPARAM)&mwid, (LPARAM)&mwd);
	if (mwd.uState & MSG_WINDOW_STATE_EXISTS)
		return true;
	return false;
}

//************************************************************************
// marks the given message as read
//************************************************************************
void CAppletManager::MarkMessageAsRead(MCONTACT hContact, MEVENT hEvent)
{
	db_event_markRead(hContact, hEvent);
	pcli->pfnRemoveEvent(hContact, hEvent);
}

//************************************************************************
// translates the given database event
//************************************************************************
bool CAppletManager::TranslateDBEvent(CEvent *pEvent, WPARAM hContact, LPARAM hdbevent)
{
	// Create struct for dbevent
	DBEVENTINFO dbevent = {};
	dbevent.cbBlob = db_event_getBlobSize(hdbevent);
	if (dbevent.cbBlob == -1)		// hdbevent is invalid
		return false;

	dbevent.pBlob = (PBYTE)malloc(dbevent.cbBlob);
	if (db_event_get(hdbevent, &dbevent) != 0) {
		free(dbevent.pBlob);
		return false;
	}

	pEvent->dwFlags = dbevent.flags;
	pEvent->hContact = hContact;
	pEvent->hValue = hdbevent;

	time_t timestamp = (time_t)dbevent.timestamp;
	localtime_s(&pEvent->Time, &timestamp);
	pEvent->bTime = true;
	/*
	if(dbevent.eventType == EVENTTYPE_MESSAGE && dbevent.flags & DBEF_READ) {
		free(dbevent.pBlob);
		return false;
	}
	*/
	// Skip events from the user except for messages
	if (dbevent.eventType != EVENTTYPE_MESSAGE && (dbevent.flags & DBEF_SENT)) {
		free(dbevent.pBlob);
		return false;
	}

	int msglen = 0;

	tstring strName = CAppletManager::GetContactDisplayname(hContact, true);

	switch (dbevent.eventType) {
	case EVENTTYPE_MESSAGE:
		msglen = (int)mir_strlen((char *)dbevent.pBlob) + 1;
		if (dbevent.flags & DBEF_UTF) {
			pEvent->strValue = Utf8_Decode((char*)dbevent.pBlob);
		}
		else if ((int)dbevent.cbBlob == msglen * 3) {
			pEvent->strValue = (wchar_t *)& dbevent.pBlob[msglen];
		}
		else {
			pEvent->strValue = toTstring((char*)dbevent.pBlob);
		}
		pEvent->eType = (dbevent.flags & DBEF_SENT) ? EVENT_MSG_SENT : EVENT_MSG_RECEIVED;
		if (pEvent->eType == EVENT_MSG_RECEIVED) {
			pEvent->dwFlags = MSG_UNREAD;
			if (CConfig::GetBoolSetting(NOTIFY_MESSAGES))
				pEvent->bNotification = true;
		}

		pEvent->strDescription = strName + L": " + pEvent->strValue;
		pEvent->strSummary = TranslateString(L"New message from %s", strName.c_str());
		break;
	case EVENTTYPE_URL:
		if (CConfig::GetBoolSetting(NOTIFY_URL))
			pEvent->bNotification = true;

		pEvent->eType = EVENT_URL;
		pEvent->strDescription = TranslateString(L"Incoming URL from %s", strName.c_str());
		break;
	case EVENTTYPE_CONTACTS:
		if (CConfig::GetBoolSetting(NOTIFY_CONTACTS))
			pEvent->bNotification = true;

		pEvent->strDescription = TranslateString(L"Incoming contacts from %s", strName.c_str());
		pEvent->eType = EVENT_CONTACTS;
		break;
	case EVENTTYPE_ADDED:
		if (CConfig::GetBoolSetting(NOTIFY_CONTACTS))
			pEvent->bNotification = true;

		pEvent->strDescription = TranslateString(L"You were added by %s", strName.c_str());
		pEvent->eType = EVENT_ADDED;
		break;
	case EVENTTYPE_AUTHREQUEST:
		if (CConfig::GetBoolSetting(NOTIFY_CONTACTS))
			pEvent->bNotification = true;

		pEvent->strDescription = TranslateString(L"Incoming Authrequest!");
		pEvent->eType = EVENT_AUTHREQUEST;
		break;
	case EVENTTYPE_FILE:
		if (CConfig::GetBoolSetting(NOTIFY_FILE))
			pEvent->bNotification = true;

		pEvent->strDescription = TranslateString(L"Incoming file from %s", strName.c_str());
		pEvent->eType = EVENT_FILE;
		break;
	default:
		return false;
		break;
	}

	if (CConfig::GetBoolSetting(NOTIFY_SHOWPROTO)) {
		char *szProto = GetContactProto(pEvent->hContact);
		pEvent->strDescription = L"(" + toTstring(szProto) + L") " + pEvent->strDescription;
	}

	// Clean up
	free(dbevent.pBlob);
	return true;
}

//************************************************************************
// removes IRC formatting tags from the string
//************************************************************************
tstring CAppletManager::StripIRCFormatting(tstring strText)
{
	tstring::size_type start = 0, i = 0;
	tstring strEntity = L"";
	tstring strReplace = L"";

	while (i < strText.length()) {
		start = strText.find(L"%", i);
		if (start != string::npos && start < strText.length() - 1) {
			strEntity = strText[start + 1];
			if (strEntity == L"%") {
				strText.replace(start, 2, L"%");
				i = start + 1;
			}
			/*
			else if(strEntity == L"b" || strEntity == L"B" ||
				strEntity == L"i" || strEntity == L"I" ||
				strEntity ==L"u" || strEntity == L"U" ||
				strEntity == L"C" ||strEntity == L"F")
			{
				strText.erase(start,2);
				i = start;
			}
			*/
			else if (strEntity == L"c" || strEntity == L"f") {
				strText.erase(start, 4);
				i = start;
			}
			else {
				strText.erase(start, 2);
				i = start;
			}
		}
		else
			break;
	}

	return strText;
}

//************************************************************************
// returns the IRC connection class for the specified protocol
//************************************************************************
CIRCConnection *CAppletManager::GetIRCConnection(tstring strProtocol)
{
	vector<CIRCConnection*>::iterator iter = m_vIRCConnections.begin();
	while (iter != m_vIRCConnections.end()) {
		if ((*iter)->strProtocol == strProtocol)
			return *iter;
		iter++;
	}
	return NULL;
}

//************************************************************************
// creates the IRC connection class for the specified protocol
//************************************************************************
CIRCConnection *CAppletManager::CreateIRCConnection(tstring strProtocol)
{
	CIRCConnection *pIRCCon = new CIRCConnection();
	pIRCCon->strProtocol = strProtocol;
	pIRCCon->strNetwork = L"";

	m_vIRCConnections.push_back(pIRCCon);

	return pIRCCon;
}

//************************************************************************
// returns the history class for the specified IRC channel
//************************************************************************
CIRCHistory *CAppletManager::GetIRCHistory(MCONTACT hContact)
{
	list<CIRCHistory*>::iterator iter = m_LIRCHistorys.begin();
	while (iter != m_LIRCHistorys.end()) {
		if ((*iter)->hContact == hContact)
			return *iter;
		iter++;
	}
	return NULL;
}

CIRCHistory *CAppletManager::GetIRCHistoryByName(tstring strProtocol, tstring strChannel)
{
	list<CIRCHistory*>::iterator iter = m_LIRCHistorys.begin();
	while (iter != m_LIRCHistorys.end()) {
		if ((*iter)->strChannel == strChannel && (*iter)->strProtocol == strProtocol)
			return *iter;
		iter++;
	}
	return NULL;
}

//************************************************************************
// deletes the history class for the specified IRC channel
//************************************************************************
void CAppletManager::DeleteIRCHistory(MCONTACT hContact)
{
	list<CIRCHistory*>::iterator iter = m_LIRCHistorys.begin();
	while (iter != m_LIRCHistorys.end()) {
		if ((*iter)->hContact == hContact) {
			CIRCHistory *pHistory = *iter;
			pHistory->LMessages.clear();
			pHistory->LUsers.clear();

			m_LIRCHistorys.erase(iter);

			delete pHistory;

			return;
		}
		iter++;
	}
}

//************************************************************************
// creates a history class for the specified IRC channel
//************************************************************************
CIRCHistory *CAppletManager::CreateIRCHistory(MCONTACT hContact, tstring strChannel)
{
	char *szProto = GetContactProto(hContact);
	if (!szProto)
		return NULL;

	CIRCHistory *pHistory = GetIRCHistoryByName(toTstring(szProto), strChannel);
	if (pHistory) {
		pHistory->hContact = hContact;
		return pHistory;
	}

	pHistory = new CIRCHistory();
	pHistory->hContact = hContact;
	pHistory->strChannel = strChannel;
	pHistory->strProtocol = toTstring(szProto);

	m_LIRCHistorys.push_back(pHistory);

	return pHistory;
}

CIRCHistory *CAppletManager::CreateIRCHistoryByName(tstring strProtocol, tstring strChannel)
{
	CIRCHistory *pHistory = GetIRCHistoryByName(strProtocol, strChannel);
	if (pHistory)
		return pHistory;

	pHistory = new CIRCHistory();
	pHistory->hContact = NULL;
	pHistory->strChannel = strChannel;
	pHistory->strProtocol = strProtocol;

	m_LIRCHistorys.push_back(pHistory);

	return pHistory;
}

//########################################################################
// hook functions
//########################################################################

//************************************************************************
// inbound chat event hook function
//************************************************************************
int CAppletManager::HookChatInbound(WPARAM, LPARAM lParam)
{
	GCEVENT *gce = (GCEVENT*)lParam;
	GCDEST *gcd;

	if (gce == NULL || (gcd = gce->pDest) == NULL) {
		TRACE(L"<< [%s] skipping invalid event\n");
		return 0;
	}

	TRACE(L"<< [%s:%s] event %04X\n", toTstring(gcd->pszModule).c_str(), gcd->ptszID, gcd->iType);

	// get the matching irc connection entry
	CIRCConnection *pIRCCon = CAppletManager::GetInstance()->GetIRCConnection(toTstring(gcd->pszModule));
	if (!pIRCCon) {
		TRACE(L"<< [%s] connection not found, skipping event\n", toTstring(gcd->pszModule).c_str());
		return 0;
	}

	// fetch the network name
	// if (gcd->iType == GC_EVENT_CHANGESESSIONAME) {
	// 	if (gcd->ptszID && !mir_wstrcmpi(gcd->ptszID, L"Network log")) {
	// 		pIRCCon->strNetwork = toTstring(gce->ptszText);
	// 		TRACE(L"\t Found network identifier: %s\n", pIRCCon->strNetwork.c_str());
	// 		return 0;
	// 	}
	// }

	CEvent Event;
	if (gce->bIsMe)
		Event.eType = EVENT_IRC_SENT;
	else
		Event.eType = EVENT_IRC_RECEIVED;
	Event.iValue = gcd->iType;
	Event.hValue = lParam;

	CIRCHistory *pHistory = NULL;
	if (gcd->ptszID) {
		tstring strChannel = toTstring(gcd->ptszID);
		tstring::size_type pos = strChannel.find('-');
		if (pos != tstring::npos)
			strChannel = strChannel.substr(0, pos - 1);
		else {
			if (mir_wstrcmpi(gcd->ptszID, L"Network log"))
				TRACE(L"\t WARNING: ignoring unknown event!\n");
			return 0;
		}
		pHistory = CAppletManager::GetInstance()->GetIRCHistoryByName(pIRCCon->strProtocol, strChannel);
		if (!pHistory) {
			if (gcd->iType == GC_EVENT_JOIN) {
				pHistory = CAppletManager::GetInstance()->CreateIRCHistoryByName(pIRCCon->strProtocol, strChannel);
				if (pHistory)
					pHistory->LUsers.push_back(toTstring(gce->ptszNick));
			}
			return 0;
		}
		Event.hContact = pHistory->hContact;
	}
	else if (gcd->iType != GC_EVENT_INFORMATION) {
		TRACE(L"\t WARNING: ignoring unknown event!\n");
		return 0;
	}
	else
		Event.hContact = NULL;

	// Ignore events from hidden chatrooms, except for join events
	if (gcd->ptszID != NULL && db_get_b(Event.hContact, "CList", "Hidden", 0)) {
		if (gcd->iType == GC_EVENT_JOIN && pHistory)
			pHistory->LUsers.push_back(toTstring(gce->ptszNick));

		TRACE(L"\t Chatroom is hidden, skipping event!\n");
		return 0;
	}

	tstring strText = StripIRCFormatting(toTstring(gce->ptszText));
	tstring strNick = toTstring(gce->ptszNick);
	tstring strStatus = toTstring(gce->ptszStatus);

	if (CConfig::GetBoolSetting(NOTIFY_NICKCUTOFF) && strNick.length() > (tstring::size_type)CConfig::GetIntSetting(NOTIFY_NICKCUTOFF_OFFSET))
		strNick = strNick.erase(CConfig::GetIntSetting(NOTIFY_NICKCUTOFF_OFFSET)) + L"...";

	TRACE(L"\t Handling event...\t");

	switch (gcd->iType) {
	case GC_EVENT_INFORMATION:
		if (CConfig::GetBoolSetting(NOTIFY_IRC_CHANNEL))
			Event.bNotification = true;

		if (strText.find(L"CTCP") == 0)
			Event.strValue = L"--> " + strText;
		else
			Event.strValue = strText;

		break;
	case GC_EVENT_ACTION:
		if (CConfig::GetBoolSetting(NOTIFY_IRC_EMOTES))
			Event.bNotification = true;
		Event.strValue = strNick + L" " + strText;
		break;
	case GC_EVENT_MESSAGE:
		if (CConfig::GetBoolSetting(NOTIFY_IRC_MESSAGES))
			Event.bNotification = true;
		Event.strValue = strNick + L": " + strText;
		break;
	case GC_EVENT_JOIN:
		// Add the user to the list
		pHistory->LUsers.push_back(toTstring(gce->ptszNick));

		if (CConfig::GetBoolSetting(NOTIFY_IRC_USERS))
			Event.bNotification = true;
		// Skip join event for user
		if (gce->bIsMe)
			return 0;
		Event.strValue = TranslateString(L"%s has joined the channel", strNick.c_str());

		break;
	case GC_EVENT_PART:
		{
			if (CConfig::GetBoolSetting(NOTIFY_IRC_USERS))
				Event.bNotification = true;
			tstring strFullNick = toTstring(gce->ptszNick);
			Event.strValue = TranslateString(strText.empty() ? L"%s has left" : L"%s has left: %s", strNick.c_str(), strText.c_str());
			if (pHistory) {
				// Remove the user from the list
				list<tstring>::iterator iter = pHistory->LUsers.begin();
				while (iter != pHistory->LUsers.end()) {
					if ((*iter) == strFullNick) {
						pHistory->LUsers.erase(iter);
						break;
					}
					iter++;
				}
			}
			break;
		}
	case GC_EVENT_QUIT:
		if (CConfig::GetBoolSetting(NOTIFY_IRC_USERS))
			Event.bNotification = true;
		Event.strValue = TranslateString(strText.empty() ? L"%s has disconnected" : L"%s has disconnected: %s", strNick.c_str(), strText.c_str());
		break;
	case GC_EVENT_KICK:
		if (CConfig::GetBoolSetting(NOTIFY_IRC_USERS))
			Event.bNotification = true;
		Event.strValue = TranslateString(L"%s has kicked %s: %s", strStatus.c_str(), strNick.c_str(), strText.c_str());
		break;
	case GC_EVENT_NICK:
		{
			if (CConfig::GetBoolSetting(NOTIFY_IRC_USERS))
				Event.bNotification = true;
			tstring strFullNick = toTstring(gce->ptszNick);

			if (CConfig::GetBoolSetting(NOTIFY_NICKCUTOFF) && strText.length() > (tstring::size_type)CConfig::GetIntSetting(NOTIFY_NICKCUTOFF_OFFSET))
				strText = strText.erase(CConfig::GetIntSetting(NOTIFY_NICKCUTOFF_OFFSET)) + L"...";

			Event.strValue = TranslateString(L"%s is now known as %s", strNick.c_str(), strText.c_str());
			if (pHistory) {
				// change the nick in the userlist
				list<tstring>::iterator iter = pHistory->LUsers.begin();
				while (iter != pHistory->LUsers.end()) {
					if ((*iter) == strFullNick)
						(*iter) = strText;
					iter++;
				}
			}
			break;
		}
	case GC_EVENT_NOTICE:
		if (CConfig::GetBoolSetting(NOTIFY_IRC_NOTICES))
			Event.bNotification = true;
		Event.strValue = TranslateString(L"Notice from %s: %s", strNick.c_str(), strText.c_str());
		break;
	case GC_EVENT_TOPIC:
		if (CConfig::GetBoolSetting(NOTIFY_IRC_CHANNEL))
			Event.bNotification = true;
		Event.strValue = TranslateString(L"Topic is now '%s' (set by %s)", strText.c_str(), strNick.c_str());
		break;
	case GC_EVENT_ADDSTATUS:
		{
			if (CConfig::GetBoolSetting(NOTIFY_IRC_STATUS))
				Event.bNotification = true;
			tstring strNick2 = toTstring(gce->ptszStatus);
			if (CConfig::GetBoolSetting(NOTIFY_NICKCUTOFF) && strNick2.length() > (tstring::size_type)CConfig::GetIntSetting(NOTIFY_NICKCUTOFF_OFFSET))
				strNick2 = strNick2.erase(CConfig::GetIntSetting(NOTIFY_NICKCUTOFF_OFFSET)) + L"...";

			Event.strValue = TranslateString(L"%s enables '%s' for %s", strText.c_str(), strNick2.c_str(), strNick.c_str());
			break;
		}
	case GC_EVENT_REMOVESTATUS:
		{
			if (CConfig::GetBoolSetting(NOTIFY_IRC_STATUS))
				Event.bNotification = true;
			tstring strNick2 = toTstring(gce->ptszStatus);
			if (CConfig::GetBoolSetting(NOTIFY_NICKCUTOFF) && strNick2.length() > (tstring::size_type)CConfig::GetIntSetting(NOTIFY_NICKCUTOFF_OFFSET))
				strNick2 = strNick2.erase(CConfig::GetIntSetting(NOTIFY_NICKCUTOFF_OFFSET)) + L"...";

			Event.strValue = TranslateString(L"%s disables '%s' for %s", strText.c_str(), strNick2.c_str(), strNick.c_str());
			break;
		}
	default:
		TRACE(L"OK!\n");
		return 0;
	}
	if (gce->bIsMe || gcd->ptszID == NULL)
		Event.bNotification = false;

	// set the event's timestamp
	Event.bTime = true;
	time_t now;
	time(&now);
	localtime_s(&Event.Time, &now);

	SIRCMessage IRCMsg;
	IRCMsg.bIsMe = (gce->bIsMe != 0);
	IRCMsg.strMessage = Event.strValue;
	IRCMsg.Time = Event.Time;

	if (pHistory) {
		pHistory->LMessages.push_back(IRCMsg);

		// Limit the size to the session logsize
		if (pHistory->LMessages.size() > CConfig::GetIntSetting(SESSION_LOGSIZE))
			pHistory->LMessages.pop_front();
	}
	else if (gce->ptszNick && gcd->iType == GC_EVENT_QUIT) {
		strNick = toTstring(gce->ptszNick);

		if (!CAppletManager::GetInstance()->m_LIRCHistorys.empty()) {
			list<CIRCHistory*>::iterator iter = CAppletManager::GetInstance()->m_LIRCHistorys.begin();
			list<tstring>::iterator nickiter;
			while (iter != CAppletManager::GetInstance()->m_LIRCHistorys.end()) {
				nickiter = (*iter)->LUsers.begin();
				while (nickiter != (*iter)->LUsers.end()) {
					if ((*nickiter) == strNick) {
						(*iter)->LMessages.push_back(IRCMsg);
						// Limit the size to the session logsize
						if ((*iter)->LMessages.size() > CConfig::GetIntSetting(SESSION_LOGSIZE))
							(*iter)->LMessages.pop_front();

						(*iter)->LUsers.erase(nickiter);

						Event.hContact = (*iter)->hContact;
						tstring strName = CAppletManager::GetContactDisplayname((*iter)->hContact, true);
						Event.strDescription = strName + L" - " + Event.strValue;
						Event.strSummary = L"(" + toTstring(gcd->pszModule) + L") " + strName;
						CAppletManager::GetInstance()->HandleEvent(&Event);
						break;
					}
					nickiter++;
				}
				iter++;
			}
		}
		TRACE(L"OK!\n");
		return 0;
	}
	else if (gcd->ptszID != NULL) {
		TRACE(L"OK!\n");
		return 0;
	}

	if (pHistory) {
		tstring strChannel = pHistory->strChannel;
		if (CConfig::GetBoolSetting(NOTIFY_CHANNELCUTOFF) && strChannel.length() > CConfig::GetIntSetting(NOTIFY_CHANNELCUTOFF_OFFSET)) {
			strChannel = strChannel.erase(CConfig::GetIntSetting(NOTIFY_CHANNELCUTOFF_OFFSET)) + L"...";
		}
		Event.strDescription = strChannel + L" - " + Event.strValue;
		Event.strSummary = L"(" + toTstring(gcd->pszModule) + L") " + pHistory->strChannel;
	}
	else
		Event.strDescription = Event.strValue;

	TRACE(L"OK!\n");

	CAppletManager::GetInstance()->HandleEvent(&Event);

	return 0;
}

//************************************************************************
// message window event hook function
//************************************************************************
int CAppletManager::HookMessageWindowEvent(WPARAM, LPARAM lParam)
{
	MessageWindowEventData *mwed = (MessageWindowEventData*)lParam;
	CEvent Event;

	Event.eType = EVENT_MESSAGEWINDOW;
	Event.hContact = mwed->hContact;
	Event.iValue = mwed->uType;

	CAppletManager::GetInstance()->HandleEvent(&Event);

	return 0;
}


//************************************************************************
// contact typing notification hook function
//************************************************************************
int CAppletManager::HookContactIsTyping(WPARAM wParam, LPARAM lParam)
{
	MCONTACT hContact = wParam;
	int iState = (int)lParam;

	CEvent Event;

	Event.eType = EVENT_TYPING_NOTIFICATION;
	Event.hContact = hContact;
	Event.iValue = iState;

	CAppletManager::GetInstance()->HandleEvent(&Event);
	return 0;
}

//************************************************************************
// new event hook function
//************************************************************************
int CAppletManager::HookEventAdded(WPARAM wParam, LPARAM lParam)
{
	CEvent Event;

	if (CAppletManager::TranslateDBEvent(&Event, wParam, lParam))
		CAppletManager::GetInstance()->HandleEvent(&Event);

	return 0;
}

//************************************************************************
// contact status change hook function
//************************************************************************
int CAppletManager::HookStatusChanged(WPARAM wParam, LPARAM lParam)
{
	DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING*)lParam;

	if ((wParam == 0) || (strcmp(cws->szSetting, "Status") != NULL))
		return 0;


	// Prepare message and append to queue
	CEvent Event;
	Event.hContact = wParam;
	int iStatus = cws->value.wVal;
	Event.iValue = iStatus;

	int iOldStatus = CAppletManager::GetInstance()->m_ContactlistScreen.GetContactStatus(Event.hContact);

	char *szProto = GetContactProto(Event.hContact);
	tstring strProto = toTstring(szProto);

	CProtocolData *pProtocolData = CAppletManager::GetInstance()->GetProtocolData(toTstring(szProto));
	if (pProtocolData == NULL)
		return false;

	// Fetch the contacts name
	tstring strName = CAppletManager::GetContactDisplayname(Event.hContact, true);

	// Get status String
	Event.strValue = toTstring(pcli->pfnGetStatusModeDescription(iStatus, 0));

	// check if this is an irc protocol
	CIRCConnection *pIRCCon = CAppletManager::GetInstance()->GetIRCConnection(strProto);

	// Contact signed on
	if (iOldStatus == ID_STATUS_OFFLINE && iStatus != ID_STATUS_OFFLINE) {
		if (CConfig::GetBoolSetting(NOTIFY_SIGNOFF))
			Event.bNotification = true;

		Event.eType = EVENT_SIGNED_ON;
		if (pIRCCon && db_get_b(Event.hContact, szProto, "ChatRoom", 0) != 0) {
			Event.strDescription = TranslateString(L"Joined %s", strName.c_str());

			DBVARIANT dbv;
			if (db_get_ws(Event.hContact, szProto, "Nick", &dbv))
				return 0;
			CAppletManager::GetInstance()->CreateIRCHistory(Event.hContact, dbv.ptszVal);
			db_free(&dbv);
		}
		else
			Event.strDescription = TranslateString(L"%s signed on (%s)", strName.c_str(), Event.strValue.c_str());
	}
	// Contact signed off
	else if (iStatus == ID_STATUS_OFFLINE && iOldStatus != ID_STATUS_OFFLINE) {
		if (CConfig::GetBoolSetting(NOTIFY_SIGNON))
			Event.bNotification = true;

		Event.eType = EVENT_SIGNED_OFF;
		if (pIRCCon &&  db_get_b(Event.hContact, szProto, "ChatRoom", 0) != 0) {
			Event.strDescription = TranslateString(L"Left %s", strName.c_str());
			// delete IRC-Channel history
			CAppletManager::GetInstance()->DeleteIRCHistory(Event.hContact);
		}
		else
			Event.strDescription = TranslateString(L"%s signed off", strName.c_str());
	}
	// Contact changed status
	else if (iStatus != iOldStatus) {
		if (CConfig::GetBoolSetting(NOTIFY_STATUS))
			Event.bNotification = true;

		Event.eType = EVENT_STATUS;
		Event.strDescription = TranslateString(L"%s is now %s", strName.c_str(), Event.strValue.c_str());
	}
	// ignore remaining events
	else
		return 0;

	if (CConfig::GetBoolSetting(NOTIFY_SHOWPROTO))
		Event.strDescription = L"(" + strProto + L") " + Event.strDescription;



	Event.strSummary = TranslateString(L"Contactlist event");

	// Block notifications after connecting/disconnecting
	if (pProtocolData->iStatus == ID_STATUS_OFFLINE || (DWORD)pProtocolData->lTimeStamp + PROTOCOL_NOTIFY_DELAY > GetTickCount())
		Event.bNotification = false;

	//CAppletManager::GetInstance()->ActivateNotificationScreen(&Event);
	CAppletManager::GetInstance()->HandleEvent(&Event);

	return 0;
}

//************************************************************************
// protocoll ack hook function
//************************************************************************
int CAppletManager::HookProtoAck(WPARAM, LPARAM lParam)
{
	ACKDATA *pAck = (ACKDATA *)lParam;

	if (lParam == 0)
		return 0;

	// Prepare message and append to queue
	CEvent Event;

	// Message job handling
	if (pAck->type == ACKTYPE_MESSAGE) {
		list<SMessageJob*>::iterator iter = CAppletManager::GetInstance()->m_MessageJobs.begin();
		while (iter != CAppletManager::GetInstance()->m_MessageJobs.end()) {
			if ((*iter)->hEvent == (UINT_PTR)pAck->hProcess && (*iter)->hContact == pAck->hContact) {
				Event.eType = EVENT_MESSAGE_ACK;
				Event.hValue = (UINT_PTR)pAck->hProcess;
				Event.hContact = pAck->hContact;
				Event.iValue = pAck->result;
				if (pAck->lParam != 0)
					Event.strValue = toTstring((char*)pAck->lParam);
				else
					Event.strValue = L"";

				if (Event.iValue == ACKRESULT_SUCCESS)
					CAppletManager::GetInstance()->FinishMessageJob((*iter));
				else
					CAppletManager::GetInstance()->CancelMessageJob((*iter));

				CAppletManager::GetInstance()->HandleEvent(&Event);

				return 0;
			}
			iter++;
		}
	}
	// protocol status changes
	else if (pAck->type == ACKTYPE_STATUS && pAck->result == ACKRESULT_SUCCESS) {
		int iOldStatus = (INT_PTR)pAck->hProcess;
		int iNewStatus = pAck->lParam;

		tstring strProto = toTstring(pAck->szModule);

		// ignore metacontacts status changes
		if (toLower(strProto) == L"metacontacts")
			return 0;

		CProtocolData *pProtoData = CAppletManager::GetInstance()->GetProtocolData(strProto);
		if (pProtoData == NULL)
			return 0;

		// Skip connecting status
		if (iNewStatus == ID_STATUS_CONNECTING)
			return 0;

		if (iNewStatus == ID_STATUS_OFFLINE) {
			if (CConfig::GetBoolSetting(NOTIFY_PROTO_SIGNOFF))
				Event.bNotification = true;
			Event.eType = EVENT_PROTO_DISCONNECTED;
		}
		else if (iNewStatus != ID_STATUS_OFFLINE && iOldStatus == ID_STATUS_CONNECTING) {
			if (CConfig::GetBoolSetting(NOTIFY_PROTO_SIGNON))
				Event.bNotification = true;
			Event.eType = EVENT_PROTO_CONNECTED;
		}
		else {
			if (CConfig::GetBoolSetting(NOTIFY_PROTO_STATUS))
				Event.bNotification = true;
			Event.eType = EVENT_PROTO_STATUS;
		}

		pProtoData->iStatus = iNewStatus;

		Event.iValue = iNewStatus;
		Event.strValue = strProto;

		// set the event description / summary
		tstring strStatus = toTstring(pcli->pfnGetStatusModeDescription(iNewStatus, 0));
		Event.strDescription = L"(" + Event.strValue + L") " + TranslateString(L"You are now %s", strStatus.c_str());
		Event.strSummary = TranslateString(L"Protocol status change");

		if (Event.eType != EVENT_PROTO_STATUS)
			pProtoData->lTimeStamp = GetTickCount();

		CAppletManager::GetInstance()->HandleEvent(&Event);
		//CAppletManager::GetInstance()->ActivateNotificationScreen(&Event);
	}

	return 0;
}

//************************************************************************
// contact added hook function
//************************************************************************
int CAppletManager::HookContactAdded(WPARAM wParam, LPARAM)
{
	CEvent Event;
	Event.eType = EVENT_CONTACT_ADDED;
	Event.hContact = wParam;

	CAppletManager::GetInstance()->HandleEvent(&Event);
	return 0;
}

//************************************************************************
// contact deleted hook function
//************************************************************************
int CAppletManager::HookContactDeleted(WPARAM wParam, LPARAM)
{
	CEvent Event;
	Event.eType = EVENT_CONTACT_DELETED;
	Event.hContact = wParam;
	Event.bNotification = CConfig::GetBoolSetting(NOTIFY_CONTACTS);
	Event.bLog = Event.bNotification;

	tstring strName = CAppletManager::GetContactDisplayname(Event.hContact, true);

	Event.strDescription = TranslateString(L"%s was deleted from contactlist!", strName.c_str());

	CAppletManager::GetInstance()->HandleEvent(&Event);
	return 0;
}

//************************************************************************
// setting changed hook function
//************************************************************************
int CAppletManager::HookSettingChanged(WPARAM hContact, LPARAM lParam)
{
	DBCONTACTWRITESETTING *dbcws = (DBCONTACTWRITESETTING*)lParam;

	CEvent Event;
	Event.hContact = hContact;

	if (!strcmp(dbcws->szSetting, "Nick") || !strcmp(dbcws->szSetting, "MyHandle")) {
		DBVARIANT dbv = { 0 };
		// if the protocol nick has changed, check if a custom handle is set
		if (!strcmp(dbcws->szSetting, "Nick")) {
			if (!db_get_ws(Event.hContact, "CList", "MyHandle", &dbv)) {
				// handle found, ignore this event
				if (dbv.pszVal && mir_strlen(dbv.pszVal) > 0)
					return 0;
			}
			db_free(&dbv);
		}

		Event.eType = EVENT_CONTACT_NICK;
		if (dbcws->value.type != DBVT_DELETED && dbcws->value.pszVal && mir_strlen(dbcws->value.pszVal) > 0) {
			if (dbcws->value.type == DBVT_UTF8)
				Event.strValue = Utf8_Decode(dbcws->value.pszVal);
			else
				Event.strValue = toTstring(dbcws->value.pszVal);
		}
		else {
			char *szProto = GetContactProto(Event.hContact);
			if (db_get_ws(Event.hContact, szProto, "Nick", &dbv))
				return 0;
			Event.strValue = dbv.ptszVal;
			db_free(&dbv);
		}
	}
	else if (!strcmp(dbcws->szModule, "CList")) {
		if (!strcmp(dbcws->szSetting, "Hidden")) {
			Event.eType = EVENT_CONTACT_HIDDEN;
			Event.iValue = db_get_b(hContact, "CList", "Hidden", 0);
		}
		else if (!strcmp(dbcws->szSetting, "Group")) {
			Event.eType = EVENT_CONTACT_GROUP;
			DBVARIANT dbv;
			int res = db_get_ws(hContact, "CList", "Group", &dbv);
			if (!res)
				Event.strValue = dbv.ptszVal;
			db_free(&dbv);
		}
		else return 0;
	}
	else return 0;

	CAppletManager::GetInstance()->HandleEvent(&Event);
	return 0;
}