/*

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

Copyright (c) 2012-14 Miranda NG project (http://miranda-ng.org),
Copyright (c) 2000-12 Miranda 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 "..\..\core\commonheaders.h"
#include "clc.h"

struct CListEvent
{
	int imlIconIndex;
	int flashesDone;
	CLISTEVENT cle;
};

struct CListImlIcon
{
	int index;
	HICON hIcon;
};
static struct CListImlIcon *imlIcon;
static int imlIconCount;

extern HIMAGELIST hCListImages;

static UINT_PTR flashTimerId;
static int iconsOn;
static int disableTrayFlash;
static int disableIconFlash;

int fnGetImlIconIndex(HICON hIcon)
{
	int i;
	for (i=0; i < imlIconCount; i++)
		if (imlIcon[i].hIcon == hIcon)
			return imlIcon[i].index;

	imlIcon = (struct CListImlIcon *) mir_realloc(imlIcon, sizeof(struct CListImlIcon) * (imlIconCount + 1));
	imlIconCount++;
	imlIcon[i].hIcon = hIcon;
	imlIcon[i].index = ImageList_AddIcon(hCListImages, hIcon);
	return imlIcon[i].index;
}

static char * GetEventProtocol(int idx)
{
	if (!cli.events.count || idx < 0 && idx >= cli.events.count)
		return NULL;

	CListEvent *ev = cli.events.items[idx];
	if (ev->cle.hContact != NULL)
		return GetContactProto(ev->cle.hContact);
		
	return (ev->cle.flags & CLEF_PROTOCOLGLOBAL) ? ev->cle.lpszProtocol : NULL;
}

static void ShowOneEventInTray(int idx)
{
	cli.pfnTrayIconUpdateWithImageList((iconsOn || disableTrayFlash) ? cli.events.items[idx]->imlIconIndex : 0, cli.events.items[idx]->cle.ptszTooltip, GetEventProtocol(idx));
}

static void ShowEventsInTray()
{
	int nTrayCnt = cli.trayIconCount;
	if (!cli.events.count || !nTrayCnt)  return;
	if (cli.events.count == 1 || nTrayCnt == 1) {
		ShowOneEventInTray(0); //for only one icon in tray show topmost event
		return;
	}

	// in case if we have several icons in tray and several events with different protocols
	// lets use several icon to show events from protocols in different icons
	cli.pfnLockTray();
	char **pTrayProtos = (char**)_alloca(sizeof(char*)*cli.trayIconCount);
	int nTrayProtoCnt = 0;
	for (int i = 0; i < cli.trayIconCount; i++) {
		if (cli.trayIcon[i].id == 0 || !cli.trayIcon[i].szProto) continue;
		pTrayProtos[nTrayProtoCnt++] = cli.trayIcon[i].szProto;
	}

	for (int i = 0; i < cli.events.count; i++) {
		char *iEventProto = GetEventProtocol(i);

		int j;
		for (j = 0; j < nTrayProtoCnt; j++)
			if (iEventProto && pTrayProtos[j] && !lstrcmpA(pTrayProtos[j], iEventProto))
				break;
		if (j >= nTrayProtoCnt)   // event was not found so assume first icon
			j = 0;
		if (pTrayProtos[j])       // if not already set
			ShowOneEventInTray(i); // show it
		pTrayProtos[j] = NULL;    // and clear slot
	}
	cli.pfnUnlockTray();
}

static VOID CALLBACK IconFlashTimer(HWND, UINT, UINT_PTR idEvent, DWORD)
{
	ShowEventsInTray();

	for (int i=0; i < cli.events.count; i++) {
		int j;
		for (j = 0; j < i; j++)
			if (cli.events.items[j]->cle.hContact == cli.events.items[i]->cle.hContact)
				break;
		if (j >= i)
			cli.pfnChangeContactIcon(cli.events.items[i]->cle.hContact, iconsOn || disableIconFlash ? cli.events.items[i]->imlIconIndex : 0, 0);
		
		// decrease eflashes in any case - no need to collect all events
		if (cli.events.items[i]->cle.flags & CLEF_ONLYAFEW)
			if (0 >= --cli.events.items[i]->flashesDone)
				cli.pfnRemoveEvent(cli.events.items[i]->cle.hContact, cli.events.items[i]->cle.hDbEvent);
	}

	if (cli.events.count == 0) {
		KillTimer(NULL, idEvent);
		cli.pfnTrayIconSetToBase(NULL);
	}

	iconsOn = !iconsOn;
}

CListEvent* fnAddEvent(CLISTEVENT *cle)
{
	int i;

	if (cle == NULL || cle->cbSize != sizeof(CLISTEVENT))
		return NULL;

	if (cle->flags & CLEF_URGENT) {
		for (i=0; i < cli.events.count; i++)
			if (!(cli.events.items[i]->cle.flags & CLEF_URGENT))
				break;
	}
	else i = cli.events.count;

	CListEvent *p = cli.pfnCreateEvent();
	if (p == NULL)
		return NULL;

	List_Insert((SortedList*)&cli.events, p, i);
	p->cle = *cle;
	p->imlIconIndex = fnGetImlIconIndex(cli.events.items[i]->cle.hIcon);
	p->flashesDone = 12;
	p->cle.pszService = mir_strdup(cli.events.items[i]->cle.pszService);
	if (p->cle.flags & CLEF_UNICODE)
		p->cle.ptszTooltip = mir_tstrdup(p->cle.ptszTooltip);
	else
		p->cle.ptszTooltip = mir_a2u(p->cle.pszTooltip); //if no flag defined it handled as unicode
	if (cli.events.count == 1) {
		char *szProto;
		if (cle->hContact == NULL) {
			if (cle->flags & CLEF_PROTOCOLGLOBAL)
				szProto = cle->lpszProtocol;
			else
				szProto = NULL;
		}
		else szProto = GetContactProto(cle->hContact);

		iconsOn = 1;
		flashTimerId = SetTimer(NULL, 0, db_get_w(NULL, "CList", "IconFlashTime", 550), IconFlashTimer);
		cli.pfnTrayIconUpdateWithImageList(p->imlIconIndex, p->cle.ptszTooltip, szProto);
	}
	cli.pfnChangeContactIcon(cle->hContact, p->imlIconIndex, 1);
	cli.pfnSortContacts();
	return p;
}

// Removes an event from the contact list's queue
// Returns 0 if the event was successfully removed, or nonzero if the event was not found
int fnRemoveEvent(MCONTACT hContact, HANDLE dbEvent)
{
	// Find the event that should be removed
	int i;
	for (i=0; i < cli.events.count; i++)
		if ((cli.events.items[i]->cle.hContact == hContact) && (cli.events.items[i]->cle.hDbEvent == dbEvent))
			break;

	// Event was not found
	if (i == cli.events.count)
		return 1;

	// Update contact's icon
	char *szProto = GetContactProto(hContact);
	cli.pfnChangeContactIcon(cli.events.items[i]->cle.hContact,
		CallService(MS_CLIST_GETCONTACTICON, (WPARAM)cli.events.items[i]->cle.hContact, 1), 0);

	// Free any memory allocated to the event
	cli.pfnFreeEvent(cli.events.items[i]);
	List_Remove((SortedList*)&cli.events, i);

	//count same protocoled events
	int nSameProto = 0;
	char *szEventProto;
	for (int i = 0; i < cli.events.count; i++) {
		if (cli.events.items[i]->cle.hContact)
			szEventProto = GetContactProto((cli.events.items[i]->cle.hContact));
		else if (cli.events.items[i]->cle.flags & CLEF_PROTOCOLGLOBAL)
			szEventProto = (char *)cli.events.items[i]->cle.lpszProtocol;
		else
			szEventProto = NULL;
		if (szEventProto && szProto && !lstrcmpA(szEventProto, szProto))
			nSameProto++;
	}

	if (cli.events.count == 0 || nSameProto == 0) {
		if (cli.events.count == 0)
			KillTimer(NULL, flashTimerId);
		cli.pfnTrayIconSetToBase(hContact == NULL ? NULL : szProto);
	}
	else {
		if (cli.events.items[0]->cle.hContact == NULL)
			szProto = NULL;
		else
			szProto = GetContactProto(cli.events.items[0]->cle.hContact);
		cli.pfnTrayIconUpdateWithImageList(iconsOn ? cli.events.items[0]->imlIconIndex : 0, cli.events.items[0]->cle.ptszTooltip, szProto);
	}

	return 0;
}

CLISTEVENT* fnGetEvent(MCONTACT hContact, int idx)
{
	if (hContact == INVALID_CONTACT_ID) {
		if (idx >= cli.events.count)
			return NULL;
		return &cli.events.items[idx]->cle;
	}

	for (int i=0; i < cli.events.count; i++)
		if (cli.events.items[i]->cle.hContact == hContact)
			if (idx-- == 0)
				return &cli.events.items[i]->cle;
	return NULL;
}

int fnEventsProcessContactDoubleClick(MCONTACT hContact)
{
	for (int i=0; i < cli.events.count; i++) {
		if (cli.events.items[i]->cle.hContact == hContact) {
			HANDLE hDbEvent = cli.events.items[i]->cle.hDbEvent;
			CallService(cli.events.items[i]->cle.pszService, (WPARAM)(HWND)NULL, (LPARAM)& cli.events.items[i]->cle);
			cli.pfnRemoveEvent(hContact, hDbEvent);
			return 0;
		}
	}

	return 1;
}

int fnEventsProcessTrayDoubleClick(int index)
{
	BOOL click_in_first_icon = FALSE;
	if (cli.events.count) {
		MCONTACT hContact;
		HANDLE hDbEvent;
		int eventIndex = 0;
		cli.pfnLockTray();
		if (cli.trayIconCount > 1 && index > 0) {
			int i;
			char *szProto = NULL;
			for (i = 0; i < cli.trayIconCount; i++)
				if (cli.trayIcon[i].id == index) {
					szProto = cli.trayIcon[i].szProto;
					if (i == 0) click_in_first_icon = TRUE;
					break;
				}
			if (szProto) {
				for (i = 0; i < cli.events.count; i++) {
					char * eventProto = NULL;
					if (cli.events.items[i]->cle.hContact)
						eventProto = GetContactProto(cli.events.items[i]->cle.hContact);
					if (!eventProto)
						eventProto = cli.events.items[i]->cle.lpszProtocol;

					if (!eventProto || !_strcmpi(eventProto, szProto)) {
						eventIndex = i;
						break;
					}
				}

				if (i == cli.events.count) { //EventNotFound
					//lets  process backward try to find first event without desired proto in tray
					int j;
					if (click_in_first_icon)
						for (i = 0; i < cli.events.count; i++) {
							char *eventProto = NULL;
							if (cli.events.items[i]->cle.hContact)
								eventProto = GetContactProto(cli.events.items[i]->cle.hContact);
							if (!eventProto)
								eventProto = cli.events.items[i]->cle.lpszProtocol;
							if (eventProto) {
								for (j = 0; j < cli.trayIconCount; j++)
									if (cli.trayIcon[j].szProto && !_strcmpi(eventProto, cli.trayIcon[j].szProto))
										break;

								if (j == cli.trayIconCount) {
									eventIndex = i;
									break;
								}
							}
						}
					if (i == cli.events.count) { //not found
						cli.pfnUnlockTray();
						return 1;	//continue processing to show contact list
					}
				}
			}
		}

		cli.pfnUnlockTray();
		hContact = cli.events.items[eventIndex]->cle.hContact;
		hDbEvent = cli.events.items[eventIndex]->cle.hDbEvent;
		//	; may be better to show send msg?
		CallService(cli.events.items[eventIndex]->cle.pszService, (WPARAM)NULL, (LPARAM)& cli.events.items[eventIndex]->cle);
		cli.pfnRemoveEvent(hContact, hDbEvent);
		return 0;
	}
	return 1;
}

static int RemoveEventsForContact(WPARAM wParam, LPARAM)
{
	int j, hit;

	/*
	the for (;;) loop is used here since the cli.events.count can not be relied upon to take us
	thru the cli.events.items[] array without suffering from shortsightedness about how many unseen
	events remain, e.g. three events, we remove the first, we're left with 2, the event
	loop exits at 2 and we never see the real new 2.
	*/

	for (; cli.events.count > 0;) {
		for (hit = 0, j = 0; j < cli.events.count; j++) {
			if (cli.events.items[j]->cle.hContact == wParam) {
				cli.pfnRemoveEvent(wParam, cli.events.items[j]->cle.hDbEvent);
				hit = 1;
			}
		}
		if (j == cli.events.count && hit == 0)
			return 0;           /* got to the end of the array and didnt remove anything */
	}

	return 0;
}

