/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2009 Miranda ICQ/IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.

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 "commonheaders.h"
#include "clc.h"

#define TOOLTIP_TOLERANCE 5

extern HIMAGELIST hCListImages;
extern BOOL(WINAPI * MySetProcessWorkingSetSize) (HANDLE, SIZE_T, SIZE_T);

int GetAverageMode(int* pNetProtoCount = NULL);

static UINT WM_TASKBARCREATED;
static UINT WM_TASKBARBUTTONCREATED;
static BOOL mToolTipTrayTips = FALSE;
static UINT_PTR cycleTimerId = 0;
static int cycleStep = 0;
static UINT_PTR RefreshTimerId=0;   /////by FYR
static CRITICAL_SECTION trayLockCS;

// don't move to win2k.h, need new and old versions to work on 9x/2000/XP
#define NIF_STATE       0x00000008
#define NIF_INFO        0x00000010

#define lock cli.pfnLockTray()
#define ulock cli.pfnUnlockTray()

#define initcheck if(!fTrayInited) return

static BOOL fTrayInited=FALSE;

static TCHAR* sttGetXStatus( const char* szProto )
{
	TCHAR* result = NULL;

	if ( CallProtoService( szProto, PS_GETSTATUS, 0, 0 ) > ID_STATUS_OFFLINE ) {
		char str[MAXMODULELABELLENGTH];
		mir_snprintf( str, sizeof(str), "%s/GetXStatus", szProto );
		if ( ServiceExists( str )) {
			char* dbTitle = "XStatusName";
			char* dbTitle2 = NULL;
			int xstatus = CallProtoService( szProto, "/GetXStatus", ( WPARAM )&dbTitle, ( LPARAM )&dbTitle2 );
			if ( dbTitle && xstatus ) {
				DBVARIANT dbv={0};
				if ( !DBGetContactSettingTString(NULL, szProto, dbTitle, &dbv )) {
					if ( dbv.ptszVal[0] != 0 )
						result = mir_tstrdup(dbv.ptszVal);
					DBFreeVariant(&dbv);
	}	}	}	}

	return result;
}

static HICON lastTaskBarIcon;
static void SetTaskBarIcon(const HICON hIcon, const TCHAR *szNewTip)
{
	if (pTaskbarInterface)
	{
		wchar_t *szTip = mir_t2u(szNewTip);
		pTaskbarInterface->SetOverlayIcon(cli.hwndContactList, hIcon, szTip);
		mir_free(szTip);
		lastTaskBarIcon = hIcon;
	}
}

