//  Beta release only.
//
//  Copyright(c) Ivyware Pty Ltd 2013  (all rights reserved)
//               MELBOURNE, VICTORIA, AUSTRALIA, 3130
//
//  Provided no fee is charged (either by the distributer or user)
//  permission to use, copy, modify, and distribute this software and
//  its documentation for any purpose and without fee is hereby granted,
//  provided that the above copyright notice appear in all copies and
//  that both that copyright notice and this permission notice appear
//  in supporting documentation.
//
//  No waranty or suitablity for purpose is implied.
//
//
//  P2PeerHub MAPI integration object
//  NOTES: Integrates with Microsoft Exchange using MAPI
//               
//

#include "StdAfx.h"
#include "P2PeerHubMAPI.h"
#include "MAPI_Ext.h"
#include "P2PeerHubMAPI.h"
#include "P2Pwin32.h"
#include "MAPImessage.h"

P2PeerHubMAPI::P2PeerHubMAPI ( P2PaddrSTR strP2PaddrThis )
             : P2PeerHub ( strP2PaddrThis )
{
    // Firstly
    RenderThisSafe();
}

P2PeerHubMAPI::~P2PeerHubMAPI ( void )
{
    if ( m_nMAPIPumpID > 0 )
      ClosePump ( m_nMAPIPumpID );
    if ( m_pMAPISession )
      delete m_pMAPISession;
    if ( m_pMAPIex )
      delete m_pMAPIex;
}

void
P2PeerHubMAPI::RenderThisSafe (  )
{
    // Attributes
    m_nMAPIPumpID        = 0;
    m_strProfileName     = _T(""); //_T("MAPIProfileName");
    m_strProfilePassword = _T("");
    m_strFrom_Name       = _T("Larry Mann");
    m_strFrom_Email      = _T("larryman@bigpond.net.au");
    m_pMAPIex            = nullptr;
    m_pMAPISession       = nullptr;
}

///////////////////////////////////////////////////////////////////////
//  Extractors
void
P2PeerHubMAPI::TranslateMsg ( CMAPImessage& oMAPImessage, P2PeerMsg *pMsg )
{
    P3PmsgNode oNodeMessage = pMsg->r_datn().ROBJ;

    // Importance
    if ( oNodeMessage.Exists("Importance") )
       oMAPImessage.SetImportance ( oNodeMessage.SelectItem("Importance").c_int() );
    else
       oMAPImessage.SetImportance ( IMPORTANCE_NORMAL );

    // From
    // NOTE: Mandatory that field exists and the actual sender exists on the Exchange server
    CString strFrom_Email = m_strFrom_Email;
    if ( oNodeMessage.Exists("From_Email") )
      strFrom_Email = oNodeMessage["From_Email"].c_str();
    CString strFrom_Name = m_strFrom_Name;
    if ( oNodeMessage.Exists("From_Name") )
      strFrom_Name = oNodeMessage["From_Name"].c_str();
    oMAPImessage.SetSender ( strFrom_Name, strFrom_Email );

    // Delivery Receipt
    if ( oNodeMessage.Exists(_T("DeliveryReceipt")) )
      oMAPImessage.SetDeliveryReport();

    // Subject
    // NOTES: For completness always satisfy field contents
    if ( oNodeMessage.Exists("Subject") )
      oMAPImessage.SetSubject ( oNodeMessage["Subject"].c_str() );
    else
		 oMAPImessage.SetSubject ( _T("No Subject") );

	  // Body
    // NOTES: Use SetBody for ANSI text, SetHTML for HTML and SetRTF for Rich Text
		oMAPImessage.SetBody("Body");

    // To
    // NOTES: Verify each address exists
    P3PmsgNode oNodeTo = oNodeMessage["To"].ROBJ;
    P3PmsgCurs oCursTo(oNodeTo);
    for ( int i = 0; oCursTo.Goto(i); i++ )
      oMAPImessage.AddRecipient ( oCursTo.r_name().c_name(), MAPI_TO );

    // Cc
    // NOTES: Verify each address exists
    if ( oNodeMessage.Exists("Cc") )
    {
      P3PmsgNode oNodeCc = oNodeMessage["Cc"].ROBJ;
      P3PmsgCurs oCursCc(oNodeCc);
      for ( int i = 0; oCursCc.Goto(i); i++ )
        oMAPImessage.AddRecipient ( oCursCc.r_name().c_name(), MAPI_CC );
    }

    // Bcc
    // NOTES: Verify each address exists
    if ( oNodeMessage.Exists("Bc") )
    {
      P3PmsgNode oNodeBc = oNodeMessage["Bc"].ROBJ;
      P3PmsgCurs oCursBc(oNodeBc);
      for ( int i = 0; oCursBc.Goto(i); i++ )
        oMAPImessage.AddRecipient ( oCursBc.r_name().c_name(), MAPI_BCC );
    }

    // Attachments
    //if ( oNodeMessage.Exists("Attach") )
    //{
    //  P3PmsgNode oNodeAttach = oNodeMessage["Attach"].c_str();
    //  P3PmsgCurs oCursAttach(oNodeAttach);
    //  for ( int i = 0; oCursAttach.Goto(i); i++ )
		//   oMAPImessage.AddAttachment ( oCursAttach.r_name().c_name() );
    //}
}

