/* 
Copyright (C) 2006 Ricardo Pescuma Domenecci, Nightwish

This is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This 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
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this file; see the file license.txt.  If
not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  
*/

#include "commonheaders.h"

/*
It has 1 queue:
A queue to request items. One request is done at a time, REQUEST_WAIT_TIME miliseconts after it has beeing fired
   ACKRESULT_STATUS. This thread only requests the avatar (and maybe add it to the cache queue)
*/

#define REQUEST_WAIT_TIME 3000

// Time to wait before re-requesting an avatar that failed
#define REQUEST_FAIL_WAIT_TIME (3 * 60 * 60 * 1000)

// Time to wait before re-requesting an avatar that received an wait for
#define REQUEST_WAITFOR_WAIT_TIME (30 * 60 * 1000)

// Number of mileseconds the threads wait until take a look if it is time to request another item
#define POOL_DELAY 1000

// Number of mileseconds the threads wait after a GAIR_WAITFOR is returned
#define REQUEST_DELAY 18000


// Prototypes ///////////////////////////////////////////////////////////////////////////

static void RequestThread(void *vParam);

extern HANDLE hShutdownEvent;
extern char *g_szMetaName;
extern int ChangeAvatar(HANDLE hContact, BOOL fLoad, BOOL fNotifyHist = FALSE, int pa_format = 0);
extern int DeleteAvatar(HANDLE hContact);
extern void MakePathRelative(HANDLE hContact, TCHAR *path);
int Proto_GetDelayAfterFail(const char *proto);
BOOL Proto_IsFetchingAlwaysAllowed(const char *proto);

struct CacheNode *FindAvatarInCache(HANDLE hContact, BOOL add, BOOL findAny = FALSE);

extern HANDLE hEventContactAvatarChanged;
extern BOOL g_AvatarHistoryAvail;
extern FI_INTERFACE *fei;

#ifdef _DEBUG
int _DebugTrace(const char *fmt, ...);
int _DebugTrace(HANDLE hContact, const char *fmt, ...);
#endif

// Functions ////////////////////////////////////////////////////////////////////////////

// Items with higher priority at end
static int QueueSortItems( const QueueItem* i1, const QueueItem* i2)
{
	return i2->check_time - i1->check_time;
}

static OBJLIST<QueueItem> queue( 20, QueueSortItems );
static CRITICAL_SECTION cs;
static int waitTime;

void InitPolls() 
{
	waitTime = REQUEST_WAIT_TIME;
	InitializeCriticalSection( &cs );

	// Init request queue
	mir_forkthread(RequestThread, NULL);
}

void FreePolls()
{
}

// Return true if this protocol can have avatar requested
static BOOL PollProtocolCanHaveAvatar(const char *szProto)
{
	int pCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0);
	int status = CallProtoService(szProto, PS_GETSTATUS, 0, 0);
	return (pCaps & PF4_AVATARS)
		&& (g_szMetaName == NULL || strcmp(g_szMetaName, szProto))
		&& ((status > ID_STATUS_OFFLINE && status != ID_STATUS_INVISIBLE) || Proto_IsFetchingAlwaysAllowed(szProto));
}

// Return true if this protocol has to be checked
static BOOL PollCheckProtocol(const char *szProto)
{
	return DBGetContactSettingByte(NULL, AVS_MODULE, szProto, 1);
}

// Return true if this contact can have avatar requested
static BOOL PollContactCanHaveAvatar(HANDLE hContact, const char *szProto)
{
	int status = DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE);
	return (Proto_IsFetchingAlwaysAllowed(szProto) || status != ID_STATUS_OFFLINE)
		&& !DBGetContactSettingByte(hContact, "CList", "NotOnList", 0)
		&& DBGetContactSettingByte(hContact, "CList", "ApparentMode", 0) != ID_STATUS_OFFLINE;
}

// Return true if this contact has to be checked
static BOOL PollCheckContact(HANDLE hContact, const char *szProto)
{
	return !DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0)
			&& FindAvatarInCache(hContact, FALSE, TRUE) != NULL;
}

static void QueueRemove(HANDLE hContact)
{
	EnterCriticalSection(&cs);

	for (int i = queue.getCount()-1 ; i >= 0 ; i-- ) {
		QueueItem& item = queue[i];
		if (item.hContact == hContact)
			queue.remove(i);
	}

	LeaveCriticalSection(&cs);
}

static void QueueAdd(HANDLE hContact, int waitTime)
{
	if(fei == NULL)
		return;

	EnterCriticalSection(&cs);

	// Only add if not exists yet
	int i;
	for (i = queue.getCount()-1; i >= 0; i--)
		if ( queue[i].hContact == hContact)
			break;

	if (i < 0) {
		QueueItem *item = new QueueItem;
		if (item != NULL) {
			item->hContact = hContact;
			item->check_time = GetTickCount() + waitTime;
			queue.insert(item);
	}	}

	LeaveCriticalSection(&cs);
}

// Add an contact to a queue
void QueueAdd(HANDLE hContact)
{
	QueueAdd(hContact, waitTime);
}