TCHAR* fnTrayIconMakeTooltip( const TCHAR *szPrefix, const char *szProto )
{
	TCHAR *szStatus, *szSeparator;
	TCHAR *ProtoXStatus=NULL;
	int t;
	PROTOACCOUNT* pa;
	initcheck NULL;
	lock;
	if ( !mToolTipTrayTips )
		szSeparator = (IsWinVerMEPlus()) ? szSeparator = _T("\n") : _T(" | ");
	else
		szSeparator = _T("\n");

	if (szProto == NULL) {
		if (accounts.getCount() == 0) {
			ulock;
			return NULL;
		}
		if (accounts.getCount() == 1) {
			ulock;
			return cli.pfnTrayIconMakeTooltip(szPrefix, accounts[0]->szModuleName);
		}

		if (szPrefix && szPrefix[0]) {
			lstrcpyn(cli.szTip, szPrefix, MAX_TIP_SIZE);
			if (!DBGetContactSettingByte(NULL, "CList", "AlwaysStatus", SETTING_ALWAYSSTATUS_DEFAULT))
			{ ulock; return cli.szTip; }
		}
		else cli.szTip[0] = '\0';
		cli.szTip[ MAX_TIP_SIZE-1 ] = '\0';

		for ( t = 0; t < accounts.getCount(); t++ ) {
			int i = cli.pfnGetAccountIndexByPos( t );
			if ( i == -1 )
				continue;

			pa = accounts[i];
			if ( !cli.pfnGetProtocolVisibility( pa->szModuleName ))
				continue;

			szStatus = cli.pfnGetStatusModeDescription( CallProtoService( pa->szModuleName, PS_GETSTATUS, 0, 0), 0);
			if ( !szStatus )
				continue;

			ProtoXStatus = sttGetXStatus( pa->szModuleName );

			if ( mToolTipTrayTips ) {
				TCHAR tipline[256];
				mir_sntprintf(tipline, SIZEOF(tipline), _T("<b>%-12.12s</b>\t%s"), pa->tszAccountName, szStatus);
				if ( cli.szTip[0] )
					_tcsncat(cli.szTip, szSeparator, MAX_TIP_SIZE - _tcslen(cli.szTip));
				_tcsncat(cli.szTip, tipline, MAX_TIP_SIZE - _tcslen(cli.szTip));
				if (ProtoXStatus) {
					mir_sntprintf(tipline, SIZEOF(tipline), _T("%-24.24s\n"), ProtoXStatus);
					if ( cli.szTip[0] )
						_tcsncat(cli.szTip, szSeparator, MAX_TIP_SIZE - _tcslen(cli.szTip));
					_tcsncat(cli.szTip, tipline, MAX_TIP_SIZE - _tcslen(cli.szTip));
				}
			}
			else {
				if (cli.szTip[0])
					_tcsncat(cli.szTip, szSeparator, MAX_TIP_SIZE - _tcslen(cli.szTip));

				_tcsncat(cli.szTip, pa->tszAccountName, MAX_TIP_SIZE - _tcslen(cli.szTip));
				_tcsncat(cli.szTip, _T(" "), MAX_TIP_SIZE - _tcslen(cli.szTip));
				_tcsncat(cli.szTip, szStatus, MAX_TIP_SIZE - _tcslen(cli.szTip));
			}
			mir_free( ProtoXStatus );
		}	
	}
	else {
		if (( pa = Proto_GetAccount( szProto )) != NULL ) {
			ProtoXStatus = sttGetXStatus( szProto );
			szStatus = cli.pfnGetStatusModeDescription(CallProtoService(szProto, PS_GETSTATUS, 0, 0), 0);
			if ( szPrefix && szPrefix[0] ) {
				if ( DBGetContactSettingByte( NULL, "CList", "AlwaysStatus", SETTING_ALWAYSSTATUS_DEFAULT )) {
					if ( mToolTipTrayTips ) {
						if ( ProtoXStatus )
							mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s%s<b>%-12.12s</b>\t%s%s%-24.24s"), szPrefix, szSeparator, pa->tszAccountName, szStatus,szSeparator,ProtoXStatus);
						else
							mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s%s<b>%-12.12s</b>\t%s"), szPrefix, szSeparator, pa->tszAccountName, szStatus);
					}
					else mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s%s%s %s"), szPrefix, szSeparator, pa->tszAccountName, szStatus);
				}
				else lstrcpyn(cli.szTip, szPrefix, MAX_TIP_SIZE);
			}
			else {
				if ( mToolTipTrayTips ) {
					if ( ProtoXStatus )
						mir_sntprintf( cli.szTip, MAX_TIP_SIZE, _T("<b>%-12.12s</b>\t%s\n%-24.24s"), pa->tszAccountName, szStatus,ProtoXStatus);
					else
						mir_sntprintf( cli.szTip, MAX_TIP_SIZE, _T("<b>%-12.12s</b>\t%s"), pa->tszAccountName, szStatus);
				}
				else mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s %s"), pa->tszAccountName, szStatus);
			}
			mir_free(ProtoXStatus);
	}	}

	ulock;
	return cli.szTip;
}

int fnTrayIconAdd(HWND hwnd, const char *szProto, const char *szIconProto, int status)
{
	NOTIFYICONDATA nid = { 0 };
	int i;
	initcheck 0;
	lock;
	for (i = 0; i < cli.trayIconCount; i++)
		if (cli.trayIcon[i].id == 0)
			break;

	cli.trayIcon[i].id = TRAYICON_ID_BASE + i;
	cli.trayIcon[i].szProto = (char *) szProto;
	cli.trayIcon[i].hBaseIcon = cli.pfnGetIconFromStatusMode( NULL, szIconProto ? szIconProto : cli.trayIcon[i].szProto, status );

	nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATA_V1_SIZE;
	nid.hWnd = hwnd;
	nid.uID = cli.trayIcon[i].id;
	nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
	nid.uCallbackMessage = TIM_CALLBACK;
	nid.hIcon = cli.trayIcon[i].hBaseIcon;

	if (cli.shellVersion >= 5)
		nid.uFlags |= NIF_INFO;

	cli.pfnTrayIconMakeTooltip( NULL, cli.trayIcon[i].szProto );
	if ( !mToolTipTrayTips )
		lstrcpyn( nid.szTip, cli.szTip, SIZEOF( nid.szTip ));
	cli.trayIcon[i].ptszToolTip = mir_tstrdup( cli.szTip );

	Shell_NotifyIcon(NIM_ADD, &nid);
	cli.trayIcon[i].isBase = 1;

	if (cli.trayIconCount == 1)
		SetTaskBarIcon(cli.trayIcon[0].hBaseIcon, cli.szTip);

	ulock; return i;
}

void fnTrayIconRemove(HWND hwnd, const char *szProto)
{
	int i;
	initcheck;
	lock;
	for ( i = 0; i < cli.trayIconCount; i++ ) {
		struct trayIconInfo_t* pii = &cli.trayIcon[i];
		if ( pii->id != 0 && !lstrcmpA( szProto, pii->szProto )) {
			NOTIFYICONDATA nid = { 0 };
			nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATA_V1_SIZE;
			nid.hWnd = hwnd;
			nid.uID = pii->id;
			Shell_NotifyIcon(NIM_DELETE, &nid);

			DestroyIcon(pii->hBaseIcon);
			mir_free(pii->ptszToolTip); pii->ptszToolTip = NULL;
			pii->id = 0;
			break;
	}	}

	if (cli.trayIconCount == 1)
		SetTaskBarIcon(NULL, NULL);

	ulock;
}