void
P2PeerHubMAPI::TranslateMsgSQL ( CMAPImessage& oMAPImessage, P2PeerMsg *pMsg )
{
    P3PmsgNode oNodeSQL = pMsg->r_datn().SelectNode(_T("SQL")).ROBJ;

    // Importance
    if ( oNodeSQL.Exists("Importance") )
       oMAPImessage.SetImportance ( oNodeSQL.SelectItem("Importance").c_int() );
    else
       oMAPImessage.SetImportance ( IMPORTANCE_NORMAL );

    // From
    // NOTE: Mandatory that field exists and the actual sender exists on the Exchange server
    CString strFrom_Email = m_strFrom_Email;
    if ( oNodeSQL.Exists("From_Email") )
      strFrom_Email = oNodeSQL["From_Email"].c_str();
    CString strFrom_Name = m_strFrom_Name;
    if ( oNodeSQL.Exists("From_Name") )
      strFrom_Name = oNodeSQL["From_Name"].c_str();
		oMAPImessage.SetSender ( strFrom_Name, strFrom_Email );

    // Delivery Receipt
    if ( oNodeSQL.Exists(_T("DeliveryReceipt")) )
      oMAPImessage.SetDeliveryReport();

    // Subject
    // NOTES: For completness always satisfy field contents
    if ( oNodeSQL.Exists("Subject") )
      oMAPImessage.SetSubject ( oNodeSQL["Subject"].c_str() );
    else
		  oMAPImessage.SetSubject ( _T("No Subject") );

	  // Body
    // NOTES: Use SetBody for ANSI text, SetHTML for HTML and SetRTF for Rich Text
		oMAPImessage.SetBody("Body");

    // To
    // NOTES: Recipients are assumed to be any field matching the wildcard "To[#]"
    //      : To1, To123, etc match but To1a, To123z, etc do no match
    P3PmsgCurs oCursTo(oNodeSQL);
    for ( int i = 0; oCursTo.Goto(i); i++ )
    {
      if ( !Wildcard(_T("To"), oCursTo.r_name().c_name() )  &&
           !Wildcard(_T("To#"), oCursTo.r_name().c_name() )    )
        continue;
      if ( oCursTo.r_data().c_size() > 3 )
        oMAPImessage.AddRecipient ( oCursTo.r_data().c_str(), MAPI_TO );
    }

    // Cc
    // NOTES: Recipients are assumed to be any field matching the wildcard "Cc[#]"
    //      : Cc1, Cc123, etc match but Cc1a, Cc123z, etc do no match
    P3PmsgCurs oCursCc(oNodeSQL);
    for ( int i = 0; oCursCc.Goto(i); i++ )
    {
      if ( !Wildcard(_T("Cc"), oCursCc.r_name().c_name() )  &&
           !Wildcard(_T("Cc#"), oCursCc.r_name().c_name() )    )
        continue;
      if ( oCursCc.r_data().c_size() > 3 )
        oMAPImessage.AddRecipient ( oCursCc.r_data().c_str(), MAPI_CC );
    }

    // Bcc
    // NOTES: Recipients are assumed to be any field matching the wildcard "Bc[#]"
    //      : Bc1, Bc123, etc match but Bc1a, Bc123z, etc do no match
    P3PmsgCurs oCursBc(oNodeSQL);
    for ( int i = 0; oCursBc.Goto(i); i++ )
    {
      if ( !Wildcard(_T("Bc"), oCursBc.r_name().c_name() )  &&
           !Wildcard(_T("Bc#"), oCursBc.r_name().c_name() )    )
        continue;
      if ( oCursBc.r_data().c_size() > 3 )
        oMAPImessage.AddRecipient ( oCursBc.r_data().c_str(), MAPI_BCC );
    }

    // Attachments
    // Attachment
    // NOTES: Attachments are assumed to be any field matching the wildcard "Attachment[#]"
    //      : Attachment1, Attachment123, etc match but Attachment1a, Attachment123z,
    //        etc do no match
    //P3PmsgCurs oCursAttach(oNodeSQL);
    //for ( int i = 0; oCursAttach.Goto(i); i++ )
    //{
    //  if ( !Wildcard(_T("Attachment"), oCursTo.r_name().c_name() )  &&
    //       !Wildcard(_T("Attachment#"), oCursTo.r_name().c_name() )    )
    //    continue;
    //  oMAPImessage.AddAttachment ( oCursAttach.r_data().c_name() );
    //}
}