void ProcessAvatarInfo(HANDLE hContact, int type, PROTO_AVATAR_INFORMATIONT *pai, const char *szProto)
{
	QueueRemove(hContact);

	if (type == GAIR_SUCCESS) 
	{
		if (pai == NULL)
			return;

		// Fix settings in DB
		DBDeleteContactSetting(hContact, "ContactPhoto", "NeedUpdate");
		DBDeleteContactSetting(hContact, "ContactPhoto", "RFile");
		if (!DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0))
			DBDeleteContactSetting(hContact, "ContactPhoto", "Backup");
		DBWriteContactSettingTString(hContact, "ContactPhoto", "File", pai->filename);
		DBWriteContactSettingWord(hContact, "ContactPhoto", "Format", pai->format);

		if (pai->format == PA_FORMAT_PNG || pai->format == PA_FORMAT_JPEG 
			|| pai->format == PA_FORMAT_ICON  || pai->format == PA_FORMAT_BMP
			|| pai->format == PA_FORMAT_GIF)
		{
			// We can load it!
			MakePathRelative(hContact, pai->filename);
			ChangeAvatar(hContact, TRUE, TRUE, pai->format);
		}
		else
		{
			// As we can't load it, notify but don't load
			ChangeAvatar(hContact, FALSE, TRUE, pai->format);
		}
	}
	else if (type == GAIR_NOAVATAR) 
	{
		DBDeleteContactSetting(hContact, "ContactPhoto", "NeedUpdate");

		if (DBGetContactSettingByte(NULL, AVS_MODULE, "RemoveAvatarWhenContactRemoves", 1)) 
		{
			// Delete settings
			DBDeleteContactSetting(hContact, "ContactPhoto", "RFile");
			if (!DBGetContactSettingByte(hContact, "ContactPhoto", "Locked", 0))
				DBDeleteContactSetting(hContact, "ContactPhoto", "Backup");
			DBDeleteContactSetting(hContact, "ContactPhoto", "File");
			DBDeleteContactSetting(hContact, "ContactPhoto", "Format");

			// Fix cache
			ChangeAvatar(hContact, FALSE, TRUE, 0);
		}
	}
	else if (type == GAIR_FAILED) 
	{
		int wait = Proto_GetDelayAfterFail(szProto);
		if (wait > 0)
		{
			// Reschedule to request after needed time (and avoid requests before that)
			EnterCriticalSection(&cs);
			QueueRemove(hContact);
			QueueAdd(hContact, wait);
			LeaveCriticalSection(&cs);
		}
	}
}

int FetchAvatarFor(HANDLE hContact, char *szProto = NULL)
{
	int result = GAIR_NOAVATAR;

	if (szProto == NULL)
		szProto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0);

	if (szProto != NULL && PollProtocolCanHaveAvatar(szProto) && PollContactCanHaveAvatar(hContact, szProto))
	{
		// Can have avatar, but must request it?
		if (
			(g_AvatarHistoryAvail && CallService(MS_AVATARHISTORY_ENABLED, (WPARAM) hContact, 0))
			 || (PollCheckProtocol(szProto) && PollCheckContact(hContact, szProto))
			)
		{
			// Request it
			PROTO_AVATAR_INFORMATIONT pai_s = {0};
			pai_s.cbSize = sizeof(pai_s);
			pai_s.hContact = hContact;
			//_DebugTrace(hContact, "schedule request");
			INT_PTR res = CallProtoService(szProto, PS_GETAVATARINFOT, GAIF_FORCE, (LPARAM)&pai_s);
#ifdef _UNICODE
			if (res == CALLSERVICE_NOTFOUND)
			{
				PROTO_AVATAR_INFORMATION pai = {0};
				pai.cbSize = sizeof(pai);
				pai.hContact = hContact;
				res = CallProtoService(szProto, PS_GETAVATARINFO, GAIF_FORCE, (LPARAM)&pai);
				MultiByteToWideChar( CP_ACP, 0, pai.filename, -1, pai_s.filename, SIZEOF(pai_s.filename));
				pai_s.format = pai.format;
			}
#endif
			if (res != CALLSERVICE_NOTFOUND) result = res;
			ProcessAvatarInfo(pai_s.hContact, result, &pai_s, szProto);
		}
	}

	return result;
}

static void RequestThread(void *vParam)
{
	while (!g_shutDown)
	{
		EnterCriticalSection(&cs);

		if ( queue.getCount() == 0 )
		{
			// No items, so supend thread
			LeaveCriticalSection(&cs);

			mir_sleep(POOL_DELAY);
		}
		else
		{
			// Take a look at first item
			QueueItem& qi = queue[ queue.getCount()-1 ];

			if (qi.check_time > GetTickCount()) 
			{
				// Not time to request yet, wait...
				LeaveCriticalSection(&cs);
				mir_sleep(POOL_DELAY);
			}
			else
			{
				// Will request this item
				HANDLE hContact = qi.hContact;
				queue.remove( queue.getCount()-1 );

				QueueRemove(hContact);

				LeaveCriticalSection(&cs);

				if (FetchAvatarFor(hContact) == GAIR_WAITFOR)
				{
					// Mark to not request this contact avatar for more 30 min
					EnterCriticalSection(&cs);
					QueueRemove(hContact);
					QueueAdd(hContact, REQUEST_WAITFOR_WAIT_TIME);
					LeaveCriticalSection(&cs);

					// Wait a little until requesting again
					mir_sleep(REQUEST_DELAY);
				}
			}
		}
	}

	DeleteCriticalSection(&cs);
	queue.destroy();
}