//  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 SQL-MAPI integration object
//  NOTES: Integrates SQL database, using ODBC API and MAPI for the purpose
//         of polling a table and sending emails
//               
//

#include "StdAfx.h"
#include "SQLmapiHub.h"
#include "P2Pwin32.h"
#include "P2Pevent.h"
#include "Kernel32_Ext.h"
#include "P2PeerHubODBC.h"
#include "P2PeerHubMAPI.h"

//
//  Standard set of HTTP methods
//#define __GET__  _T("GET")
//#define __POST__ _T("POST")

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

SQLmapiHub::~SQLmapiHub ( void )
{
    // Resources recovery
    delete m_pTargetODBC;
    delete m_pTargetMAPI;
}

void
SQLmapiHub::RenderThisSafe (  )
{
    // Attributes
    m_nPumpODBC              = 0;
    m_pTargetODBC            = nullptr;
    m_nPumpMAPI              = 0;
    m_pTargetMAPI            = nullptr;
    m_nWebServerConID        = 0;      // Accepted connections
    m_nEmailID               = 0;      // SQL table index of last sent email
    m_nPITimerSQL            = 0;      // Polling PITimer
    m_nPITimerSecs           = 0;      // Last polling time
    m_nPITimerSecsMax        = 180;    // Maximum polling time in seconds

    m_strUsernameODBC        = _T("");
    m_strPasswordODBC        = _T("");
    m_strServernameODBC      = _T("Ivyware01\\SQLEXPRESS");
    // SPECIALIZATION No#
    // The above ODBC loggon string will need to be specialized
    // ASSERT(0);
    m_strEmailTablename      = _T("[SQLimapi].[dbo].[TobeSentEmails]");
    // SPECIALIZATION No#
    // The above SQL Email Table name will need to be specialized
    // ASSERT(0);
}

///////////////////////////////////////////////////////////////////////////////
//  Helpers


///////////////////////////////////////////////////////////////////////
//  SQLmapiHub management

//  Pause all webserver operations
//  NOTES: Upon completion hub effectively exists in idle space
//       : Both hub context and non-hub context OK
void
SQLmapiHub::PauseHub ( )
{
    // Pause processing must be performed from context of this hub
    // NOTES: OK to re-signal from any other non-hub context
    if ( GetCurrentThreadId() != GetHubID() )
    {
      if ( GetHubID() > 0 )
        SignalP2PmsgHub ( GetHubID(), P2PsigHub_PAUSE );
      return;
    }
      
    // Because this is all problematic, we
    try
    {
      // Outstanding timers
      // NOTES: May be in limbo waiting for new emails to send
      if ( m_nPITimerSQL )
      {
        m_nPITimerSQL = KillP2PmsgTimer ( m_nPITimerSQL );
        DisconnectODBC ( );
        DisconnectMAPI ( );
      }
      m_nPITimerSecs = 0;

      // Firstly stop accepting new connections
      if ( m_nWebServerConID > 0 )
        ConSignal ( m_nWebServerConID, P2PsigCon_SHUTDOWN );
      m_nWebServerConID = 0;

      // Secondly close all idle client connections
      ConSignal ( _T("*"), P2PsigCon_CLOSEONIDLE );
    }

    // Exceptions
    catch_pP2Pevent_Cancel
    catch_pCException_Cancel
    catch_ALL_Cancel
}