int fnTrayIconInit(HWND hwnd)
{
    int netProtoCount = 0;
	initcheck 0;
	lock;

    int averageMode = GetAverageMode(&netProtoCount);
	mToolTipTrayTips = ServiceExists("mToolTip/ShowTip") != 0;

	if ( cli.cycleTimerId ) {
		KillTimer(NULL, cli.cycleTimerId);
		cli.cycleTimerId = 0;
	}

	cli.trayIconCount = 1;

    if (netProtoCount)
    {
	    cli.trayIcon = (trayIconInfo_t *) mir_calloc(sizeof(trayIconInfo_t) * accounts.getCount());

        int trayIconSetting = DBGetContactSettingByte(NULL, "CList", "TrayIcon", SETTING_TRAYICON_DEFAULT);

        if (trayIconSetting == SETTING_TRAYICON_MULTI) cli.trayIconCount = netProtoCount;

	    if (trayIconSetting == SETTING_TRAYICON_SINGLE) 
        {
		    DBVARIANT dbv = { DBVT_DELETED };
		    char *szProto;
		    if (!DBGetContactSettingString(NULL, "CList", "PrimaryStatus", &dbv)
			     && (averageMode < 0 || DBGetContactSettingByte(NULL, "CList", "AlwaysPrimary", 0) ))
			    szProto = dbv.pszVal;
		    else
			    szProto = NULL;

		    cli.pfnTrayIconAdd(hwnd, NULL, szProto, szProto ? CallProtoService(szProto, PS_GETSTATUS, 0, 0) : CallService(MS_CLIST_GETSTATUSMODE, 0, 0));
		    DBFreeVariant(&dbv);
	    }
	    else if (trayIconSetting == SETTING_TRAYICON_MULTI &&
	         (averageMode < 0 || DBGetContactSettingByte(NULL, "CList", "AlwaysMulti", SETTING_ALWAYSMULTI_DEFAULT )))
        {
		    for (int i = accounts.getCount(); i--; ) 
            {
			    int j = cli.pfnGetAccountIndexByPos( i );
			    if ( j >= 0 ) 
                {
				    PROTOACCOUNT* pa = accounts[j];
				    if ( cli.pfnGetProtocolVisibility( pa->szModuleName ))
					    cli.pfnTrayIconAdd(hwnd, pa->szModuleName, NULL, CallProtoService(pa->szModuleName, PS_GETSTATUS, 0, 0));
		        }	
            }
	    }	
	    else
        {
		    cli.pfnTrayIconAdd(hwnd, NULL, NULL, averageMode);
	    
            if (trayIconSetting == SETTING_TRAYICON_CYCLE && averageMode < 0)
		        cli.cycleTimerId = SetTimer(NULL, 0, DBGetContactSettingWord(NULL, "CList", "CycleTime", SETTING_CYCLETIME_DEFAULT) * 1000, cli.pfnTrayCycleTimerProc);
        }
    }
    else
    {
	    cli.trayIcon = (trayIconInfo_t *) mir_calloc(sizeof(trayIconInfo_t));
	    cli.pfnTrayIconAdd(hwnd, NULL, NULL, CallService(MS_CLIST_GETSTATUSMODE, 0, 0));
    }

    ulock; 
    return 0;
}

int fnTrayIconDestroy(HWND hwnd)
{
	NOTIFYICONDATA nid = { 0 };
	int i;
	initcheck 0;
	lock;

	if (cli.trayIconCount == 1)
		SetTaskBarIcon(NULL, NULL);

	nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATA_V1_SIZE;
	nid.hWnd = hwnd;
	for ( i = 0; i < cli.trayIconCount; i++ ) {
		if ( cli.trayIcon[i].id == 0 )
			continue;
		nid.uID = cli.trayIcon[i].id;
		Shell_NotifyIcon( NIM_DELETE, &nid );
		DestroyIcon( cli.trayIcon[i].hBaseIcon );
		mir_free( cli.trayIcon[i].ptszToolTip );
	}
	mir_free(cli.trayIcon);
	cli.trayIcon = NULL;
	cli.trayIconCount = 0;

	ulock; 
    return 0;
}

//called when Explorer crashes and the taskbar is remade
void fnTrayIconTaskbarCreated(HWND hwnd)
{
	initcheck;
	cli.pfnTrayIconDestroy(hwnd);
	cli.pfnTrayIconInit(hwnd);
}

static VOID CALLBACK RefreshTimerProc(HWND, UINT, UINT_PTR, DWORD)
{
	int i;
	if ( RefreshTimerId ) {
		KillTimer(NULL,RefreshTimerId); 
		RefreshTimerId=0;
	}
	for (i=0; i < accounts.getCount(); i++) {
		cli.pfnTrayIconUpdateBase( accounts[i]->szModuleName );
	}
}