///////////////////////////////////////////////////////////////////////
// P2PeerTarget virtual overrides


///////////////////////////////////////////////////////////////////////
//  P2PeerCon event handlers
//  NOTES: If there is a desire to optimise the connection map
//         subjectively place frequent messages at the top and less
//         frequent messages at the bottom of the map
//       : 
BEGIN_P2PeerCon_MAP(P2PeerHubMAPI, P2PeerHub)
END_P2PeerCon_MAP()

///////////////////////////////////////////////////////////////////////
//  P2PeerMsg Handlers
//  NOTES: Default handler set available in P2PeerHub derived objects
//       : If there is a desire to optimise the message map
//         subjectively place frequent messages at the top and less
//         frequent messages at the bottom of the map
//
BEGIN_P2PeerMsg_MAP(P2PeerHubMAPI, P2PeerHub)
    ON_P2PeerMsg("MAPIInitialize",On_MAPIInitialize)
    ON_P2PeerMsg_REFLECT("MAPIInitialize",On_MsgIgnore)
    ON_P2PeerMsg("MAPILogonEx",On_MAPILogonEx)
    ON_P2PeerMsg_REFLECT("MAPILogonEx",On_MsgIgnore)
    ON_P2PeerMsg("MAPIsendEmail",On_MAPIsendEmail)
    ON_P2PeerMsg_REFLECT("MAPIsendEmail",On_MsgIgnore)
    ON_P2PeerMsg("MAPILogoffEx",On_MAPILogoffEx)
    ON_P2PeerMsg_REFLECT("MAPILogoffEx",On_MsgIgnore)
    ON_P2PeerMsg("MAPIUninitialize",On_MAPIUnitialize)
    ON_P2PeerMsg_CATCH ("*",On_MsgIgnore)
END_P2PeerMsg_MAP()

