/*
    AdvancedAutoAway Plugin for Miranda-IM (www.miranda-im.org)
    KeepStatus Plugin for Miranda-IM (www.miranda-im.org)
    StartupStatus Plugin for Miranda-IM (www.miranda-im.org)
    Copyright 2003-2006 P. Boon

    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 "commonstatus.h"

// handles for hooks and other Miranda thingies
static HANDLE hCSStatusChangedExEvent;

OBJLIST<PROTOCOLSETTINGEX>* protoList;

// prototypes
char *StatusModeToDbSetting(int status,const char *suffix);
DWORD StatusModeToProtoFlag(int status);
INT_PTR SetStatusEx(WPARAM wParam, LPARAM lParam);
int InitCommonStatus();
int GetProtoCount();

// extern
extern INT_PTR ShowConfirmDialogEx(WPARAM wParam, LPARAM lParam);

// some helpers from awaymsg.c ================================================================
char *StatusModeToDbSetting(int status,const char *suffix)
{
	char *prefix;
	static char str[64];

	switch(status) {
		case ID_STATUS_AWAY: prefix="Away";	break;
		case ID_STATUS_NA: prefix="Na";	break;
		case ID_STATUS_DND: prefix="Dnd"; break;
		case ID_STATUS_OCCUPIED: prefix="Occupied"; break;
		case ID_STATUS_FREECHAT: prefix="FreeChat"; break;
		case ID_STATUS_ONLINE: prefix="On"; break;
		case ID_STATUS_OFFLINE: prefix="Off"; break;
		case ID_STATUS_INVISIBLE: prefix="Inv"; break;
		case ID_STATUS_ONTHEPHONE: prefix="Otp"; break;
		case ID_STATUS_OUTTOLUNCH: prefix="Otl"; break;
		default: return NULL;
	}
	lstrcpyA(str,prefix); lstrcatA(str,suffix);
	return str;
}

DWORD StatusModeToProtoFlag(int status)
{
	// *not* the same as in core, <offline>
	switch(status) {
		case ID_STATUS_ONLINE: return PF2_ONLINE;
		case ID_STATUS_OFFLINE: return PF2_OFFLINE;
		case ID_STATUS_INVISIBLE: return PF2_INVISIBLE;
		case ID_STATUS_OUTTOLUNCH: return PF2_OUTTOLUNCH;
		case ID_STATUS_ONTHEPHONE: return PF2_ONTHEPHONE;
		case ID_STATUS_AWAY: return PF2_SHORTAWAY;
		case ID_STATUS_NA: return PF2_LONGAWAY;
		case ID_STATUS_OCCUPIED: return PF2_LIGHTDND;
		case ID_STATUS_DND: return PF2_HEAVYDND;
		case ID_STATUS_FREECHAT: return PF2_FREECHAT;
	}
	return 0;
}

int GetActualStatus(PROTOCOLSETTINGEX *protoSetting)
{
	if (protoSetting->status == ID_STATUS_LAST) {
		if ( (protoSetting->lastStatus < MIN_STATUS) || (protoSetting->lastStatus > MAX_STATUS))
			return CallProtoService(protoSetting->szName, PS_GETSTATUS, 0, 0);
		return protoSetting->lastStatus;
	}
	if (protoSetting->status == ID_STATUS_CURRENT)
		return CallProtoService(protoSetting->szName, PS_GETSTATUS, 0, 0);

	if ( (protoSetting->status < ID_STATUS_OFFLINE) || (protoSetting->status > ID_STATUS_OUTTOLUNCH)) {
		log_debugA("invalid status detected: %d", protoSetting->status);
		return 0;
	}
	return protoSetting->status;
}

// helper, from core
static TCHAR *GetDefaultMessage(int status)
{
	switch(status) {
		case ID_STATUS_AWAY:       return TranslateT("I've been away since %time%.");
		case ID_STATUS_NA:         return TranslateT("Give it up, I'm not in!");
		case ID_STATUS_OCCUPIED:   return TranslateT("Not right now.");
		case ID_STATUS_DND:        return TranslateT("Give a guy some peace, would ya?");
		case ID_STATUS_FREECHAT:   return TranslateT("I'm a chatbot!");
		case ID_STATUS_ONLINE:     return TranslateT("Yep, I'm here.");
		case ID_STATUS_OFFLINE:    return TranslateT("Nope, not here.");
		case ID_STATUS_INVISIBLE:  return TranslateT("I'm hiding from the mafia.");
		case ID_STATUS_ONTHEPHONE: return TranslateT("That'll be the phone.");
		case ID_STATUS_OUTTOLUNCH: return TranslateT("Mmm... food.");
		case ID_STATUS_IDLE:       return TranslateT("idleeeeeeee");
	}
	return NULL;
}

TCHAR *GetDefaultStatusMessage(PROTOCOLSETTINGEX *ps, int newstatus)
{
	if (ps->szMsg != NULL) {// custom message set
		log_infoA("CommonStatus: Status message set by calling plugin");
		return mir_tstrdup( ps->szMsg );
	}

	if ( ServiceExists( MS_AWAYMSG_GETSTATUSMSGT )) {
		TCHAR* tMsg = ( TCHAR* )CallService(MS_AWAYMSG_GETSTATUSMSGT, newstatus, (LPARAM)ps->szName );
		log_debugA("CommonStatus: Status message retrieved from general awaysys (TCHAR)");
		return tMsg;
	}

	if ( ServiceExists(MS_AWAYMSG_GETSTATUSMSG)) {
		char *tMsg;
		if ( ServiceExists(MS_SA_ISSARUNNING) && CallService(MS_SA_ISSARUNNING, 0, 0))
			tMsg = (char*)CallService(MS_AWAYMSG_GETSTATUSMSG, (WPARAM)newstatus, (LPARAM)ps->szName);
		else
      	tMsg = (char*)CallService(MS_AWAYMSG_GETSTATUSMSG, (WPARAM)newstatus, 0);

		log_debugA("CommonStatus: Status message retrieved from general awaysys");

		TCHAR* result = mir_a2t( tMsg );
		mir_free(tMsg);
		return result;
	}

	/* awaysys doesn't define the service above */
	TCHAR* tMsg = GetDefaultMessage(newstatus);
	if (tMsg != NULL) {
		log_debugA("CommonStatus: Status message retrieved from defaults");
		return mir_tstrdup(tMsg);
	}

	return NULL;
}