int fnTrayIconUpdate(HICON hNewIcon, const TCHAR *szNewTip, const char *szPreferredProto, int isBase)
{
	NOTIFYICONDATA nid = { 0 };
	int i;

	initcheck -1;
	lock;
	nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATA_V1_SIZE;
	nid.hWnd = cli.hwndContactList;
	nid.uFlags = NIF_ICON | NIF_TIP;
	nid.hIcon = hNewIcon;
	if (!hNewIcon)
	{ ulock; return -1; }

	for (i = 0; i < cli.trayIconCount; i++) {
		if (cli.trayIcon[i].id == 0)
			continue;
		if (lstrcmpA(cli.trayIcon[i].szProto, szPreferredProto))
			continue;

		nid.uID = cli.trayIcon[i].id;
		cli.pfnTrayIconMakeTooltip(szNewTip, cli.trayIcon[i].szProto);
		mir_free( cli.trayIcon[i].ptszToolTip );
		cli.trayIcon[i].ptszToolTip = mir_tstrdup( cli.szTip );
		if(!mToolTipTrayTips)
			lstrcpyn(nid.szTip, cli.szTip, SIZEOF(nid.szTip));
		Shell_NotifyIcon(NIM_MODIFY, &nid);

		if (cli.trayIconCount == 1)
			SetTaskBarIcon(hNewIcon, cli.szTip);
		else
			SetTaskBarIcon(NULL, NULL);

		cli.trayIcon[i].isBase = isBase;
		{ ulock; return i; }
	}

	//if there wasn't a suitable icon, change all the icons
	{
		for (i = 0; i < cli.trayIconCount; i++) {
			if (cli.trayIcon[i].id == 0)
				continue;
			nid.uID = cli.trayIcon[i].id;

			cli.pfnTrayIconMakeTooltip(szNewTip, cli.trayIcon[i].szProto);
			mir_free( cli.trayIcon[i].ptszToolTip );
			cli.trayIcon[i].ptszToolTip = mir_tstrdup( cli.szTip );
			if(!mToolTipTrayTips)
				lstrcpyn(nid.szTip, cli.szTip, SIZEOF(nid.szTip));
			Shell_NotifyIcon(NIM_MODIFY, &nid);

			if (cli.trayIconCount == 1)
				SetTaskBarIcon(hNewIcon, cli.szTip);
			else
				SetTaskBarIcon(NULL, NULL);

			cli.trayIcon[i].isBase = isBase;
			if (DBGetContactSettingByte(NULL,"CList","TrayIcon",SETTING_TRAYICON_DEFAULT) == SETTING_TRAYICON_MULTI)
			{
				DWORD time1=DBGetContactSettingWord(NULL,"CList","CycleTime",SETTING_CYCLETIME_DEFAULT)*200;
				DWORD time2=DBGetContactSettingWord(NULL,"CList","IconFlashTime",550)+1000;
				DWORD time=max(max(2000,time1),time2);
				if(RefreshTimerId) {KillTimer(NULL,RefreshTimerId); RefreshTimerId=0;}
				RefreshTimerId=SetTimer(NULL,0,time,RefreshTimerProc);	// if unknown base was changed - than show preffered proto icon for 2 sec and reset it to original one after timeout
			}
			{ ulock; return i; }
		}
	}
	{ ulock; return -1; }
}

int fnTrayIconSetBaseInfo(HICON hIcon, const char *szPreferredProto)
{
	int i;
	initcheck -1;
	lock;
	if (szPreferredProto)
	{
		for (i = 0; i < cli.trayIconCount; i++) {
			if (cli.trayIcon[i].id == 0)
				continue;
			if (lstrcmpA(cli.trayIcon[i].szProto, szPreferredProto))
				continue;

			DestroyIcon(cli.trayIcon[i].hBaseIcon);
			cli.trayIcon[i].hBaseIcon = hIcon;
			ulock; return i;
		}
		if ((cli.pfnGetProtocolVisibility(szPreferredProto))
			 && (GetAverageMode()==-1) 
          && (DBGetContactSettingByte(NULL,"CList","TrayIcon",SETTING_TRAYICON_DEFAULT)==SETTING_TRAYICON_MULTI) 
          && !(DBGetContactSettingByte(NULL,"CList","AlwaysMulti",SETTING_ALWAYSMULTI_DEFAULT)))
			goto LBL_Error;
	}

	//if there wasn't a specific icon, there will only be one suitable
	for (i = 0; i < cli.trayIconCount; i++) {
		if (cli.trayIcon[i].id == 0)
			continue;

		DestroyIcon(cli.trayIcon[i].hBaseIcon);
		cli.trayIcon[i].hBaseIcon = hIcon;
		ulock; return i;
	}

LBL_Error:
	DestroyIcon(hIcon);
	ulock; return -1;
}

void fnTrayIconUpdateWithImageList(int iImage, const TCHAR *szNewTip, char *szPreferredProto)
{
	HICON hIcon = ImageList_GetIcon(hCListImages, iImage, ILD_NORMAL);
	cli.pfnTrayIconUpdate(hIcon, szNewTip, szPreferredProto, 0);
	DestroyIcon(hIcon);
}