//
//  MSG_MAPIInitialise
//  Performs MAPI environment initialisation and connection
//
//
//  Parameters:  P2PeerMsg *pMsg
//               MAPI initialisation message
//
//  Returns:     msgRESULT
//               Completion summary
//
msgRESULT
P2PeerHubMAPI::On_MAPIInitialize ( P2PeerMsg *pMsg )
{
    // Locals
    CMAPIEx   *pMAPIex = nullptr;

    // Because this is all problematic, we
    try
    {
      // Create P2Pump to run subsequent operations
      // NOTES: MAPI runs asynchronously in its own thread.  Facilitates
      //        non-blocking hub operation
      if ( m_nMAPIPumpID == NULL )
        SpawnPump ( 0, 0, _T("MAPI"), &m_nMAPIPumpID );

      // Confirm thread context
      // NOTES: MAPI operations are synchronous and as such can work against
      //        the smooth flow of messages.  As such the MAPI send and receipt
      //        of emails performed by the dedicated MAPI pump
      //      : This leaves the primary Hub thread free to pump the P2PeerMsg's
      //        generated by the MAPI operations
      if (  m_nMAPIPumpID                &&
           !P2PeerContext(m_nMAPIPumpID)    )
        return P2PeerContextSwap(m_nMAPIPumpID);

      // To be sure, to be sure
      // NOTES: Confirm MAPI not already initaised
      if ( m_pMAPIex )
        EVERR_MAPI(0,"MAPI already initialised")->Throw();

      // Initialise MAPI environment
      // NOTES: When running from the debugger with exceptions turned on the
      //        subsequent call generates internals exceptions.  {Continue}
      //        through exception list and all appears OK.
      pMAPIex = new CMAPIEx ( );
#define MAPI_NO_COINIT 8
		  MAPIINIT_0 oMAPINIT = { 0, MAPI_MULTITHREAD_NOTIFICATIONS|MAPI_NT_SERVICE | MAPI_NO_COINIT };
      if ( pMAPIex->Initialize(&oMAPINIT) != S_OK )
        ThrowP2Pevent ( );
      m_pMAPIex = pMAPIex;

      // Perform acknowledgement
      return ReflectP2PeerMsg ( pMsg );
    }

    // Exceptions
    catch( P2Pevent *pEVT )
    {
      pEVT -> Advice_T ( "MAPIInitialize request abandoned" );
      PostP2PeerMsg ( pMsg->ExceptionFactory(pEVT) );
      pEVT -> Cancel(true);
    }

    // Tidy up and
    // NOTES: We may be processing in the context of the MAPI pump
    //if ( nMAPIPumpID )
    //  SignalP2PmsgPump ( nMAPIPumpID, P2PsigPump_CLOSE );
    delete pMAPIex;
    return msgHANDLED;
}

//
//  MSG_MAPILogonEx
//  Manages MAPILogonEx() sequence
//
//
//  Parameters:  P2PeerMsg *pMsg
//               MAPILogonEx connection message
//
//  Returns:     msgRESULT
//               Completion summary
//
msgRESULT
P2PeerHubMAPI::On_MAPILogonEx ( P2PeerMsg *pMsg )
{
    // Locals
    //P2PumpID   nMAPIPumpID = 0;
    HRESULT    hr;

    // Because this is all problematic, we
    try
    {
      // Confirm thread context
      // NOTES: MAPI operations are synchronous and as such can work against
      //        the smooth flow of messages.  As such the MAPI send and receipt
      //        of emails performed by the dedicated MAPI pump
      //      : This leaves the primary Hub thread free to pump the P2PeerMsg's
      //        generated by the MAPI operations
      if (  m_nMAPIPumpID                &&
           !P2PeerContext(m_nMAPIPumpID)    )
        return P2PeerContextSwap(m_nMAPIPumpID);

      // To be sure, to be sure
      // NOTES: Environmental checks
      if ( m_pMAPIex == nullptr )
        EVERR->MODULE
             ->Message_T("Precondition MAPIInitailse() does not exist")
             ->Throw();
      if ( m_pMAPISession )
        EVERR->MODULE
             ->Message_T("Outstanding MAPILogonEx() already exists")
             ->Throw();

      // Create MAPI session and logon
      P3PmsgNode oMAPILogonEx = pMsg->r_datn().SelectNode(_T("MAPILogonEx")).ROBJ;
      ULONG_PTR ulUIParam = 0;
      if ( oMAPILogonEx.Exists("UIParam") )
        ulUIParam = oMAPILogonEx["UIParam"].c_uint();
      CString strProfileName = m_strProfileName;
      if ( oMAPILogonEx.Exists("ProfileName") )
        strProfileName = oMAPILogonEx["ProfileName"].c_str();
      CString strPassword = m_strProfilePassword;
      if ( oMAPILogonEx.Exists("Password") )
        strProfileName = oMAPILogonEx["Password"].c_str();
      FLAGS flFlags = MAPI_EXTENDED | MAPI_USE_DEFAULT | MAPI_NEW_SESSION
                    | MAPI_EXPLICIT_PROFILE | MAPI_NT_SERVICE;
      if ( oMAPILogonEx.Exists("Flags") )
        flFlags = oMAPILogonEx["Flags"].c_uint();
      CMAPISessionSP spMAPISession = m_pMAPIex -> Query4Session ( );
      hr = spMAPISession -> LogonEx ( ulUIParam
                                    , (LPSTR)(LPCTSTR)strProfileName
                                    , _T("") //(LPSTR)(LPCTSTR)strPassword
                                    , flFlags );
      if ( hr != S_OK )
        ThrowP2Pevent ( );

      // MAPI open message store (aka email address)
      // NOTES: Empty address resolves to default message store
      CString strMsgStorename = m_strMsgStorename;
      if ( oMAPILogonEx.Exists("MsgStorename") )
        strMsgStorename = oMAPILogonEx["MsgStorename"].c_str();
      hr = spMAPISession -> OpenMsgStore ( strMsgStorename );
      if ( hr != S_OK )
        ThrowP2Pevent ( );

      // Perform acknowledgement
      m_pMAPISession = spMAPISession.Dereference();
      return ReflectP2PeerMsg ( pMsg );
    }

    // Exceptions
    catch( P2Pevent *pEVT )
    {
      pEVT -> Advice_T ( "MAPILogonEx() sequence abandoned" );
      PostP2PeerMsg ( pMsg->ExceptionFactory(pEVT) );
      pEVT -> Cancel(true);
    }

    // Tidy up and
    // NOTES: We may be processing in the context of the MAPI pump
    return msgHANDLED;
}