static int equalsGlobalStatus(PROTOCOLSETTINGEX **ps) {

	int i, j, pstatus = 0, gstatus = 0;

	for ( i=0; i < protoList->getCount(); i++ )
		if ( ps[i]->szMsg != NULL && GetActualStatus(ps[i]) != ID_STATUS_OFFLINE )
			return 0;

	int count;
	PROTOACCOUNT **protos;
	ProtoEnumAccounts(&count, &protos);

	for (i=0; i < count; i++) {
		if ( !IsSuitableProto(protos[i]))
			continue;

		pstatus = 0;
		for ( j=0; j < protoList->getCount(); j++ )
			if (!strcmp(protos[i]->szModuleName, ps[j]->szName))
				pstatus = GetActualStatus(ps[j]);

		if (pstatus == 0)
			pstatus = CallProtoService(protos[i]->szModuleName, PS_GETSTATUS, 0, 0);

		if ( db_get_b(NULL, protos[i]->szModuleName, "LockMainStatus", 0)) {
			// if proto is locked, pstatus must be the current status
			if (pstatus != CallProtoService(protos[i]->szModuleName, PS_GETSTATUS, 0, 0))
				return 0;
		}
		else {
			if (gstatus == 0)
				gstatus = pstatus;

			if (pstatus != gstatus)
				return 0;
		}
	}

	return gstatus;
}

static void SetStatusMsg(PROTOCOLSETTINGEX *ps, int newstatus)
{
	TCHAR* tszMsg = GetDefaultStatusMessage( ps, newstatus );
	if ( tszMsg ) {
			/* replace the default vars in msg  (I believe this is from core) */
		for ( int j=0; tszMsg[j]; j++ ) {
			TCHAR substituteStr[128];

			if ( tszMsg[j] != '%' )
				continue;

			if ( !_tcsnicmp( tszMsg+j, _T("%time%"), 6 ))
				GetTimeFormat( LOCALE_USER_DEFAULT, TIME_NOSECONDS, 0, 0, substituteStr, SIZEOF(substituteStr));
			else if ( !_tcsnicmp( tszMsg+j, _T("%date%"), 6 ))
				GetDateFormat( LOCALE_USER_DEFAULT, DATE_SHORTDATE, 0, 0, substituteStr, SIZEOF(substituteStr));
			else
				continue;

			if ( lstrlen( substituteStr ) > 6 )
				tszMsg = (TCHAR*)mir_realloc(tszMsg, sizeof(TCHAR)*(lstrlen(tszMsg)+1+lstrlen(substituteStr)-6));
			MoveMemory( tszMsg + j + lstrlen(substituteStr), tszMsg+j+6, sizeof(TCHAR)*(lstrlen(tszMsg)-j-5));
			CopyMemory( tszMsg + j, substituteStr, sizeof(TCHAR)*lstrlen( substituteStr ));
		}

		TCHAR* szFormattedMsg = variables_parsedup(tszMsg, ps->tszAccName, NULL);
		if (szFormattedMsg != NULL) {
			mir_free(tszMsg);
			tszMsg = szFormattedMsg;
		}
	}
	log_debugA("CommonStatus sets status message for %s directly", ps->szName);
	if ( CALLSERVICE_NOTFOUND == CallProtoService(ps->szName, PS_SETAWAYMSGT, newstatus, (LPARAM)tszMsg )) {
		char* sMsg = mir_t2a( tszMsg );
		CallProtoService(ps->szName, PS_SETAWAYMSG, newstatus, (LPARAM)sMsg );
		mir_free( sMsg );
	}
	mir_free( tszMsg );
}