VOID CALLBACK fnTrayCycleTimerProc(HWND, UINT, UINT_PTR, DWORD)
{
	initcheck;
	lock;

    int i;
    for (i = accounts.getCount() + 1; --i;) {
	    cycleStep = (cycleStep + 1) % accounts.getCount();
	    if ( cli.pfnGetProtocolVisibility( accounts[cycleStep]->szModuleName ))
		    break;
    }

    if (i)
    {
        DestroyIcon(cli.trayIcon[0].hBaseIcon);
        cli.trayIcon[0].hBaseIcon = cli.pfnGetIconFromStatusMode(NULL, accounts[cycleStep]->szModuleName, 
	        CallProtoService( accounts[cycleStep]->szModuleName, PS_GETSTATUS, 0, 0 ));
        if (cli.trayIcon[0].isBase)
	        cli.pfnTrayIconUpdate(cli.trayIcon[0].hBaseIcon, NULL, NULL, 1);
    }

    ulock;
}

void fnTrayIconUpdateBase(const char *szChangedProto)
{
	if ( !cli.pfnGetProtocolVisibility( szChangedProto )) return;

	int i, netProtoCount, changed = -1;
	HWND hwnd = cli.hwndContactList;
	initcheck;
	lock;
	int averageMode = GetAverageMode(&netProtoCount);

    if (cli.cycleTimerId) {
		KillTimer(NULL, cli.cycleTimerId);
		cli.cycleTimerId = 0;
	}

	for (i = 0; i < accounts.getCount(); i++) {
		if (!lstrcmpA(szChangedProto, accounts[i]->szModuleName ))
			cycleStep = i - 1;
	}
    
	if (netProtoCount > 0) 
    {
        int trayIconSetting = DBGetContactSettingByte(NULL, "CList", "TrayIcon", SETTING_TRAYICON_DEFAULT);

		if (averageMode > 0) {
			if (trayIconSetting == SETTING_TRAYICON_MULTI) {
				if (DBGetContactSettingByte(NULL, "CList", "AlwaysMulti", SETTING_ALWAYSMULTI_DEFAULT))
					//changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode((char*)szChangedProto, NULL, averageMode), (char*)szChangedProto);
					changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode( NULL, szChangedProto, CallProtoService(szChangedProto, PS_GETSTATUS, 0, 0)), (char*)szChangedProto );
				else if (cli.trayIcon && cli.trayIcon[0].szProto != NULL) {
					cli.pfnTrayIconDestroy(hwnd);
					cli.pfnTrayIconInit(hwnd);
				}
				else
					changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode(NULL, NULL, averageMode), NULL );
			}
			else
				changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode(NULL, NULL, averageMode), NULL);
		}
		else {
			switch (trayIconSetting) {
			case SETTING_TRAYICON_SINGLE:
				{
					DBVARIANT dbv = { DBVT_DELETED };
					char *szProto;
					if (DBGetContactSettingString(NULL, "CList", "PrimaryStatus", &dbv))
						szProto = NULL;
					else
						szProto = dbv.pszVal;
					changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode( NULL, szProto, szProto ? CallProtoService(szProto, PS_GETSTATUS, 0,0) : CallService(MS_CLIST_GETSTATUSMODE, 0, 0)), szProto );
					DBFreeVariant(&dbv);
					break;
				}
			case SETTING_TRAYICON_CYCLE:
				cli.cycleTimerId =
					SetTimer(NULL, 0, DBGetContactSettingWord(NULL, "CList", "CycleTime", SETTING_CYCLETIME_DEFAULT) * 1000, cli.pfnTrayCycleTimerProc);
				changed =
					cli.pfnTrayIconSetBaseInfo(ImageList_GetIcon
					(hCListImages, cli.pfnIconFromStatusMode(szChangedProto, CallProtoService(szChangedProto, PS_GETSTATUS, 0, 0), NULL),
					ILD_NORMAL), NULL);
				break;
			case SETTING_TRAYICON_MULTI:
				if (!cli.trayIcon) {
					cli.pfnTrayIconRemove(NULL, NULL);
				}
				else if (DBGetContactSettingByte( NULL, "CList", "AlwaysMulti", SETTING_ALWAYSMULTI_DEFAULT ))
					changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode( NULL, szChangedProto, CallProtoService(szChangedProto, PS_GETSTATUS, 0, 0)), (char*)szChangedProto );
				else {
					cli.pfnTrayIconDestroy(hwnd);
					cli.pfnTrayIconInit(hwnd);
				}
				break;
			}
		}
	}
	else
		changed = cli.pfnTrayIconSetBaseInfo(ImageList_GetIcon(hCListImages, cli.pfnIconFromStatusMode(NULL, averageMode, NULL), ILD_NORMAL), NULL);

	if (changed != -1 && cli.trayIcon[changed].isBase)
		cli.pfnTrayIconUpdate(cli.trayIcon[changed].hBaseIcon, NULL, cli.trayIcon[changed].szProto, 1);
	ulock;
}