//  Wakeup all webserver operations
//  NOTES: Upon completion hub effectively operational
//       : Both hub context and non-hub context OK
void
SQLmapiHub::WakeupHub ( )
{
    // Wakeup processing must be performed from context of this hub
    // NOTES: OK to re-signal from any other non-hub context
    if ( GetCurrentThreadId() != GetHubID() )
    {
      if ( GetHubID() > 0 )
        SignalP2PmsgHub ( GetHubID(), P2PsigHub_WAKEUP );
      return;
    }
      
    // Because this is all problematic, we
    try
    {
      // Create MIME map
      
      // Start primary listening server
      //P2PeerConAKA *pWebserver
      //  = P2PeerConAKA::ServiceFactory( _T("This"), m_nIPort );
      //              pWebserver -> SetState ( ConState_BCasts
      //                                     | ConState_UCasts, 0 );
      //if ( !PostP2PeerCon(pWebserver) )
      //  ThrowP2Pevent ( );
      //m_nWebServerConID = pWebserver -> GetP2PconID ( );
      
      // Create ODBC processing target
      // NOTES: P2PeerHubODBC is run as a P2PeerTarget in this instance to
      //        remove overhead and complexity of inter-hub P2PeerMsg pumping
      ASSERT(m_pTargetODBC==nullptr);
      if ( m_pTargetODBC == nullptr )
        m_pTargetODBC = new P2PeerHubODBC ( _T("") );
      RegisterTarget ( m_pTargetODBC, +15 );
      
      // Create MAPI processing target
      // NOTES: P2PeerHubMAPI is run as a P2PeerTarget in this instance to
      //        remove overhead and complexity of inter-hub P2PeerMsg pumping
      ASSERT(m_pTargetMAPI==nullptr);
      if ( m_pTargetMAPI == nullptr )
        m_pTargetMAPI = new P2PeerHubMAPI ( _T("") );
      RegisterTarget ( m_pTargetMAPI, +17 );

      // Initiate processing
      P2PeerMsgSP spMsgSQL = new P2PeerMsg ( GetP2PaddrHub(), GetP2PaddrODBC()
                                           , _T("SQLDriverConnect")
                                           , (void *)0, 0 );
      CString strConnection;
      strConnection += _T("DRIVER={SQL Server};");
      strConnection += CString("SERVER=") += m_strServernameODBC += CString(";");
      CString strAuthentication;
      if ( 1 == 1 )
        strAuthentication = _T("Trusted_Connection=yes;");
      if ( 1 == 2 )
        strAuthentication.Format ( _T("UID=%s;PWD=%s;"), m_strUsernameODBC, m_strPasswordODBC );
      strConnection += strAuthentication;
      spMsgSQL -> r_datn().AddField ("ConnectionString", DataBSTR08(strConnection) );
      spMsgSQL = PostP2PeerMsg ( spMsgSQL.Dereference() );
    }

    // Exceptions
    catch_pP2Pevent_Cancel
    catch_pCException_Cancel
    catch_ALL_Cancel
}

///////////////////////////////////////////////////////////////////////
//  Operations
void
SQLmapiHub::SelectNextEmail ( )
{
    // Select next unsent email
    // NOTES: Requires emails to be flagged as sent etc
    CString strSQLselect;
    strSQLselect.Format ( _T("SELECT TOP 1 ")
                          _T("nEmailID as EmailID, ")
                          _T("strFromName as FromName, strFromEmail as FromEmail, ")
                          _T("strTo as 'To', ") //strTo1 as To1, strTo2 as To2, ")
                          _T("strCc as Cc, ") //strCc1 as Cc1, strCc2 as Cc2, ")
                          _T("strBc as Bc, ") //strBc1 as Bc1, strBc2 as Bc2, ")
                          _T("strSubject as 'Subject', strBody as Body, ")
                          _T("strAttachName as AttachName, strAttachContent as AttachContent ")
                          _T("FROM %s ")
                          _T("WHERE nEmailID > %i and bEmailSent < 1;")
                        , m_strEmailTablename, m_nEmailID );
    // SPECIALIZATION No#
    // The above SQL SELECT statement will need to be specialized according to your target table
    // ASSERT(0);

    // Package and issue above request
    // NOTES: Response drives the process.  P2PeerMsg is routed to the MAPI hub
    //        and selected record reflected back to this hub
    P2PeerMsgSP spMsgSQL = new P2PeerMsg ( GetP2PaddrHub(), GetP2PaddrODBC()
                                         , _T("SQLselect")
                                         , (void *)0, 0 );
    spMsgSQL -> r_datn().AddField ("SQLstmt", DataBSTR16(strSQLselect) );
    spMsgSQL -> r_datn().AddField ("MsgReply", DataBSTR16("SQLemail2send") );
    PostP2PeerMsg   ( spMsgSQL.Dereference() );
}

