/*
	New Away System - plugin for Miranda IM
	Copyright (C) 2005-2007 Chervov Dmitry
	Copyright (C) 2004-2005 Iksaif Entertainment
	Copyright (C) 2002-2003 Goblineye Entertainment

    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 "stdafx.h"
#include "Properties.h"

static struct
{
	int Status, DisableReplyCtlID, DontShowDialogCtlID;
} StatusModeList[] = {
	ID_STATUS_ONLINE, IDC_REPLYDLG_DISABLE_ONL, IDC_MOREOPTDLG_DONTPOPDLG_ONL,
	ID_STATUS_AWAY, IDC_REPLYDLG_DISABLE_AWAY, IDC_MOREOPTDLG_DONTPOPDLG_AWAY,
	ID_STATUS_NA, IDC_REPLYDLG_DISABLE_NA, IDC_MOREOPTDLG_DONTPOPDLG_NA,
	ID_STATUS_OCCUPIED, IDC_REPLYDLG_DISABLE_OCC, IDC_MOREOPTDLG_DONTPOPDLG_OCC,
	ID_STATUS_DND, IDC_REPLYDLG_DISABLE_DND, IDC_MOREOPTDLG_DONTPOPDLG_DND,
	ID_STATUS_FREECHAT, IDC_REPLYDLG_DISABLE_FFC, IDC_MOREOPTDLG_DONTPOPDLG_FFC,
	ID_STATUS_INVISIBLE, IDC_REPLYDLG_DISABLE_INV, IDC_MOREOPTDLG_DONTPOPDLG_INV,
	ID_STATUS_ONTHEPHONE, IDC_REPLYDLG_DISABLE_OTP, IDC_MOREOPTDLG_DONTPOPDLG_OTP,
	ID_STATUS_OUTTOLUNCH, IDC_REPLYDLG_DISABLE_OTL, IDC_MOREOPTDLG_DONTPOPDLG_OTL
};


class CAutoreplyData
{
public:
	CAutoreplyData(MCONTACT hContact, TCString Reply): hContact(hContact), Reply(Reply) {}

	MCONTACT hContact;
	TCString Reply;
};

// _ad must be allocated using "new CAutoreplyData()"
void __cdecl AutoreplyDelayThread(void *_ad)
{
	Thread_SetName("NewAwaySysMod: AutoreplyDelayThread");

	CAutoreplyData *ad = (CAutoreplyData*)_ad;
	_ASSERT(ad && ad->hContact && ad->Reply.GetLen());
	char *szProto = GetContactProto(ad->hContact);
	if (!szProto) {
		_ASSERT(0);
		return;
	}

	T2Utf pszReply(ad->Reply);
	int ReplyLen = (int)mir_strlen(pszReply);
	ProtoChainSend(ad->hContact, PSS_MESSAGE, 0, (LPARAM)pszReply);

	if (g_AutoreplyOptPage.GetDBValueCopy(IDC_REPLYDLG_LOGREPLY)) { // store in the history
		DBEVENTINFO dbeo = { 0 };
		dbeo.cbSize = sizeof(dbeo);
		dbeo.eventType = EVENTTYPE_MESSAGE;
		dbeo.flags = DBEF_SENT | DBEF_UTF;
		dbeo.szModule = szProto;
		dbeo.timestamp = time(NULL);

		dbeo.cbBlob = ReplyLen;
		dbeo.pBlob = (PBYTE)(char*)pszReply;

		SleepEx(1000, true); // delay before sending the reply, as we need it to be later than the message we're replying to (without this delay, srmm puts the messages in a wrong order)
		db_event_add(ad->hContact, &dbeo);
	}

	delete ad;
}


int IsSRMsgWindowOpen(MCONTACT hContact, int DefaultRetVal)
{
	if (ServiceExists(MS_MSG_GETWINDOWDATA)) {
		MessageWindowData mwd = { 0 };
		mwd.cbSize = sizeof(mwd);
		MessageWindowInputData mwid = { 0 };
		mwid.cbSize = sizeof(mwid);
		mwid.uFlags = MSG_WINDOW_UFLAG_MSG_BOTH;
		mwid.hContact = hContact;
		return !CallService(MS_MSG_GETWINDOWDATA, (WPARAM)&mwid, (LPARAM)&mwd) && mwd.hwndWindow;
	}

	return DefaultRetVal;
}


#define MAX_REPLY_TIMEDIFF 5 // maximum "age" of an event to remain unfiltered; in seconds
#define MSGWNDOPEN_UNDEFINED (-1)

class CMetacontactEvent
{
public:
	CMetacontactEvent(MCONTACT hMetaContact, DWORD timestamp, int bMsgWindowIsOpen) : hMetaContact(hMetaContact), timestamp(timestamp), bMsgWindowIsOpen(bMsgWindowIsOpen) {};

	MCONTACT hMetaContact;
	DWORD timestamp;
	int bMsgWindowIsOpen;
};

TMyArray<CMetacontactEvent> MetacontactEvents;


int MsgEventAdded(WPARAM hContact, LPARAM lParam)
{
	DBEVENTINFO *dbei = (DBEVENTINFO*)lParam;
	if (!hContact)
		return 0;

	if (dbei->flags & DBEF_SENT || (dbei->eventType != EVENTTYPE_MESSAGE && dbei->eventType != EVENTTYPE_URL && dbei->eventType != EVENTTYPE_FILE))
		return 0;

	if (time(NULL) - dbei->timestamp > MAX_REPLY_TIMEDIFF) // don't reply to offline messages
		return 0;

	char *szProto = GetContactProto(hContact);
	if (!szProto)
		return 0;

	DWORD Flags1 = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0);
	if (!(Flags1 & PF1_IMSEND)) // don't reply to protocols that don't support outgoing messages
		return 0;

	int bMsgWindowIsOpen = MSGWNDOPEN_UNDEFINED;
	if (dbei->flags & DBEF_READ) {
		// if it's a subcontact of a metacontact
		MCONTACT hMetaContact = db_mc_getMeta(hContact);
		if (hMetaContact == 0)
			return 0;

		// remove outdated events first
		DWORD CurTime = time(NULL);
		int i;
		for (i = MetacontactEvents.GetSize() - 1; i >= 0; i--)
			if (CurTime - MetacontactEvents[i].timestamp > MAX_REPLY_TIMEDIFF)
				MetacontactEvents.RemoveElem(i);

		// we compare only event timestamps, and do not look at the message itself. it's unlikely that there'll be two events from a contact at the same second, so it's a trade-off between speed and reliability
		for (i = MetacontactEvents.GetSize() - 1; i >= 0; i--) {
			if (MetacontactEvents[i].timestamp == dbei->timestamp && MetacontactEvents[i].hMetaContact == hMetaContact) {
				bMsgWindowIsOpen = MetacontactEvents[i].bMsgWindowIsOpen;
				break;
			}
		}
		if (i < 0) {
			_ASSERT(0);
			return 0;
		}
	}

	// ugly workaround for metacontacts, part i; store all metacontacts' events to a temporary array, so we'll be able to get the 'source' protocol when subcontact event happens later. we need the protocol to get its status and per-status settings properly
	if (!mir_strcmp(szProto, META_PROTO)) {
		// remove outdated events first
		DWORD CurTime = time(NULL);
		for (int i = MetacontactEvents.GetSize() - 1; i >= 0; i--)
			if (CurTime - MetacontactEvents[i].timestamp > MAX_REPLY_TIMEDIFF)
				MetacontactEvents.RemoveElem(i);

		// add the new event and wait for a subcontact's event
		MetacontactEvents.AddElem(CMetacontactEvent(hContact, dbei->timestamp, IsSRMsgWindowOpen(hContact, false)));
		return 0;
	}

	int iMode = CallProtoService(szProto, PS_GETSTATUS, 0, 0);
	int i;
	for (i = _countof(StatusModeList) - 1; i >= 0; i--)
		if (iMode == StatusModeList[i].Status)
			break;

	if (i < 0)
		return 0;

	COptPage AutoreplyOptData(g_AutoreplyOptPage);
	AutoreplyOptData.DBToMem();
	if (dbei->eventType == EVENTTYPE_MESSAGE)
		db_set_w(hContact, MOD_NAME, DB_MESSAGECOUNT, db_get_w(hContact, MOD_NAME, DB_MESSAGECOUNT, 0) + 1); // increment message counter

	if (AutoreplyOptData.GetValue(StatusModeList[i].DisableReplyCtlID))
		return 0;

	MCONTACT hContactForSettings = hContact; // used to take into account not-on-list contacts when getting contact settings, but at the same time allows to get correct contact info for contacts that are in the DB
	if (hContactForSettings != INVALID_CONTACT_ID && db_get_b(hContactForSettings, "CList", "NotOnList", 0))
		hContactForSettings = INVALID_CONTACT_ID; // INVALID_HANDLE_VALUE means the contact is not-on-list

	if (!CContactSettings(iMode, hContactForSettings).Autoreply.IncludingParents(szProto) || CContactSettings(iMode, hContactForSettings).Ignore)
		return 0;

	if (AutoreplyOptData.GetValue(IDC_REPLYDLG_DONTREPLYINVISIBLE)) {
		WORD ApparentMode = db_get_w(hContact, szProto, "ApparentMode", 0);
		if ((iMode == ID_STATUS_INVISIBLE && (!(Flags1 & PF1_INVISLIST) || ApparentMode != ID_STATUS_ONLINE)) || (Flags1 & PF1_VISLIST && ApparentMode == ID_STATUS_OFFLINE))
			return 0;
	}
	if (AutoreplyOptData.GetValue(IDC_REPLYDLG_ONLYCLOSEDDLGREPLY)) {
		if (bMsgWindowIsOpen && bMsgWindowIsOpen != MSGWNDOPEN_UNDEFINED)
			return 0;

		// we never get here for a metacontact; we did check for metacontact's window earlier, and here we need to check only for subcontact's window
		if (IsSRMsgWindowOpen(hContact, false))
			return 0;
	}
	if (AutoreplyOptData.GetValue(IDC_REPLYDLG_ONLYIDLEREPLY) && !g_bIsIdle)
		return 0;

	int UIN = 0;
	if (IsAnICQProto(szProto))
		UIN = db_get_dw(hContact, szProto, "UIN", 0);

	int SendCount = (int)AutoreplyOptData.GetValue(IDC_REPLYDLG_SENDCOUNT);
	if ((AutoreplyOptData.GetValue(IDC_REPLYDLG_DONTSENDTOICQ) && UIN) || // an icq contact
		 (SendCount != -1 && db_get_b(hContact, MOD_NAME, DB_SENDCOUNT, 0) >= SendCount))
		return 0;

	if ((dbei->eventType == EVENTTYPE_MESSAGE && !AutoreplyOptData.GetValue(IDC_REPLYDLG_EVENTMSG)) || 
		 (dbei->eventType == EVENTTYPE_URL && !AutoreplyOptData.GetValue(IDC_REPLYDLG_EVENTURL)) || 
		 (dbei->eventType == EVENTTYPE_FILE && !AutoreplyOptData.GetValue(IDC_REPLYDLG_EVENTFILE)))
		return 0;

	db_set_b(hContact, MOD_NAME, DB_SENDCOUNT, db_get_b(hContact, MOD_NAME, DB_SENDCOUNT, 0) + 1);
	GetDynamicStatMsg(hContact); // it updates VarParseData.Message needed for %extratext% in the format
	TCString Reply(*(TCString*)AutoreplyOptData.GetValue(IDC_REPLYDLG_PREFIX));
	if (Reply != NULL && ServiceExists(MS_VARS_FORMATSTRING) && !g_SetAwayMsgPage.GetDBValueCopy(IDS_SAWAYMSG_DISABLEVARIABLES)) {
		wchar_t *szResult = variables_parse(Reply, VarParseData.Message, hContact);
		if (szResult != NULL) {
			Reply = szResult;
			mir_free(szResult);
		}
	}
	
	if (Reply.GetLen()) {
		CAutoreplyData *ad = new CAutoreplyData(hContact, Reply);
		mir_forkthread(AutoreplyDelayThread, ad);
	}
	return 0;
}