void fnTrayIconSetToBase(char *szPreferredProto)
{
	int i;
	initcheck;
	lock;
    
	for (i = 0; i < cli.trayIconCount; i++) {
		if ( cli.trayIcon[i].id == 0 )
			continue;
		if ( lstrcmpA( cli.trayIcon[i].szProto, szPreferredProto ))
			continue;
		cli.pfnTrayIconUpdate( cli.trayIcon[i].hBaseIcon, NULL, szPreferredProto, 1);
		ulock; return;
	}

	//if there wasn't a specific icon, there will only be one suitable
	for ( i = 0; i < cli.trayIconCount; i++) {
		if ( cli.trayIcon[i].id == 0 )
			continue;
		cli.pfnTrayIconUpdate( cli.trayIcon[i].hBaseIcon, NULL, szPreferredProto, 1);
		ulock; return;
	}
	ulock; return;
}

void fnTrayIconIconsChanged(void)
{
	initcheck;
	lock;
	cli.pfnTrayIconDestroy(cli.hwndContactList);
	cli.pfnTrayIconInit(cli.hwndContactList);
	ulock;
}

static UINT_PTR autoHideTimerId;
static VOID CALLBACK TrayIconAutoHideTimer(HWND hwnd, UINT, UINT_PTR idEvent, DWORD)
{
	HWND hwndClui;
	initcheck;
	lock;
	KillTimer(hwnd, idEvent);
	hwndClui = cli.hwndContactList;
	if (GetActiveWindow() != hwndClui) {
		ShowWindow(hwndClui, SW_HIDE);
		if (MySetProcessWorkingSetSize != NULL)
			MySetProcessWorkingSetSize(GetCurrentProcess(), -1, -1);
	}
	ulock; return;
}

int fnTrayIconPauseAutoHide(WPARAM, LPARAM)
{
	initcheck 0;
	lock;
	if (DBGetContactSettingByte(NULL, "CList", "AutoHide", SETTING_AUTOHIDE_DEFAULT)) {
		if ( GetActiveWindow() != cli.hwndContactList ) {
			KillTimer(NULL, autoHideTimerId);
			autoHideTimerId = SetTimer(NULL, 0, 1000 * DBGetContactSettingWord(NULL, "CList", "HideTime", SETTING_HIDETIME_DEFAULT), TrayIconAutoHideTimer);
		}
	}
	ulock; return 0;
}

/////////////////////////////////////////////////////////////////////////////////////////
// processes tray icon's messages

static BYTE s_LastHoverIconID = 0;
static BOOL g_trayTooltipActive = FALSE;
static POINT tray_hover_pos = {0};

static void CALLBACK TrayHideToolTipTimerProc(HWND hwnd, UINT, UINT_PTR, DWORD)
{
	if ( g_trayTooltipActive ) {
		POINT pt;
		GetCursorPos(&pt);
		if ( abs(pt.x - tray_hover_pos.x) > TOOLTIP_TOLERANCE || abs(pt.y - tray_hover_pos.y) > TOOLTIP_TOLERANCE ) {
			CallService("mToolTip/HideTip", 0, 0);
			g_trayTooltipActive = FALSE;
			KillTimer( hwnd, TIMERID_TRAYHOVER_2 );
		}
	}
	else KillTimer( hwnd, TIMERID_TRAYHOVER_2 );
}

static void CALLBACK TrayToolTipTimerProc(HWND hwnd, UINT, UINT_PTR id, DWORD)
{
	if ( !g_trayTooltipActive && !cli.bTrayMenuOnScreen ) {
		CLCINFOTIP ti = {0};
		POINT pt;
		GetCursorPos( &pt );
		if ( abs(pt.x - tray_hover_pos.x) <= TOOLTIP_TOLERANCE && abs(pt.y - tray_hover_pos.y) <= TOOLTIP_TOLERANCE ) {
			TCHAR* szTipCur = cli.szTip;
			{
				int n = s_LastHoverIconID-100;
				if ( n >= 0 && n < cli.trayIconCount )
					szTipCur = cli.trayIcon[n].ptszToolTip;
			}
			ti.rcItem.left   = pt.x - 10;
			ti.rcItem.right  = pt.x + 10;
			ti.rcItem.top    = pt.y - 10;
			ti.rcItem.bottom = pt.y + 10;
			ti.cbSize = sizeof( ti );
			ti.isTreeFocused = GetFocus() == cli.hwndContactList ? 1 : 0;
			#if defined( _UNICODE )
	        if (CallService( "mToolTip/ShowTipW", (WPARAM)szTipCur, (LPARAM)&ti ) == CALLSERVICE_NOTFOUND)
			{	
				char* p = mir_u2a( szTipCur );
	        	CallService( "mToolTip/ShowTip", (WPARAM)p, (LPARAM)&ti );
				mir_free( p );
			}
			#else
	        	CallService( "mToolTip/ShowTip", (WPARAM)szTipCur, (LPARAM)&ti );
			#endif
			GetCursorPos( &tray_hover_pos );
			SetTimer( cli.hwndContactList, TIMERID_TRAYHOVER_2, 600, TrayHideToolTipTimerProc );
			g_trayTooltipActive = TRUE;
	}	}

	KillTimer(hwnd, id);
}