void
SQLmapiHub::DisconnectMAPI ( )
{
    ASSERT(0);
}
void
SQLmapiHub::DisconnectODBC ( )
{
    ASSERT(0);
}

///////////////////////////////////////////////////////////////////////////////
//  Properties

LPCTSTR
SQLmapiHub::GetP2PaddrMAPI ( )
{
    return m_pTargetMAPI->GetP2PaddrHub();
}
LPCTSTR
SQLmapiHub::GetP2PaddrODBC ( )
{
    return m_pTargetODBC->GetP2PaddrHub();
}

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

//
//   PITimer handler for hub
//   NOTES: Used for next email delayed polling
void
SQLmapiHub::On_PITimer ( bool bCancel, PITimerID nPITimerID, DWORD dwUserKey )
{
    // Handle next email delayed polling
    if ( nPITimerID == m_nPITimerSQL )
    {
      m_nPITimerSQL = 0;               // Expired
      if ( !bCancel )
        SelectNextEmail ( );           // Self contained
      return;
    }

    // Delegate
  __super::On_PITimer ( bCancel, nPITimerID, dwUserKey );
}

///////////////////////////////////////////////////////////////////////
//  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(SQLmapiHub, 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(SQLmapiHub, P2PeerHub)
    ON_P2PeerMsg("StartWebserver",On_StartSQL2MAPI)
    ON_P2PeerMsg("StopWebserver",On_StopSQL2MAPI)
    // ODBC Handlers
    ON_P2PeerMsg_REFLECT("SQLDriverConnect",On_SQLconnected)
    //ON_P2PeerMsg_CATCH("SQLDriverConnect",On_ProcessError)
    ON_P2PeerMsg("SQLemail2send",On_SQLemail2send)
    //ON_P2PeerMsg_CATCH("SQLupdate",On_ProcessError)
    ON_P2PeerMsg("SQLemailCompleted",On_SQLemailCompleted)
    // MAPI Handlers
    ON_P2PeerMsg_REFLECT("MAPIInitialize",On_MAPIInitialised)
    ON_P2PeerMsg_REFLECT("MAPILogonEx",On_MAPILoggedOn)
    ON_P2PeerMsg_REFLECT("MAPIsendEmail",On_MAPIemailsent)
    //ON_P2PeerMsg_CATCH("MAPIsendEmail",On_ProcessError)
END_P2PeerMsg_MAP()
//
//  MSG_StartSQL2MAPI
//
//  Parameters:  P2PeerMsg *pP2PeerMsg
//               Start emailing message
//
//  Returns:     msgRESULT
//               Completion summary
//
msgRESULT
SQLmapiHub::On_StartSQL2MAPI  ( P2PeerMsg *pMsg )
{
    // Locals

    // Because this is all problematic, we
    try
    {
      // Extract and delegate
      WakeupHub ( );
    }

    // Exceptions
    catch_pP2Pevent_Cancel
    catch_pCException_Cancel
    catch_ALL_Cancel

    // Shutdown restart
    return msgHANDLED;
}

//
//  MSG_StopWebserver
//
//  Parameters:  P2PeerMsg *pP2PeerMsg
//               Stop emailing message
//
//  Returns:     msgRESULT
//               Completion summary
//
msgRESULT
SQLmapiHub::On_StopSQL2MAPI  ( P2PeerMsg *pMsg )
{
    // Because this is all problematic, we
    try
    {
      // Delegate
      PauseHub ( );
    }

    // Exceptions
    catch_pP2Pevent_Cancel
    catch_pCException_Cancel
    catch_ALL_Cancel

    // Shutdown restart
    return msgHANDLED;
}


///////////////////////////////////////////////////////////////////////
//  ODBC management handlers