//
//  MSG_MAPILogoffEx
//  Manages MAPILogoffEx() sequence and compliments MAPILogonEx()
//
//
//  Parameters:  P2PeerMsg *pMsg
//               MAPILogoffEx connection message
//
//  Returns:     msgRESULT
//               Completion summary
//
msgRESULT
P2PeerHubMAPI::On_MAPILogoffEx ( P2PeerMsg *pMsg )
{
    // Because this is all problematic, we
    try
    {
      // Confirm thread context
      if (  m_nMAPIPumpID                &&
           !P2PeerContext(m_nMAPIPumpID)    )
        return P2PeerContextSwap(m_nMAPIPumpID);

      // Implementation
      // NOTES: Shutdowns are more tolerant
      if ( m_pMAPIex      &&
           m_pMAPISession    )
      {
        m_pMAPISession -> LogoffEx ( );
        delete m_pMAPISession;
               m_pMAPISession = 0;
      }

      // Perform acknowledgement
      return ReflectP2PeerMsg ( pMsg );
    }

    // Exceptions
    catch ( P2Pevent *pEVT )
    {
      pEVT -> Advice_T ( "MAPILogoffEx request abandoned" );
      PostP2PeerMsg ( pMsg->ExceptionFactory(pEVT) );
      pEVT -> Cancel(true);
    }

    // Tidy up and
    return msgHANDLED;
}