INT_PTR fnTrayIconProcessMessage(WPARAM wParam, LPARAM lParam)
{
	MSG *msg = (MSG *) wParam;
	switch (msg->message) {
	case WM_CREATE: {
		WM_TASKBARCREATED = RegisterWindowMessage( _T("TaskbarCreated"));
		WM_TASKBARBUTTONCREATED = RegisterWindowMessage( _T("TaskbarButtonCreated"));
		PostMessage(msg->hwnd, TIM_CREATE, 0, 0);
		break;
	}
	case TIM_CREATE:
		cli.pfnTrayIconInit(msg->hwnd);
		break;

	case WM_ACTIVATE:
		if (DBGetContactSettingByte(NULL, "CList", "AutoHide", SETTING_AUTOHIDE_DEFAULT)) {
			if (LOWORD(msg->wParam) == WA_INACTIVE)
				autoHideTimerId = SetTimer(NULL, 0, 1000 * DBGetContactSettingWord(NULL, "CList", "HideTime", SETTING_HIDETIME_DEFAULT), TrayIconAutoHideTimer);
			else
				KillTimer(NULL, autoHideTimerId);
		}
		break;

	case WM_DESTROY:
		cli.pfnTrayIconDestroy(msg->hwnd);
		cli.pfnUninitTray();
		break;

	case TIM_CALLBACK:
		if ( msg->lParam == WM_RBUTTONDOWN || msg->lParam == WM_LBUTTONDOWN || msg->lParam == WM_RBUTTONDOWN && g_trayTooltipActive ) {
			CallService("mToolTip/HideTip", 0, 0);
			g_trayTooltipActive = FALSE;
		}

		if ( msg->lParam == WM_MBUTTONUP )
			cli.pfnShowHide(0, 0);
		else if (msg->lParam == (DBGetContactSettingByte(NULL, "CList", "Tray1Click", SETTING_TRAY1CLICK_DEFAULT) ? WM_LBUTTONUP : WM_LBUTTONDBLCLK)) {
			if ((GetAsyncKeyState(VK_CONTROL) & 0x8000))
				cli.pfnShowHide(0, 0);
			else if (cli.pfnEventsProcessTrayDoubleClick(msg->wParam))
				cli.pfnShowHide(0, 0);
		}
		else if (msg->lParam == WM_RBUTTONUP) {
			MENUITEMINFO mi;
			POINT pt;
			HMENU hMainMenu = LoadMenu(cli.hInst, MAKEINTRESOURCE(IDR_CONTEXT));
			HMENU hMenu = GetSubMenu(hMainMenu, 0);
			CallService(MS_LANGPACK_TRANSLATEMENU, (WPARAM) hMenu, 0);

			ZeroMemory(&mi, sizeof(mi));
			mi.cbSize = MENUITEMINFO_V4_SIZE;
			mi.fMask = MIIM_SUBMENU | MIIM_TYPE;
			mi.fType = MFT_STRING;
			mi.hSubMenu = (HMENU) CallService(MS_CLIST_MENUGETMAIN, 0, 0);
			mi.dwTypeData = TranslateT("&Main Menu");
			InsertMenuItem(hMenu, 1, TRUE, &mi);
			mi.hSubMenu = (HMENU) CallService(MS_CLIST_MENUGETSTATUS, 0, 0);
			mi.dwTypeData = TranslateT("&Status");
			InsertMenuItem(hMenu, 2, TRUE, &mi);
			SetMenuDefaultItem(hMenu, ID_TRAY_HIDE, FALSE);

			SetForegroundWindow(msg->hwnd);
			SetFocus(msg->hwnd);
			GetCursorPos(&pt);
			TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, msg->hwnd, NULL);

			RemoveMenu(hMenu, 1, MF_BYPOSITION);
			RemoveMenu(hMenu, 1, MF_BYPOSITION);
			DestroyMenu(hMainMenu);
		}
		else if ( msg->lParam == WM_MOUSEMOVE ) {
			s_LastHoverIconID = msg->wParam;
			if ( g_trayTooltipActive ) {
				POINT pt;
				GetCursorPos( &pt );
				if ( abs(pt.x - tray_hover_pos.x) > TOOLTIP_TOLERANCE || abs(pt.y - tray_hover_pos.y) > TOOLTIP_TOLERANCE ) {
					CallService("mToolTip/HideTip", 0, 0);
					g_trayTooltipActive = FALSE;
					ReleaseCapture();
				}
			}
			else {
				GetCursorPos(&tray_hover_pos);
				SetTimer(cli.hwndContactList, TIMERID_TRAYHOVER, 600, TrayToolTipTimerProc);
			}
			break;
		}

		*((LRESULT *) lParam) = 0;
		return TRUE;

	default:
		if (msg->message == WM_TASKBARCREATED) {
			cli.pfnTrayIconTaskbarCreated(msg->hwnd);
			*((LRESULT *) lParam) = 0;
			return TRUE;
		}
		else if (msg->message == WM_TASKBARBUTTONCREATED) { 
			SetTaskBarIcon(lastTaskBarIcon, NULL);
			*((LRESULT *) lParam) = 0;
			return TRUE;
		}
	}

	return FALSE;
}