INT_PTR SetStatusEx(WPARAM wParam, LPARAM lParam)
{
	PROTOCOLSETTINGEX** protoSettings = *(PROTOCOLSETTINGEX***)wParam;
	if ( protoSettings == NULL )
		return -1;

	int globStatus = equalsGlobalStatus( protoSettings );

	/*
		issue with setting global status;
		things get messy because SRAway hooks ME_CLIST_STATUSMODECHANGE, so the status messages of SRAway and
		commonstatus will clash
	*/
	NotifyEventHooks(hCSStatusChangedExEvent, (WPARAM)&protoSettings, 0);

	// set all status messages first
	for (int i=0; i < protoList->getCount(); i++) {
		char* szProto = protoSettings[i]->szName;
		if ( !CallService(MS_PROTO_ISPROTOCOLLOADED, 0, (LPARAM)szProto)) {
			log_debugA( "CommonStatus: %s is not loaded", szProto );
			continue;
		}
		// some checks
		int newstatus = GetActualStatus(protoSettings[i]);
		if (newstatus == 0) {
			log_debugA("CommonStatus: incorrect status for %s (%d)", szProto, protoSettings[i]->status);
			continue;
		}
		int oldstatus = CallProtoService(szProto, PS_GETSTATUS, 0, 0);
		// set last status
		protoSettings[i]->lastStatus = oldstatus;
		if (oldstatus <= MAX_CONNECT_RETRIES) {// ignore if connecting, but it didn't came this far if it did
			log_debugA("CommonStatus: %s is already connecting", szProto);
			continue;
		}

		// status checks
		long protoFlag = Proto_Status2Flag( newstatus );
		int b_Caps2 = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_2, 0) & protoFlag;
		int b_Caps5 = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_5, 0) & protoFlag;
		if (newstatus != ID_STATUS_OFFLINE && ( !b_Caps2 || b_Caps5)) {
			// status and status message for this status not supported
			//log_debug("CommonStatus: status not supported %s", szProto);
			continue;
		}

		int b_Caps1 = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_MODEMSGSEND & ~PF1_INDIVMODEMSG;
		int b_Caps3 = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_3, 0) & protoFlag;
		if (newstatus == oldstatus && (!b_Caps1 || !b_Caps3)) {
			// no status change and status messages are not supported
			//log_debug("CommonStatus: no change, %s (%d %d)", szProto, oldstatus, newstatus);
			continue;
		}

		// set status message first
		if (b_Caps1 && b_Caps3)
			SetStatusMsg( protoSettings[i], newstatus );

		// set the status
		if (newstatus != oldstatus) {
			log_debugA("CommonStatus sets status for %s to %d", szProto, newstatus);
			CallProtoService(szProto, PS_SETSTATUS, (WPARAM)newstatus, 0);
		}
	}

	if ( globStatus != 0 ) {
		if ( !ServiceExists( MS_CLIST_SETSTATUSMODE )) {
			log_debugA("CommonStatus: MS_CLIST_SETSTATUSMODE not available!");
			return -1;
		}
		log_debugA("CommonStatus: setting global status %u", globStatus);
		CallService(MS_CLIST_SETSTATUSMODE, (WPARAM)globStatus, 0);
	}

	return 0;
}

static INT_PTR GetProtocolCountService(WPARAM wParam, LPARAM lParam)
{
	return GetProtoCount();
}

bool IsSuitableProto(PROTOACCOUNT *pa)
{
	return (pa == NULL) ? false : (pcli->pfnGetProtocolVisibility(pa->szModuleName) != 0);
}

int GetProtoCount()
{
	int pCount = 0;

	int count;
	PROTOACCOUNT **protos;
	ProtoEnumAccounts( &count, &protos );

	for (int i=0; i < count; i++)
		if ( IsSuitableProto(protos[i]))
			pCount++;

	return pCount;
}

static int CreateServices()
{
	if (ServiceExists(MS_CS_SETSTATUSEX))
		return -1;

	hCSStatusChangedExEvent = CreateHookableEvent(ME_CS_STATUSCHANGEEX);

	CreateServiceFunction(MS_CS_SETSTATUSEX, SetStatusEx);
	CreateServiceFunction(MS_CS_SHOWCONFIRMDLGEX, ShowConfirmDialogEx);
	CreateServiceFunction(MS_CS_GETPROTOCOUNT, GetProtocolCountService);
	return 0;
}

static int onShutdown(WPARAM wParam, LPARAM lParam)
{
	DestroyHookableEvent(hCSStatusChangedExEvent);
	return 0;
}

int InitCommonStatus()
{
	if (!CreateServices())
	  	HookEvent(ME_SYSTEM_PRESHUTDOWN, onShutdown);

	return 0;
}