/*
astyle --force-indent=tab=4 --brackets=linux --indent-switches
		--pad=oper --one-line=keep-blocks  --unpad=paren

Chat module plugin for Miranda NG

Copyright (C) 2003 J�rgen Persson

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"

#define	WINDOWS_COMMANDS_MAX 30
#define	STATUSICONCOUNT 6

static SESSION_INFO *s_WndList = NULL;
static MODULEINFO *s_ModList = NULL;

void SetActiveSession(const TCHAR* pszID, const char* pszModule)
{
	SESSION_INFO *si = SM_FindSession(pszID, pszModule);
	if (si)
		SetActiveSessionEx(si);
}

void SetActiveSessionEx(SESSION_INFO *si)
{
	if (si) {
		replaceStrT(pszActiveWndID, si->ptszID);
		replaceStr(pszActiveWndModule, si->pszModule);
	}
}

SESSION_INFO* GetActiveSession(void)
{
	SESSION_INFO *si = SM_FindSession(pszActiveWndID, pszActiveWndModule);
	if (si)
		return si;

	return s_WndList;
}

//---------------------------------------------------
//		Session Manager functions
//
//		Keeps track of all sessions and its windows
//---------------------------------------------------

SESSION_INFO* SM_AddSession(const TCHAR* pszID, const char* pszModule)
{
	if (!pszID || !pszModule)
		return NULL;

	if (SM_FindSession(pszID, pszModule))
		return NULL;

	SESSION_INFO *node = (SESSION_INFO*)mir_calloc(sizeof(SESSION_INFO));
	node->ptszID = mir_tstrdup(pszID);
	node->pszModule = mir_strdup(pszModule);

	MODULEINFO *mi = MM_FindModule(pszModule);
	if (mi) {
		mi->idleTimeStamp = time(0);
		SM_BroadcastMessage(pszModule, GC_UPDATESTATUSBAR, 0, 1, TRUE);
	}

	if (s_WndList == NULL) { // list is empty
		s_WndList = node;
		node->next = NULL;
	}
	else {
		node->next = s_WndList;
		s_WndList = node;
	}
	node->Highlight = g_Settings.Highlight;
	return node;
}

int SM_RemoveSession(const TCHAR* pszID, const char* pszModule, bool removeContact)
{
	if (!pszModule)
		return FALSE;

	SESSION_INFO* pTemp = s_WndList, *pLast = NULL;
	while (pTemp != NULL) {
		if ((!pszID && pTemp->iType != GCW_SERVER || !lstrcmpi(pTemp->ptszID, pszID)) && !lstrcmpiA(pTemp->pszModule, pszModule)) { // match
			DWORD dw = pTemp->dwItemData;

			if (pTemp->hWnd)
				SendMessage(pTemp->hWnd, GC_EVENT_CONTROL + WM_USER + 500, SESSION_TERMINATE, 0);

			DoEventHook(pTemp->ptszID, pTemp->pszModule, GC_SESSION_TERMINATE, NULL, NULL, (DWORD)pTemp->dwItemData);

			if (pLast == NULL)
				s_WndList = pTemp->next;
			else
				pLast->next = pTemp->next;

			UM_RemoveAll(&pTemp->pUsers);
			TM_RemoveAll(&pTemp->pStatuses);
			LM_RemoveAll(&pTemp->pLog, &pTemp->pLogEnd);
			pTemp->iStatusCount = 0;
			pTemp->nUsersInNicklist = 0;

			if (pTemp->hContact)
				CList_SetOffline(pTemp->hContact, pTemp->iType == GCW_CHATROOM ? TRUE : FALSE);

			db_set_s(pTemp->hContact, pTemp->pszModule , "Topic", "");
			db_set_s(pTemp->hContact, pTemp->pszModule, "StatusBar", "");
			db_unset(pTemp->hContact, "CList", "StatusMsg");

			if (removeContact)
				CallService(MS_DB_CONTACT_DELETE, (WPARAM)pTemp->hContact, 0);

			mir_free(pTemp->pszModule);
			mir_free(pTemp->ptszID);
			mir_free(pTemp->ptszName);
			mir_free(pTemp->ptszStatusbarText);
			mir_free(pTemp->ptszTopic);
			mir_free(pTemp->pszID);
			mir_free(pTemp->pszName);

			// delete commands
			COMMAND_INFO *pCurComm = pTemp->lpCommands;
			while (pCurComm != NULL) {
				COMMAND_INFO *pNext = pCurComm->next;
				mir_free(pCurComm->lpCommand);
				mir_free(pCurComm);
				pCurComm = pNext;
			}

			mir_free(pTemp);
			if (pszID)
				return (int)dw;
			if (pLast)
				pTemp = pLast->next;
			else
				pTemp = s_WndList;
		}
		else {
			pLast = pTemp;
			pTemp = pTemp->next;
		}
	}
	return FALSE;
}

void SM_RemoveContainer(TContainerData *pContainer)
{
	for (SESSION_INFO *si = s_WndList; si; si = si->next)
		if (si->pContainer == pContainer)
			si->pContainer = NULL;
}

SESSION_INFO* SM_FindSession(const TCHAR* pszID, const char* pszModule)
{
	if (!pszID || !pszModule)
		return NULL;

	for (SESSION_INFO *si = s_WndList; si; si = si->next)
		if (!lstrcmpi(si->ptszID, pszID) && !lstrcmpiA(si->pszModule, pszModule))
			return si;

	return NULL;
}

BOOL SM_SetOffline(const TCHAR* pszID, const char* pszModule)
{
	if (!pszModule)
		return FALSE;

	for (SESSION_INFO *si = s_WndList; si; si = si->next) {
		if ((!pszID || !lstrcmpi(si->ptszID, pszID)) && !lstrcmpiA(si->pszModule, pszModule)) {
			UM_RemoveAll(&si->pUsers);
			si->nUsersInNicklist = 0;
			if (si->iType != GCW_SERVER)
				si->bInitDone = FALSE;

			if (pszID)
				return TRUE;
		}
	}

	return TRUE;
}

BOOL SM_SetStatusEx(const TCHAR* pszID, const char* pszModule, const TCHAR* pszText, int flags)
{
	if (!pszModule)
		return FALSE;

	for (SESSION_INFO *si = s_WndList; si; si = si->next) {
		if ((!pszID || !lstrcmpi(si->ptszID, pszID)) && !lstrcmpiA(si->pszModule, pszModule)) {
			UM_SetStatusEx(si->pUsers, pszText, flags);
			if (si->hWnd)
				RedrawWindow(GetDlgItem(si->hWnd, IDC_LIST), NULL, NULL, RDW_INVALIDATE);
			if (pszID)
				return TRUE;
		}
	}

	return TRUE;
}

HICON SM_GetStatusIcon(SESSION_INFO *si, USERINFO* ui, char *szIndicator)
{
	if (!ui || !si)
		return NULL;

	*szIndicator = 0;

	STATUSINFO *ti = TM_FindStatus(si->pStatuses, TM_WordToString(si->pStatuses, ui->Status));
	if (ti == NULL)
		return hIcons[ICON_STATUS0];

	if ((INT_PTR)ti->hIcon >= STATUSICONCOUNT)
		return ti->hIcon;

	int id = si->iStatusCount - (int)ti->hIcon - 1;
	if (id == 0) {
		*szIndicator = 0;
		return hIcons[ICON_STATUS0];
	}
	if (id == 1) {
		*szIndicator = '+';
		return hIcons[ICON_STATUS1];
	}
	if (id == 2) {
		*szIndicator = '%';
		return hIcons[ICON_STATUS2];
	}
	if (id == 3) {
		*szIndicator = '@';
		return hIcons[ICON_STATUS3];
	}
	if (id == 4) {
		*szIndicator = '!';
		return hIcons[ICON_STATUS4];
	}
	if (id == 5) {
		*szIndicator = '*';
		return hIcons[ICON_STATUS5];
	}
		
	return hIcons[ICON_STATUS0];
}

BOOL SM_AddEventToAllMatchingUID(GCEVENT *gce, BOOL bIsHighLight)
{
	int bManyFix = 0;

	for (SESSION_INFO *si = s_WndList; si; si = si->next) {
		if (!lstrcmpiA(si->pszModule, gce->pDest->pszModule)) {
			if (UM_FindUser(si->pUsers, gce->ptszUID)) {
				if (si->bInitDone) {
					if (SM_AddEvent(si->ptszID, si->pszModule, gce, FALSE) && si->hWnd && si->bInitDone)
						SendMessage(si->hWnd, GC_ADDLOG, 0, 0);
					else if (si->hWnd && si->bInitDone)
						SendMessage(si->hWnd, GC_REDRAWLOG2, 0, 0);

					if (!(gce->dwFlags & GCEF_NOTNOTIFY))
						DoSoundsFlashPopupTrayStuff(si, gce, bIsHighLight, bManyFix);
					bManyFix ++;
					if ((gce->dwFlags & GCEF_ADDTOLOG) && g_Settings.bLoggingEnabled)
						LogToFile(si, gce);
				}
			}
		}
	}

	return 0;
}

BOOL SM_AddEvent(const TCHAR* pszID, const char* pszModule, GCEVENT *gce, BOOL bIsHighlighted)
{
	if (!pszID || !pszModule)
		return TRUE;

	SESSION_INFO *si = SM_FindSession(pszID, pszModule);
	if (si == NULL)
		return TRUE;

	if (!lstrcmpi(si->ptszID, pszID) && !lstrcmpiA(si->pszModule, pszModule)) {
		LOGINFO *li = LM_AddEvent(&si->pLog, &si->pLogEnd);
		si->iEventCount += 1;

		li->iType = gce->pDest->iType;
		li->ptszNick = mir_tstrdup(gce->ptszNick);
		li->ptszText = mir_tstrdup(gce->ptszText);
		li->ptszStatus = mir_tstrdup(gce->ptszStatus);
		li->ptszUserInfo = mir_tstrdup(gce->ptszUserInfo);

		li->bIsMe = gce->bIsMe != 0;
		li->bIsHighlighted = bIsHighlighted != 0;
		li->time = gce->time;

		if (g_Settings.iEventLimit > 0 && si->iEventCount > g_Settings.iEventLimit + g_Settings.iEventLimitThreshold) {
			LM_TrimLog(&si->pLog, &si->pLogEnd, si->iEventCount - g_Settings.iEventLimit);
			si->wasTrimmed = TRUE;
			si->iEventCount = g_Settings.iEventLimit;
		}
	}

	return TRUE;
}

USERINFO* SM_AddUser(const TCHAR* pszID, const char* pszModule, const TCHAR* pszUID, const TCHAR* pszNick, WORD wStatus)
{
	if (!pszID || !pszModule)
		return NULL;

	SESSION_INFO *si = SM_FindSession(pszID, pszModule);
	if (si) {
		USERINFO *p = UM_AddUser(si->pStatuses, &si->pUsers, pszUID, pszNick, wStatus);
		si->nUsersInNicklist++;
		return p;
	}

	return 0;
}

BOOL SM_MoveUser(const TCHAR* pszID, const char* pszModule, const TCHAR* pszUID)
{
	if (!pszID || !pszModule || !pszUID)
		return FALSE;

	SESSION_INFO *si = SM_FindSession(pszID, pszModule);
	if (si == NULL)
		return FALSE;

	UM_SortUser(&si->pUsers, pszUID);
	return TRUE;
}

BOOL SM_RemoveUser(const TCHAR* pszID, const char* pszModule, const TCHAR* pszUID)
{
	if (!pszModule || !pszUID)
		return FALSE;

	for (SESSION_INFO *si = s_WndList; si; si = si->next) {
		if ((!pszID || !lstrcmpi(si->ptszID, pszID)) && !lstrcmpiA(si->pszModule, pszModule)) {
			USERINFO *ui = UM_FindUser(si->pUsers, pszUID);
			if (ui) {
				si->nUsersInNicklist--;

				UM_RemoveUser(&si->pUsers, pszUID);

				if (si->hWnd)
					SendMessage(si->hWnd, GC_UPDATENICKLIST, 0, 0);

				if (pszID)
					return TRUE;
			}
		}
	}

	return 0;
}

USERINFO* SM_GetUserFromIndex(const TCHAR* pszID, const char* pszModule, int index)
{
	if (!pszModule)
		return NULL;

	SESSION_INFO *si = SM_FindSession(pszID, pszModule);
	if (si == NULL)
		return NULL;

	return UM_FindUserFromIndex(si->pUsers, index);
}

STATUSINFO* SM_AddStatus(const TCHAR* pszID, const char* pszModule, const TCHAR* pszStatus)
{
	if (!pszID || !pszModule)
		return NULL;

	SESSION_INFO *si = SM_FindSession(pszID, pszModule);
	if (si == NULL)
		return NULL;

	STATUSINFO *ti = TM_AddStatus(&si->pStatuses, pszStatus, &si->iStatusCount);
	if (ti)
		si->iStatusCount++;
	return ti;
}

BOOL SM_GiveStatus(const TCHAR* pszID, const char* pszModule, const TCHAR* pszUID, const TCHAR* pszStatus)
{
	if (!pszID || !pszModule)
		return FALSE;

	SESSION_INFO *si = SM_FindSession(pszID, pszModule);
	if (si == NULL)
		return FALSE;
		
	USERINFO *ui = UM_GiveStatus(si->pUsers, pszUID, TM_StringToWord(si->pStatuses, pszStatus));
	if (ui) {
		SM_MoveUser(si->ptszID, si->pszModule, ui->pszUID);
		if (si->hWnd)
			SendMessage(si->hWnd, GC_UPDATENICKLIST, 0, 0);
	}
	return TRUE;
}

BOOL SM_SetContactStatus(const TCHAR* pszID, const char* pszModule, const TCHAR* pszUID, WORD wStatus)
{
	if (!pszID || !pszModule)
		return FALSE;

	SESSION_INFO *si = SM_FindSession(pszID, pszModule);
	if (si == NULL)
		return FALSE;

	USERINFO *ui = UM_SetContactStatus(si->pUsers, pszUID, wStatus);
	if (ui) {
		SM_MoveUser(si->ptszID, si->pszModule, ui->pszUID);
		if (si->hWnd)
			SendMessage(si->hWnd, GC_UPDATENICKLIST, 0, 0);
	}
	return TRUE;
}

BOOL SM_TakeStatus(const TCHAR* pszID, const char* pszModule, const TCHAR* pszUID, const TCHAR* pszStatus)
{
	if (!pszID || !pszModule)
		return FALSE;

	SESSION_INFO *si = SM_FindSession(pszID, pszModule);
	if (si == NULL)
		return FALSE;

	USERINFO* ui = UM_TakeStatus(si->pUsers, pszUID, TM_StringToWord(si->pStatuses, pszStatus));
	if (ui) {
		SM_MoveUser(si->ptszID, si->pszModule, ui->pszUID);
		if (si->hWnd)
			SendMessage(si->hWnd, GC_UPDATENICKLIST, 0, 0);
	}
	return TRUE;
}

LRESULT SM_SendMessage(const TCHAR* pszID, const char* pszModule, UINT msg, WPARAM wParam, LPARAM lParam)
{
	for (SESSION_INFO *si = s_WndList; si && pszModule; si = si->next) {
		if ((!pszID || !lstrcmpi(si->ptszID, pszID)) && !lstrcmpiA(si->pszModule, pszModule)) {
			if (si->hWnd) {
				LRESULT i = SendMessage(si->hWnd, msg, wParam, lParam);
				if (pszID)
					return i;
			}
			if (pszID)
				return 0;
		}
	}

	return 0;
}

BOOL SM_PostMessage(const TCHAR* pszID, const char* pszModule, UINT msg, WPARAM wParam, LPARAM lParam)
{
	if (!pszID || !pszModule)
		return 0;

	SESSION_INFO *si = SM_FindSession(pszID, pszModule);
	if (si && si->hWnd)
		return PostMessage(si->hWnd, msg, wParam, lParam);

	return FALSE;
}

BOOL SM_BroadcastMessage(const char* pszModule, UINT msg, WPARAM wParam, LPARAM lParam, BOOL bAsync)
{
	for (SESSION_INFO *si = s_WndList; si; si = si->next) {
		if (!pszModule || !lstrcmpiA(si->pszModule, pszModule)) {
			if (si->hWnd) {
				if (bAsync)
					PostMessage(si->hWnd, msg, wParam, lParam);
				else
					SendMessage(si->hWnd, msg, wParam, lParam);
			}
		}
	}

	return TRUE;
}

BOOL SM_ReconfigureFilters()
{
	for (SESSION_INFO *si = s_WndList; si; si = si->next)
		Chat_SetFilters(si);

	return TRUE;
}

BOOL SM_InvalidateLogDirectories()
{
	mir_cslock lck(cs);

	for (SESSION_INFO *si = s_WndList; si; si = si->next)
		si->pszLogFileName[0] = si->pszLogFileName[1] = 0;

	return TRUE;
}

BOOL SM_SetStatus(const TCHAR* pszID, const char* pszModule, int wStatus)
{
	if (!pszModule)
		return FALSE;

	for (SESSION_INFO *si = s_WndList; si; si = si->next) {
		if ((!pszID || !lstrcmpi(si->ptszID, pszID)) && !lstrcmpiA(si->pszModule, pszModule)) {
			si->wStatus = wStatus;

			if (si->hContact) {
				if (si->iType != GCW_SERVER && wStatus != ID_STATUS_OFFLINE)
					db_unset(si->hContact, "CList", "Hidden");

				db_set_w(si->hContact, si->pszModule, "Status", (WORD)wStatus);
			}

			if (pszID)
				return TRUE;
		}
	}

	return TRUE;
}

BOOL SM_SendUserMessage(const TCHAR* pszID, const char* pszModule, const TCHAR* pszText)
{
	if (!pszModule || !pszText)
		return FALSE;

	for (SESSION_INFO *si = s_WndList; si; si = si->next) {
		if ((!pszID || !lstrcmpi(si->ptszID, pszID)) && !lstrcmpiA(si->pszModule, pszModule)) {
			if (si->iType == GCW_CHATROOM)
				DoEventHook(si->ptszID, si->pszModule, GC_USER_MESSAGE, NULL, pszText, 0);
			if (pszID)
				return TRUE;
		}
	}

	return TRUE;
}

BOOL SM_ChangeUID(const TCHAR* pszID, const char* pszModule, const TCHAR* pszUID, const TCHAR* pszNewUID)
{
	if (!pszModule)
		return FALSE;

	for (SESSION_INFO *si = s_WndList; si; si = si->next) {
		if ((!pszID || !lstrcmpi(si->ptszID, pszID)) && !lstrcmpiA(si->pszModule, pszModule)) {
			USERINFO* ui = UM_FindUser(si->pUsers, pszUID);
			if (ui)
				replaceStrT(ui->pszUID, pszNewUID);

			if (pszID)
				return TRUE;
		}
	}

	return TRUE;
}

BOOL SM_ChangeNick(const TCHAR* pszID, const char* pszModule, GCEVENT *gce)
{
	if (!pszModule)
		return FALSE;

	for (SESSION_INFO *si = s_WndList; si; si = si->next) {
		if ((!pszID || !lstrcmpi(si->ptszID, pszID)) && !lstrcmpiA(si->pszModule, pszModule)) {
			USERINFO* ui = UM_FindUser(si->pUsers, gce->ptszUID);
			if (ui) {
				replaceStrT(ui->pszNick, gce->ptszText);
				SM_MoveUser(si->ptszID, si->pszModule, ui->pszUID);
				if (si->hWnd) {
					SendMessage(si->hWnd, GC_UPDATENICKLIST, 0, 0);
					if (si->dat)
						GetMyNick(si->dat);
					SendMessage(si->hWnd, GC_UPDATESTATUSBAR, 0, 0);
				}
			}
			if (pszID)
				return TRUE;
		}
	}

	return TRUE;
}

BOOL SM_RemoveAll(void)
{
	while (s_WndList) {
		SESSION_INFO *pLast = s_WndList->next;

		if (s_WndList->hWnd)
			SendMessage(s_WndList->hWnd, GC_EVENT_CONTROL + WM_USER + 500, SESSION_TERMINATE, 0);
		DoEventHook(s_WndList->ptszID, s_WndList->pszModule, GC_SESSION_TERMINATE, NULL, NULL, (DWORD)s_WndList->dwItemData);
		if (s_WndList->hContact)
			CList_SetOffline(s_WndList->hContact, s_WndList->iType == GCW_CHATROOM ? TRUE : FALSE);
		db_set_s(s_WndList->hContact, s_WndList->pszModule , "Topic", "");
		db_unset(s_WndList->hContact, "CList", "StatusMsg");
		db_set_s(s_WndList->hContact, s_WndList->pszModule, "StatusBar", "");

		UM_RemoveAll(&s_WndList->pUsers);
		TM_RemoveAll(&s_WndList->pStatuses);
		LM_RemoveAll(&s_WndList->pLog, &s_WndList->pLogEnd);
		s_WndList->iStatusCount = 0;
		s_WndList->nUsersInNicklist = 0;

		mir_free(s_WndList->pszModule);
		mir_free(s_WndList->ptszID);
		mir_free(s_WndList->ptszName);
		mir_free(s_WndList->ptszStatusbarText);
		mir_free(s_WndList->ptszTopic);

		while (s_WndList->lpCommands != NULL) {
			COMMAND_INFO *pNext = s_WndList->lpCommands->next;
			mir_free(s_WndList->lpCommands->lpCommand);
			mir_free(s_WndList->lpCommands);
			s_WndList->lpCommands = pNext;
		}

		mir_free(s_WndList);
		s_WndList = pLast;
	}
	s_WndList = NULL;
	return TRUE;
}

void SM_AddCommand(const TCHAR* pszID, const char* pszModule, const char* lpNewCommand)
{
	for (SESSION_INFO *si = s_WndList; si; si = si->next) {
		if (lstrcmpi(si->ptszID, pszID) == 0 && lstrcmpiA(si->pszModule, pszModule) == 0) {      // match
			COMMAND_INFO *node = (COMMAND_INFO *)mir_alloc(sizeof(COMMAND_INFO));
			node->lpCommand = mir_strdup(lpNewCommand);
			node->last = NULL; // always added at beginning!
			// new commands are added at start
			if (si->lpCommands == NULL) {
				node->next = NULL;
				si->lpCommands = node;
			}
			else {
				node->next = si->lpCommands;
				si->lpCommands->last = node; // hmm, weird
				si->lpCommands = node;
			}
			si->lpCurrentCommand = NULL; // current command
			si->wCommandsNum++;

			if (si->wCommandsNum > WINDOWS_COMMANDS_MAX) {
				COMMAND_INFO *pCurComm = si->lpCommands;
				COMMAND_INFO *pLast;
				while (pCurComm->next != NULL) {
					pCurComm = pCurComm->next;
				}
				pLast = pCurComm->last;
				mir_free(pCurComm->lpCommand);
				mir_free(pCurComm);
				pLast->next = NULL;
				// done
				si->wCommandsNum--;
			}
		}
	}
}

char* SM_GetPrevCommand(const TCHAR* pszID, const char* pszModule) // get previous command. returns NULL if previous command does not exist. current command remains as it was.
{
	SESSION_INFO *si = SM_FindSession(pszID, pszModule);
	if (si == NULL)
		return NULL;
	
	COMMAND_INFO *pPrevCmd = NULL;
	if (si->lpCurrentCommand != NULL) {
		if (si->lpCurrentCommand->next != NULL) // not NULL
			pPrevCmd = si->lpCurrentCommand->next; // next command (newest at beginning)
		else
			pPrevCmd = si->lpCurrentCommand;
	}
	else pPrevCmd = si->lpCommands;

	si->lpCurrentCommand = pPrevCmd; // make it the new command
	return pPrevCmd ? pPrevCmd->lpCommand : NULL;
}

char* SM_GetNextCommand(const TCHAR* pszID, const char* pszModule) // get next command. returns NULL if next command does not exist. current command becomes NULL (a prev command after this one will get you the last command)
{
	SESSION_INFO *si = SM_FindSession(pszID, pszModule);
	if (si == NULL)
		return NULL;

	COMMAND_INFO *pNextCmd = NULL;
	if (si->lpCurrentCommand != NULL)
		pNextCmd = si->lpCurrentCommand->last; // last command (newest at beginning)
	
	si->lpCurrentCommand = pNextCmd; // make it the new command
	return pNextCmd ? pNextCmd->lpCommand : NULL;
}

int SM_GetCount(const char* pszModule)
{
	int count = 0;

	for (SESSION_INFO *si = s_WndList; si; si = si->next)
		if (!lstrcmpiA(pszModule, si->pszModule))
			count++;

	return count;
}

SESSION_INFO* SM_FindSessionByHWND(HWND hWnd)
{
	for (SESSION_INFO *si = s_WndList; si; si = si->next)
		if (si->hWnd == hWnd)
			return si;

	return NULL;
}

SESSION_INFO* SM_FindSessionByHCONTACT(HANDLE h)
{
	for (SESSION_INFO *si = s_WndList; si; si = si->next)
		if (si->hContact == h)
			return si;

	return NULL;
}

SESSION_INFO* SM_FindSessionByIndex(const char* pszModule, int iItem)
{
	int count = 0;
	for (SESSION_INFO *si = s_WndList; si; si = si->next) {
		if (!lstrcmpiA(pszModule, si->pszModule)) {
			if (iItem == count)
				return si;

			count++;
		}
	}

	return NULL;
}

SESSION_INFO* SM_FindSessionAutoComplete(const char* pszModule, SESSION_INFO* currSession, SESSION_INFO* prevSession, const TCHAR* pszOriginal, const TCHAR* pszCurrent)
{
	if (prevSession == NULL && my_strstri(currSession->ptszName, pszOriginal) == currSession->ptszName)
		return currSession;

	TCHAR* pszName = NULL;
	if (currSession == prevSession)
		pszCurrent = pszOriginal;

	SESSION_INFO *pResult = NULL;
	for (SESSION_INFO *si = s_WndList; si; si = si->next)
		if (si != currSession && !lstrcmpiA(pszModule, si->pszModule))
			if (my_strstri(si->ptszName, pszOriginal) == si->ptszName)
				if (prevSession != si && lstrcmpi(si->ptszName, pszCurrent) > 0 && (!pszName || lstrcmpi(si->ptszName, pszName) < 0)) {
					pResult = si;
					pszName = si->ptszName;
				}

	return pResult;
}

char* SM_GetUsers(SESSION_INFO *si)
{
	if (si == NULL)
		return NULL;

	for (SESSION_INFO *psi = s_WndList; psi; psi = psi->next)
		if (psi == si)
			goto LBL_Found;
	return NULL;

LBL_Found:
	USERINFO *utemp = si->pUsers;
	if (utemp == NULL)
		return NULL;

	char* p = NULL;
	int alloced = 0;

	while (utemp != NULL) {
		int pLen = lstrlenA(p), nameLen = lstrlen(utemp->pszUID);
		if (pLen + nameLen + 2 > alloced)
			p = (char *)mir_realloc(p, alloced += 4096);
		WideCharToMultiByte(CP_ACP, 0, utemp->pszUID, -1, p + pLen, nameLen + 1, 0, 0);
		lstrcpyA(p + pLen + nameLen, " ");
		utemp = utemp->next;
	}
		
	return p;
}

//---------------------------------------------------
//		Module Manager functions
//
//		Necessary to keep track of all modules
//		that has registered with the plugin
//---------------------------------------------------

MODULEINFO* MM_AddModule(const char* pszModule)
{
	if (!pszModule)
		return NULL;
	if (!MM_FindModule(pszModule)) {
		MODULEINFO *node = (MODULEINFO*) mir_alloc(sizeof(MODULEINFO));
		ZeroMemory(node, sizeof(MODULEINFO));

		node->pszModule = (char*)mir_alloc(lstrlenA(pszModule) + 1);
		lstrcpyA(node->pszModule, pszModule);
		node->idleTimeStamp = time(0);
		if (s_ModList == NULL) { // list is empty
			s_ModList = node;
			node->next = NULL;
		} else {
			node->next = s_ModList;
			s_ModList = node;
		}
		return node;
	}
	return FALSE;
}

void MM_FontsChanged(void)
{
	MODULEINFO *pTemp = s_ModList;
	while (pTemp != NULL) {
		pTemp->pszHeader = Log_CreateRtfHeader(pTemp);
		pTemp = pTemp->next;
	}
	return;
}

MODULEINFO* MM_FindModule(const char* pszModule)
{
	MODULEINFO *pTemp = s_ModList;

	if (!pszModule)
		return NULL;

	while (pTemp != NULL) {
		if (lstrcmpiA(pTemp->pszModule, pszModule) == 0)
			return pTemp;

		pTemp = pTemp->next;
	}
	return 0;
}

// stupid thing..
void MM_FixColors()
{
	MODULEINFO *pTemp = s_ModList;

	while (pTemp != NULL) {
		CheckColorsInModule(pTemp->pszModule);
		pTemp = pTemp->next;
	}
	return;
}

BOOL MM_RemoveAll(void)
{
	while (s_ModList != NULL) {
		MODULEINFO *pLast = s_ModList->next;
		mir_free(s_ModList->pszModule);
		mir_free(s_ModList->ptszModDispName);
		if (s_ModList->pszHeader)
			mir_free(s_ModList->pszHeader);
		mir_free(s_ModList->crColors);

		mir_free(s_ModList);
		s_ModList = pLast;
	}
	s_ModList = NULL;
	return TRUE;
}

//---------------------------------------------------
//		Status manager functions
//
//		Necessary to keep track of what user statuses
//		per window nicklist that is available
//---------------------------------------------------

STATUSINFO * TM_AddStatus(STATUSINFO** ppStatusList, const TCHAR* pszStatus, int* iCount)
{
	if (!ppStatusList || !pszStatus)
		return NULL;

	if (!TM_FindStatus(*ppStatusList, pszStatus)) {
		STATUSINFO *node = (STATUSINFO*)mir_calloc(sizeof(STATUSINFO));
		node->pszGroup = mir_tstrdup(pszStatus);
		node->hIcon = (HICON)(*iCount);
		while ((int)node->hIcon > STATUSICONCOUNT - 1)
			node->hIcon--;

		if (*ppStatusList == NULL) { // list is empty
			node->Status = 1;
			*ppStatusList = node;
			node->next = NULL;
		} else {
			node->Status = ppStatusList[0]->Status * 2;
			node->next = *ppStatusList;
			*ppStatusList = node;
		}
		return node;

	}
	return FALSE;
}

STATUSINFO * TM_FindStatus(STATUSINFO* pStatusList, const TCHAR* pszStatus)
{
	if (!pStatusList || !pszStatus)
		return NULL;

	for (STATUSINFO *si = pStatusList; si != NULL; si = si->next)
		if (lstrcmpi(si->pszGroup, pszStatus) == 0)
			return si;

	return 0;
}

WORD TM_StringToWord(STATUSINFO* pStatusList, const TCHAR* pszStatus)
{
	if (!pStatusList || !pszStatus)
		return 0;

	for (STATUSINFO *si = pStatusList; si != NULL; si = si->next) {
		if (lstrcmpi(si->pszGroup, pszStatus) == 0)
			return si->Status;

		if (si->next == NULL)
			return pStatusList->Status;
	}
	return 0;
}

TCHAR* TM_WordToString(STATUSINFO* pStatusList, WORD Status)
{
	if (!pStatusList)
		return NULL;

	for (STATUSINFO *si = pStatusList; si != NULL; si = si->next)
		if (si->Status&Status) {
			Status -= si->Status;
			if (Status == 0)
				return si->pszGroup;
		}

	return 0;
}

BOOL TM_RemoveAll(STATUSINFO** ppStatusList)
{
	if (!ppStatusList)
		return FALSE;

	while (*ppStatusList != NULL) {
		STATUSINFO *pLast = ppStatusList[0]->next;
		mir_free(ppStatusList[0]->pszGroup);
		if ((int)ppStatusList[0]->hIcon > 10)
			DestroyIcon(ppStatusList[0]->hIcon);
		mir_free(*ppStatusList);
		*ppStatusList = pLast;
	}
	*ppStatusList = NULL;
	return TRUE;
}

//---------------------------------------------------
//		User manager functions
//
//		Necessary to keep track of the users
//		in a window nicklist
//---------------------------------------------------

//MAD: alternative sorting by Nullbie
static int sttCompareNicknames(const TCHAR *s1, const TCHAR *s2)
{
	if (!s1 && !s2) return 0;
	if (!s1 && s2) return +1;
	if (s1 && !s2) return -1;

	// skip rubbish
	while (*s1 && !_istalpha(*s1)) ++s1;
	while (*s2 && !_istalpha(*s2)) ++s2;

	// are there ~0veRy^kEwL_n1kz?
	if (!*s1 && !*s2) return 0;
	if (!*s1 && *s2) return +1;
	if (*s1 && !*s2) return -1;

	// compare tails
	return lstrcmpi(s1, s2);
}
//

static int UM_CompareItem(USERINFO * u1, const TCHAR* pszNick, WORD wStatus)
{
	WORD dw1 = u1->Status;
	WORD dw2 = wStatus;

	for (int i=0; i < 8; i++) {
		if ((dw1 & 1) && !(dw2 & 1))
			return -1;

		if ((dw2 & 1) && !(dw1 & 1))
			return 1;

		if ((dw1 & 1) && (dw2 & 1)) {
			if (g_Settings.bAlternativeSorting)
				return sttCompareNicknames(u1->pszNick, pszNick);
			return lstrcmp(u1->pszNick, pszNick);
		}
		dw1 = dw1 >> 1;
		dw2 = dw2 >> 1;
	}
	if (g_Settings.bAlternativeSorting)
		return sttCompareNicknames(u1->pszNick, pszNick);
	return lstrcmp(u1->pszNick, pszNick);
}

USERINFO* UM_SortUser(USERINFO** ppUserList, const TCHAR* pszUID)
{
	USERINFO *pTemp = *ppUserList, *pLast = NULL;
	if (!pTemp || !pszUID)
		return NULL;

	while (pTemp && lstrcmpi(pTemp->pszUID, pszUID)) {
		pLast = pTemp;
		pTemp = pTemp->next;
	}

	if (pTemp == NULL)
		return NULL;

	USERINFO *node = pTemp;
	if (pLast)
		pLast->next = pTemp->next;
	else
		*ppUserList = pTemp->next;
	pTemp = *ppUserList;

	pLast = NULL;

	while (pTemp && UM_CompareItem(pTemp, node->pszNick, node->Status) <= 0) {
		pLast = pTemp;
		pTemp = pTemp->next;
	}

	if (*ppUserList == NULL) { // list is empty
		*ppUserList = node;
		node->next = NULL;
	}
	else if (pLast) {
		node->next = pTemp;
		pLast->next = node;
	}
	else {
		node->next = *ppUserList;
		*ppUserList = node;
	}

	return node;
}

USERINFO* UM_AddUser(STATUSINFO* pStatusList, USERINFO** ppUserList, const TCHAR* pszUID, const TCHAR* pszNick, WORD wStatus)
{
	if (!pStatusList || !ppUserList)
		return NULL;

	USERINFO *pTemp = *ppUserList, *pLast = NULL;
	while (pTemp && UM_CompareItem(pTemp, pszNick, wStatus) <= 0) {
		pLast = pTemp;
		pTemp = pTemp->next;
	}

	USERINFO *node = (USERINFO*)mir_calloc(sizeof(USERINFO));
	node->pszUID = mir_tstrdup(pszUID);

	if (*ppUserList == NULL) { // list is empty
		*ppUserList = node;
		node->next = NULL;
	}
	else if (pLast) {
		node->next = pTemp;
		pLast->next = node;
	}
	else {
		node->next = *ppUserList;
		*ppUserList = node;
	}

	return node;
}

USERINFO* UM_FindUser(USERINFO* pUserList, const TCHAR* pszUID)
{
	if (!pUserList || !pszUID)
		return NULL;

	for (USERINFO *pTemp = pUserList; pTemp != NULL; pTemp = pTemp->next)
		if (!lstrcmpi(pTemp->pszUID, pszUID))
			return pTemp;

	return 0;
}

USERINFO* UM_FindUserFromIndex(USERINFO* pUserList, int index)
{
	if (!pUserList)
		return NULL;

	int i=0;
	for (USERINFO *pTemp = pUserList; pTemp != NULL; pTemp = pTemp->next, i++)
		if (i == index)
			return pTemp;

	return NULL;
}

USERINFO* UM_GiveStatus(USERINFO* pUserList, const TCHAR* pszUID, WORD status)
{
	if (!pUserList || !pszUID)
		return NULL;

	for (USERINFO *pTemp = pUserList; pTemp != NULL; pTemp = pTemp->next)
		if (!lstrcmpi(pTemp->pszUID, pszUID)) {
			pTemp->Status |= status;
			return pTemp;
		}

	return 0;
}

USERINFO* UM_SetContactStatus(USERINFO* pUserList, const TCHAR* pszUID, WORD status)
{
	if (!pUserList || !pszUID)
		return NULL;

	for (USERINFO *pTemp = pUserList; pTemp != NULL; pTemp = pTemp->next)
		if (!lstrcmpi(pTemp->pszUID, pszUID)) {
			pTemp->ContactStatus = status;
			return pTemp;
		}

	return 0;
}

BOOL UM_SetStatusEx(USERINFO* pUserList, const TCHAR* pszText, int flags)
{
	bool bOnlyMe = (flags & GC_SSE_ONLYLISTED) != 0, bAwaySetStatus = (flags & GC_SSE_ONLINE) != 0, bOfflineSetStatus = (flags & GC_SSE_OFFLINE) != 0;
	char cDelimiter = (flags & GC_SSE_TABDELIMITED) ? '\t' : ' ';

	for (USERINFO *p = pUserList; p != NULL; p = p->next) {
		if (!bOnlyMe)
			p->iStatusEx = CHAT_STATUS_NORMAL;

		if (pszText == NULL)
			continue;
			
		TCHAR* s = (TCHAR*)_tcsstr(pszText, p->pszUID);
		if (s == NULL)
			continue;

		p->iStatusEx = CHAT_STATUS_NORMAL;
		if (s == pszText || s[-1] == cDelimiter) {
			int len = lstrlen(p->pszUID);
			if (s[len] == cDelimiter || s[len] == '\0') {
				if (!bOnlyMe || bAwaySetStatus)
					p->iStatusEx = CHAT_STATUS_AWAY;
				else if (bOfflineSetStatus)
					p->iStatusEx = CHAT_STATUS_OFFLINE;
			}
		}
	}
	return TRUE;
}

USERINFO* UM_TakeStatus(USERINFO* pUserList, const TCHAR* pszUID, WORD status)
{
	if (!pUserList || !pszUID)
		return NULL;

	for (USERINFO *pTemp = pUserList; pTemp != NULL; pTemp = pTemp->next)
		if (!lstrcmpi(pTemp->pszUID, pszUID)) {
			pTemp->Status &= ~status;
			return pTemp;
		}

	return 0;
}

TCHAR* UM_FindUserAutoComplete(USERINFO* pUserList, const TCHAR* pszOriginal, const TCHAR* pszCurrent)
{
	if (!pUserList || !pszOriginal || !pszCurrent)
		return NULL;

	TCHAR *pszName = NULL;
	for (USERINFO *pTemp = pUserList; pTemp != NULL; pTemp = pTemp->next)
		if (my_strstri(pTemp->pszNick, pszOriginal) == pTemp->pszNick)
			if (lstrcmpi(pTemp->pszNick, pszCurrent) > 0 && (!pszName || lstrcmpi(pTemp->pszNick, pszName) < 0))
				pszName = pTemp->pszNick;

	return pszName;
}

BOOL UM_RemoveUser(USERINFO** ppUserList, const TCHAR* pszUID)
{
	if (!ppUserList || !pszUID)
		return FALSE;

	USERINFO *pTemp = *ppUserList, *pLast = NULL;

	while (pTemp != NULL) {
		if (!lstrcmpi(pTemp->pszUID, pszUID)) {
			if (pLast == NULL)
				*ppUserList = pTemp->next;
			else
				pLast->next = pTemp->next;
			mir_free(pTemp->pszNick);
			mir_free(pTemp->pszUID);
			mir_free(pTemp);
			return TRUE;
		}
		pLast = pTemp;
		pTemp = pTemp->next;
	}
	return FALSE;
}

BOOL UM_RemoveAll(USERINFO** ppUserList)
{
	if (!ppUserList)
		return FALSE;

	while (*ppUserList != NULL) {
		USERINFO *pLast = ppUserList[0]->next;
		mir_free(ppUserList[0]->pszUID);
		mir_free(ppUserList[0]->pszNick);
		mir_free(*ppUserList);
		*ppUserList = pLast;
	}
	*ppUserList = NULL;
	return TRUE;
}

//---------------------------------------------------
//		Log manager functions
//
//		Necessary to keep track of events
//		in a window log
//---------------------------------------------------

LOGINFO * LM_AddEvent(LOGINFO** ppLogListStart, LOGINFO** ppLogListEnd)
{
	if (!ppLogListStart || !ppLogListEnd)
		return NULL;

	LOGINFO *node = (LOGINFO*)mir_calloc(sizeof(LOGINFO));

	if (*ppLogListStart == NULL) { // list is empty
		*ppLogListStart = node;
		*ppLogListEnd = node;
		node->next = NULL;
		node->prev = NULL;
	} else {
		ppLogListStart[0]->prev = node;
		node->next = *ppLogListStart;
		*ppLogListStart = node;
		ppLogListStart[0]->prev = NULL;
	}

	return node;
}

BOOL LM_TrimLog(LOGINFO** ppLogListStart, LOGINFO** ppLogListEnd, int iCount)
{
	LOGINFO *pTemp = *ppLogListEnd;
	while (pTemp != NULL && iCount > 0) {
		*ppLogListEnd = pTemp->prev;
		if (*ppLogListEnd == NULL)
			*ppLogListStart = NULL;

		mir_free(pTemp->ptszNick);
		mir_free(pTemp->ptszUserInfo);
		mir_free(pTemp->ptszText);
		mir_free(pTemp->ptszStatus);
		mir_free(pTemp);
		pTemp = *ppLogListEnd;
		iCount--;
	}
	ppLogListEnd[0]->next = NULL;

	return TRUE;
}

BOOL LM_RemoveAll(LOGINFO** ppLogListStart, LOGINFO** ppLogListEnd)
{
	while (*ppLogListStart != NULL) {
		LOGINFO *pLast = ppLogListStart[0]->next;
		mir_free(ppLogListStart[0]->ptszText);
		mir_free(ppLogListStart[0]->ptszNick);
		mir_free(ppLogListStart[0]->ptszStatus);
		mir_free(ppLogListStart[0]->ptszUserInfo);
		mir_free(*ppLogListStart);
		*ppLogListStart = pLast;
	}
	*ppLogListStart = NULL;
	*ppLogListEnd = NULL;
	return TRUE;
}