/////////////////////////////////////////////////////////////////////////////////////////
// processes tray icon's notifications

int fnCListTrayNotify( MIRANDASYSTRAYNOTIFY* msn )
{
	UINT iconId = 0;

	if ( msn == NULL )
		return 1;

	if ( msn->cbSize != sizeof(MIRANDASYSTRAYNOTIFY) || msn->szInfo == NULL || msn->szInfoTitle == NULL )
		return 1;

	if ( cli.trayIcon == NULL )
		return 2;

	if ( msn->szProto ) {
		int j;
		for ( j = 0; j < cli.trayIconCount; j++ ) {
			if ( cli.trayIcon[j].szProto != NULL ) {
				if ( !strcmp( msn->szProto, cli.trayIcon[j].szProto )) {
					iconId = cli.trayIcon[j].id;
					break;
				}
			}
			else if ( cli.trayIcon[j].isBase ) {
				iconId = cli.trayIcon[j].id;
				break;
		}	}
	}
	else iconId = cli.trayIcon[0].id;

#if defined(_UNICODE)
	if ( msn->dwInfoFlags & NIIF_INTERN_UNICODE ) {
		NOTIFYICONDATAW nid = {0};
		nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATAW_V1_SIZE;
		nid.hWnd = cli.hwndContactList;
		nid.uID = iconId;
		nid.uFlags = NIF_INFO;
		lstrcpynW( nid.szInfo, msn->tszInfo, SIZEOF( nid.szInfo ));
		lstrcpynW( nid.szInfoTitle, msn->tszInfoTitle, SIZEOF( nid.szInfoTitle ));
		nid.szInfo[ SIZEOF(nid.szInfo)-1 ] = 0;
		nid.szInfoTitle[ SIZEOF(nid.szInfoTitle)-1 ] = 0;
		nid.uTimeout = msn->uTimeout;
		nid.dwInfoFlags = (msn->dwInfoFlags & ~NIIF_INTERN_UNICODE);
		return Shell_NotifyIconW( NIM_MODIFY, &nid ) == 0;
	}
	else
#endif
	{
		NOTIFYICONDATAA nid = { 0 };
		nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATAA_V1_SIZE;
		nid.hWnd = cli.hwndContactList;
		nid.uID = iconId;
		nid.uFlags = NIF_INFO;
		lstrcpynA( nid.szInfo, msn->szInfo, sizeof( nid.szInfo ));
		lstrcpynA( nid.szInfoTitle, msn->szInfoTitle, sizeof( nid.szInfoTitle ));
		nid.uTimeout = msn->uTimeout;
		nid.dwInfoFlags = msn->dwInfoFlags;
		return Shell_NotifyIconA( NIM_MODIFY, &nid ) == 0;
}	}

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

typedef struct _DllVersionInfo
{
	DWORD cbSize;
	DWORD dwMajorVersion;       // Major version
	DWORD dwMinorVersion;       // Minor version
	DWORD dwBuildNumber;        // Build number
	DWORD dwPlatformID;         // DLLVER_PLATFORM_*
}
	DLLVERSIONINFO;

typedef HRESULT(CALLBACK * DLLGETVERSIONPROC) (DLLVERSIONINFO *);

static DLLVERSIONINFO dviShell;

static INT_PTR pfnCListTrayNotifyStub(WPARAM, LPARAM lParam )
{	return cli.pfnCListTrayNotify(( MIRANDASYSTRAYNOTIFY* )lParam );
}

void fnInitTray( void )
{
	HMODULE hLib = GetModuleHandleA("shell32");
	if ( hLib ) {
		DLLGETVERSIONPROC proc;
		dviShell.cbSize = sizeof(dviShell);
		proc = ( DLLGETVERSIONPROC )GetProcAddress( hLib, "DllGetVersion" );
		if (proc) {
			proc( &dviShell );
			cli.shellVersion = dviShell.dwMajorVersion;
		}
 		FreeLibrary(hLib);
	}
	InitializeCriticalSection(&trayLockCS);
	if ( cli.shellVersion >= 5 )
		CreateServiceFunction(MS_CLIST_SYSTRAY_NOTIFY, pfnCListTrayNotifyStub );
	fTrayInited=TRUE;
}

void fnUninitTray( void )
{
	fTrayInited=FALSE;
	DeleteCriticalSection( &trayLockCS );
}
void   fnLockTray( void )
{
//	return;				//stub to be removed
	initcheck;
	EnterCriticalSection( &trayLockCS );
}

void   fnUnlockTray( void )
{
//	return;				//stub to be removed
	initcheck;
#ifdef _DEBUG
	if (trayLockCS.RecursionCount==0) DebugBreak();	   //try to unlock already
#endif
	LeaveCriticalSection( &trayLockCS );
}

#undef lock
#undef ulock
#undef initcheck