//
//  MSG_MAPIsendEmail
//  Sends email message via MAPI
//  FORMAT: Data Node
//            From_Name
//            From_Email
//            Subject
//            Content
//           [Attach]
//              Attachment1, Attachment2, [etc]
//            To
//              Recipient1, [Recipient2], [etc]
//           [Cc]
//              Recipient1, [Recipient2], [etc]
//           [Bc]
//              Recipient1, [Recipient2], [etc]
//
//
//  Parameters:  P2PeerMsg *pMsg
//               Email request
//
//  Returns:     msgRESULT
//               Completion summary
//
msgRESULT
P2PeerHubMAPI::On_MAPIsendEmail ( P2PeerMsg *pMsg )
{
    // Confirm thread context
    // NOTES: Process in MAPI context
    //      : This leaves the primary Hub thread free to pump the P2PeerMsg's
    //        generated by the hub
    //      : However, this all negated if MAPI processing pump does not exist
    if (  m_nMAPIPumpID                &&
         !P2PeerContext(m_nMAPIPumpID)    )
      return P2PeerContextSwap(m_nMAPIPumpID);

    // Because this is all problematic, we
    try
    {
      // To be sure, to be sure
      // NOTES: Pre-fetch a number of fields to ensure availablity
      if ( m_pMAPIex      == nullptr ||
           m_pMAPISession == nullptr    )
        EVERR->MODULE
             ->Message_T("Emailing requires active MAPI session" )
             ->Throw    ( );
      P3PmsgNode oNodeMessage = pMsg->r_datn().ROBJ;

      // MAPI messaging object
      // NOTES: Populated in the image of Outlook
      //      : Observe nominated format
      CMAPIFolderSP  spMAPIFolder  = m_pMAPISession -> GetMsgStore() -> Query4Outbox ( );
      CMAPImessageSP spMAPImessage = spMAPIFolder -> CreateMessage ( IMPORTANCE_NORMAL, TRUE );
      if ( spMAPImessage.IsEmpty() )
        ThrowP2Pevent ();
      if ( pMsg->r_datn().r_Attr().Exists("Format-SQL") )
        TranslateMsgSQL ( *spMAPImessage, pMsg );
      else
        TranslateMsg ( *spMAPImessage, pMsg );

      // Delivery
			if ( spMAPImessage->SubmitMessage() != S_OK )
        throw GetP2Pevent();

      // Perform acknowledgement
      return ReflectP2PeerMsg ( pMsg );
    }

    // Exceptions
    catch( P2Pevent *pEVT )
    {
      pEVT -> Advice_T ( "MAPIsendEmail request abandoned" );
      PostP2PeerMsg ( pMsg->ExceptionFactory(pEVT) );
      pEVT -> Cancel(true);
    }

    // Tidy up and
    return msgHANDLED;
}

//
//  MSG_MAPIUninitialize
//  Unitializes an Initialized MAPI environment
//
//
//  Parameters:  P2PeerMsg *pP2PeerMsg
//               Unitialize message
//
//  Returns:     msgRESULT
//               Completion summary
//
msgRESULT
P2PeerHubMAPI::On_MAPIUnitialize  ( P2PeerMsg *pMsg )
{
    // Beacause this is all problematic, we
    try
    {
      // To be sure, to be sure
      // NOTES: Confirm MAPI environment operational
      if ( m_nMAPIPumpID )
      {
        // Confirm thread context
        // NOTES: Disconnect MAPI in context
        if ( !P2PeerContext(m_nMAPIPumpID) )
          return P2PeerContextSwap(m_nMAPIPumpID);

        // Disconnect
        if ( m_pMAPIex )
        {
          delete m_pMAPISession;
                 m_pMAPISession = 0;
          m_pMAPIex -> Uninitialize ( );
          delete m_pMAPIex;
          m_pMAPIex = 0;
        }

        // Tidy up, and
        if ( m_nMAPIPumpID )
          SignalP2PmsgPump ( m_nMAPIPumpID, P2PsigPump_CLOSE );
        m_nMAPIPumpID = 0;
        return ReflectP2PeerMsg ( pMsg );
       }
    }

    // Exceptions
    // NOTES: Post back to source
    catch ( P2Pevent *pEVT )
    {
      pEVT -> Advice_T ( "MAPIUnitialize request abandoned" );
      PostP2PeerMsg ( pMsg->ExceptionFactory(pEVT) );
      pEVT -> Cancel ( );
    }

    catch ( ... )
    {
      P2Pevent *pEVT =
      EVERR->MODULE
           ->Message_T("Last resort exception" );
      PostP2PeerMsg ( pMsg->ExceptionFactory(pEVT) );
      pEVT -> Cancel( );
    }

    // Tidy up and
    // NOTES: We may be processing in the context of the MAPI pump
    if ( m_nMAPIPumpID )
      SignalP2PmsgPump ( m_nMAPIPumpID, P2PsigPump_CLOSE );
    m_nMAPIPumpID = 0;
    return msgHANDLED;
}