//
//  Reflected MSG_SQLDriverConnect handler
//  NOTES: Nominated MSG_SQLDriverConnect response handler
//
msgRESULT
SQLmapiHub::On_SQLconnected ( P2PeerMsg *pMsg )
{
    // Because this is all problematic, we
    try
    {
      // Progogate initialization sequence
      P2PeerMsgSP spMsgMAPI = new P2PeerMsg ( GetP2PaddrHub(), GetP2PaddrMAPI()
                                            , "MAPIInitialize"
                                            , (void *)0, 0 );
      spMsgMAPI = PostP2PeerMsg ( spMsgMAPI.Dereference() );
    }

    // Exceptions
    catch_pP2Pevent_Cancel
    catch_pCException_Cancel
    catch_ALL_Cancel

    // Shutdown restart
    return msgHANDLED;
}

//
//  MSG_SQLemail2send handler
//  NOTES: Nominated MSG_SQLselect response message
//
msgRESULT
SQLmapiHub::On_SQLemail2send ( P2PeerMsg *pMsg )
{
    // Issues expected
    try
    {
      // Intercept nothing to email
      // NOTES: Activate P2PmsgTimer for later processing
      if ( !pMsg->r_datn().Exists("SQL") )
      {
        if ( m_nPITimerSQL > 0 )
          return msgHANDLED;           // Outstanding timer exists
        m_nPITimerSecs = m_nPITimerSecs * 13 / 10 + 1;
        if ( m_nPITimerSecs > m_nPITimerSecsMax )
          m_nPITimerSecs = m_nPITimerSecsMax;
        m_nPITimerSQL = SetPITimer ( m_nPITimerSecs*1000, 0 );
        return msgHANDLED;             // On_PITimer() re-activates processing
      }

      // Rename and re-pump P2PeerMsg directly through to MAPI
      // NOTES: Single chance to send email on each pass otherwise we may
      //        indefinately loop on ill-formed data
      //      : Qualify the email passed to MAPI as being in SQL format
      m_nPITimerSecs = 0;              // Immediate poll next time
      m_nEmailID = pMsg->r_datn().SelectNode("SQL")["EmailID"].c_int();
      pMsg -> r_name().c_name ( "MAPIsendEmail", 0 );
      pMsg -> r_datn().r_Attr().DeclareField("Format-SQL",P3PmsgData());
      return RepumpP2PeerMsg ( pMsg );
    }

    // Exceptions
    catch_pP2Pevent_Cancel
    catch_pCException_Cancel
    catch_ALL_Cancel

    // Shutdown restart
    return msgHANDLED;
}

//
//  MSG_SQLemailCompleted handler
//  NOTES: Nominated MSG_SQLupdate response message
//
msgRESULT
SQLmapiHub::On_SQLemailCompleted ( P2PeerMsg *pMsg )
{
    // Issues expected
    try
    {
      // Tidy up, and
      // NOTES: Initiate processing for next email
      SelectNextEmail ( );
      return msgHANDLED;
    }

    // Exceptions
    catch_pP2Pevent_Cancel
    catch_pCException_Cancel
    catch_ALL_Cancel

    // Shutdown restart
    return msgHANDLED;
}

///////////////////////////////////////////////////////////////////////////////
//  MAPI Handlers