static int CListEventSettingsChanged(WPARAM hContact, LPARAM lParam)
{
	DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING*)lParam;
	if (hContact == NULL && cws && cws->szModule && cws->szSetting && strcmp(cws->szModule, "CList") == 0) {
		if (strcmp(cws->szSetting, "DisableTrayFlash") == 0)
			disableTrayFlash = (int)cws->value.bVal;
		else if (strcmp(cws->szSetting, "NoIconBlink") == 0)
			disableIconFlash = (int)cws->value.bVal;
	}
	return 0;
}

/***************************************************************************************/

INT_PTR AddEventSyncStub(WPARAM wParam, LPARAM lParam) { return CallServiceSync(MS_CLIST_ADDEVENT"_SYNC", wParam, lParam); }
INT_PTR AddEventStub(WPARAM, LPARAM lParam) { return cli.pfnAddEvent((CLISTEVENT*)lParam) == NULL; }
INT_PTR RemoveEventStub(WPARAM wParam, LPARAM lParam) { return cli.pfnRemoveEvent(wParam, (HANDLE)lParam); }
INT_PTR GetEventStub(WPARAM wParam, LPARAM lParam) { return (INT_PTR)cli.pfnGetEvent(wParam, (int)lParam); }

int InitCListEvents(void)
{
	memset(&cli.events, 0, sizeof(cli.events));
	cli.events.increment = 10;

	disableTrayFlash = db_get_b(NULL, "CList", "DisableTrayFlash", 0);
	disableIconFlash = db_get_b(NULL, "CList", "NoIconBlink", 0);
	CreateServiceFunction(MS_CLIST_ADDEVENT, AddEventSyncStub); //need to be called through sync to keep flash timer workable
	CreateServiceFunction(MS_CLIST_ADDEVENT"_SYNC", AddEventStub);
	CreateServiceFunction(MS_CLIST_REMOVEEVENT, RemoveEventStub);
	CreateServiceFunction(MS_CLIST_GETEVENT, GetEventStub);
	HookEvent(ME_DB_CONTACT_DELETED, RemoveEventsForContact);
	HookEvent(ME_DB_CONTACT_SETTINGCHANGED, CListEventSettingsChanged);
	return 0;
}

CListEvent* fnCreateEvent(void)
{
	return (CListEvent*)mir_calloc(sizeof(CListEvent));
}

void fnFreeEvent(CListEvent* p)
{
	mir_free(p->cle.pszService);
	mir_free(p->cle.pszTooltip);
	mir_free(p);
}

void UninitCListEvents(void)
{
	if (cli.events.count)
		KillTimer(NULL, flashTimerId);

	for (int i=0; i < cli.events.count; i++)
		cli.pfnFreeEvent((CListEvent*)cli.events.items[i]);
	List_Destroy((SortedList*)&cli.events);

	if (imlIcon != NULL)
		mir_free(imlIcon);
}