//
//  MSG_MAPIInitialized 
//  NOTES: Nominated MSG_MAPIInitialize response message
msgRESULT
SQLmapiHub::On_MAPIInitialised ( P2PeerMsg *pMsg )
{
    // Issues expected
    try
    {
      // Propogate with MAPI logon sequence
      P2PeerMsgSP spMsgMAPI = new P2PeerMsg ( GetP2PaddrHub(), GetP2PaddrMAPI()
                                            , "MAPILogonEx"
                                            , (void *)0, 0 );
      spMsgMAPI->r_datn() += P3PmsgNode ( "MAPILogonEx", P3PmsgData() );
      P3PmsgNode oMAPILogonEx = spMsgMAPI->r_datn().SelectNode("MAPILogonEx").ROBJ;
      ULONG_PTR ulUIParam = 0;
      if ( ulUIParam )
        oMAPILogonEx += P3PmsgField("UIParam",P3PmsgData(ulUIParam));
      CString strProfileName;
      if ( !strProfileName.IsEmpty() )
        oMAPILogonEx += P3PmsgField("ProfileName",P3PmsgData(strProfileName) );
      CString strPassword;
      if ( !strPassword.IsEmpty() )
        oMAPILogonEx += P3PmsgField("Password",P3PmsgData(strPassword) );
      FLAGS flFlags = MAPI_EXTENDED | MAPI_USE_DEFAULT | MAPI_NEW_SESSION
                    | MAPI_EXPLICIT_PROFILE | MAPI_NT_SERVICE;
      if ( flFlags )
        oMAPILogonEx += P3PmsgField("Flags",P3PmsgData(flFlags) );
      spMsgMAPI = PostP2PeerMsg ( spMsgMAPI.Dereference() );
    }

    // Exceptions
    catch_pP2Pevent_Cancel
    catch_pCException_Cancel
    catch_ALL_Cancel

    // Shutdown restart
    return msgHANDLED;
}

//  MSG_MAPILoggedon handler
//  NOTES: Initiate email processing from here only.  Since we require
//         a fully operational MAPI to send emails
msgRESULT
SQLmapiHub::On_MAPILoggedOn ( P2PeerMsg *pMsg )
{
    // Issues expected
    try
    {
       // Tidy up, and
       // NOTES: Initiate processing for next email
       SelectNextEmail ( );
       return msgHANDLED;
    }

    // Exceptions
    catch_pP2Pevent_Cancel
    catch_pCException_Cancel
    catch_ALL_Cancel

    // Shutdown restart
    // TODO:LJM Requires reset logic
    return msgHANDLED;
}

//
//  MSG_SQLemailsent handler
//  NOTES: Nominated MSG_SQLconnect2server response message
//
msgRESULT
SQLmapiHub::On_MAPIemailsent ( P2PeerMsg *pMsg )
{
    try
    {
       // Extract information
       // NOTES: Various fields sit passively in pumped P2PeerMsg until required
       P3PmsgNode oNodeSQL = pMsg->r_datn().SelectNode(_T("SQL")).ROBJ;
       long       nEmailID = oNodeSQL["EmailID"].c_int();

       // Flag email as sent
       // NOTES: Requires SQL update to block re-tranmission on re-start or
       //        re-synchronisation etc
       CString strSQLupdate;
       strSQLupdate.Format ( _T("UPDATE %s ")
                             _T("SET bEmailSent=1 ")
                             _T("WHERE nEmailID=%i;")
                           , m_strEmailTablename, nEmailID );
       // SPECIALIZATION No#
       // The above SQL UPDATE statement will need to be specialized according to your target table
       // ASSERT(0);

       // Peform update
       // NOTES: Assume sequence may or may not proceed to completion 
       // P2PeerHubODBC request
       P2PeerMsgSP spMsgSQL = new P2PeerMsg ( GetP2PaddrHub(), GetP2PaddrMAPI()
                                            , _T("SQLupdate")
                                            , (void *)0, 0 );
       spMsgSQL -> r_datn().AddField ("SQLstmt"
                                     , DataBSTR16(strSQLupdate) );
       spMsgSQL -> r_datn().AddField ("MsgReply"
                                     , DataBSTR16("SQLemailCompleted") );
       PostP2PeerMsg   ( spMsgSQL.Dereference() );

       // Tidy up, and
       // NOTES: Initiate processing for next email
       return msgHANDLED;
    }

    // Exceptions
    catch_pP2Pevent_Cancel
    catch_pCException_Cancel
    catch_ALL_Cancel

    // Shutdown restart
    // TODO:LJM Requires reset logic
    return msgHANDLED;
}
msgRESULT
SQLmapiHub::On_MAPIexception ( P2PeerMsg *pMsg )
{
    // Issues expected
    try
    {
    }

    // Exceptions
    catch_pP2Pevent_Cancel
    catch_pCException_Cancel
    catch_ALL_Cancel

    // Shutdown restart
    return msgHANDLED;
}
