////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// This is a part of the COM Serialization SDK VC++ Library.
//  
// Copyright 2009 BPM Microsystems
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//                                                                                                                    //
// $Header: //sesca/src/Winprog/COMSerializationSDK/VC++/HeaderLib/ComSerSdk/Detail/rcs/Server.h 1.6 2011/07/21 11:29:48 arturoc Exp $
//                                                                                                                    //
///////// 120 columns, Tab = Insert 3 Spaces ///////////////////////////////////////////////////////////////////////////

/**
   This header file defines the CServerBase and CServerCommonBase classes used throughout the implementation of the
   library.
*/
#pragma once

#include <memory>
#include <string>
#include "Atl.h"
#include "ScriptingRuntime.h"
#include "Exception.h"
#include "Constants.h"
#include "Detail/Vector.h"
#include "Detail/LastError.h"
#include "..\ByteArray.h"

namespace ComSerSdk
{

namespace Detail
{
   
CLastError GetLastError();
/**
   Root of the entire server class hierarchy.  Manages the creation of and owns the dictionary object.
   The ESS COM object forwards its functionality to our pure-virtuals.
*/
class CServerBase
{
public:

   /**
      Type of smart-pointer-to-us.
   */
   typedef std::auto_ptr<CServerBase> CPtr;

   /**
      So that the ESS COM object's GetSerialData() method can forward its functionality to a concrete of this class.
   */
   virtual HRESULT GetSerialData(BSTR sCurrentNumber, IDispatch ** ppoDictionaryAsIDispatch) = 0;
   virtual HRESULT GetSerialAddresses(IDispatch ** ppoDictionaryAsIDispatch) = 0;

   /**
      So that the ESS COM object's OnBeginJob() method can forward its functionality to a concrete of this class.
   */
   virtual HRESULT OnBeginJob(BSTR sLastHandle, LONG lNumDevices) = 0;
   
   /**
      So that the ESS COM object's OnResult() method can forward its functionality to a concrete of this class.
   */
   virtual HRESULT OnResult(BSTR sHandle, VARIANT_BOOL bSuccess, LONG lSite, LONG lSocket) = 0;

   virtual HRESULT OnSocketResult(BSTR sHandle, VARIANT_BOOL bSuccess, LONG lSite, LONG lSocket,
                                  BSTR sSiteId, BSTR sAdapter, BSTR sSocketId) = 0;

   /**
      So that the ESS COM object's SetInitializationData() method can forward its functionality to a concrete of this
      class.
   */
   virtual HRESULT SetInitializationData(BSTR sInitializationData) = 0;

   virtual HRESULT GetSocketSerialData(BSTR sCurrentNumber, long lSite, long lSocket,
                                       BSTR sSiteId, BSTR sAdapter, BSTR sSocketId, IDispatch ** ppoDictionaryAsIDispatch) = 0;

   virtual HRESULT OnRetry(BSTR sCurrentHandle, LONG lSite, LONG lSocket, BSTR sSiteId, BSTR sAdapter, BSTR sSocketId) = 0;

   

   /**
      Instantiates the dictionary object. 
   */
   CServerBase():
      m_spoDictionary(CreateDictionary())
   {}

   /**
      Must be virtual since this instance is deleted via ptr-to-base.
   */
   virtual ~CServerBase()
   {}

   /**
      Member function pointer type, instance of which is passed to CallUserDdsMethod().
   */
   typedef void (CServerBase::* UserMethodType)(const std::string & sHandle,
                                                const CInBoundByteArray & oDataIn,
                                                COutBoundByteArray & oDataOut);

   /**
      Calls CServerBase's implementation of the DDS member function which is passed in.
   */
   virtual HRESULT CallUserDdsMethod(UserMethodType pfnUserMethod,
                                     BSTR sHandle,
                                     VARIANT oDataIn,
                                     VARIANT* poDataOut)
   {
      CInBoundByteArray  oInBoundByteArray(oDataIn);
      COutBoundByteArray oOutBoundByteArray;

      (this->*pfnUserMethod)(
                             static_cast<const char *> // invokes _bstr_t's "operator const char *()"
                                 (_bstr_t(sHandle)),
                             oInBoundByteArray,
                             oOutBoundByteArray);

      CComSafeArray<BYTE> & oSafeArray = oOutBoundByteArray.WrappedSafeArray();
      
      if(oSafeArray.m_psa)
      {
         poDataOut->vt     = oSafeArray.GetType() | VT_ARRAY;
         poDataOut->parray = oSafeArray.Detach();
      }
      return S_OK;
   }

/**
Expands to a member function that implements a version of the "Template Method" design pattern.
Defines a Device-Driven Serialization (DDS) member function to be called from the ESS class.
*/
#define COMSERSDK_SERVERBASE_TEMPMETH(methname)                                      \
   virtual HRESULT methname(BSTR sHandle, VARIANT oDataIn, VARIANT* poDataOut)       \
   {                                                                                 \
      return CallUserDdsMethod(&CServerBase::methname, sHandle, oDataIn, poDataOut); \
   } 

#if defined(COMSERSDK_METHOD1NAME)
   COMSERSDK_SERVERBASE_TEMPMETH(COMSERSDK_METHOD1NAME)
#endif

#if defined(COMSERSDK_METHOD2NAME)
   COMSERSDK_SERVERBASE_TEMPMETH(COMSERSDK_METHOD2NAME)
#endif

#if defined(COMSERSDK_METHOD3NAME)
   COMSERSDK_SERVERBASE_TEMPMETH(COMSERSDK_METHOD3NAME)
#endif

#if defined(COMSERSDK_METHOD4NAME)
   COMSERSDK_SERVERBASE_TEMPMETH(COMSERSDK_METHOD4NAME)
#endif

#if defined(COMSERSDK_METHOD5NAME)
   COMSERSDK_SERVERBASE_TEMPMETH(COMSERSDK_METHOD5NAME)
#endif

#if defined(COMSERSDK_METHOD6NAME)
   COMSERSDK_SERVERBASE_TEMPMETH(COMSERSDK_METHOD6NAME)
#endif

#if defined(COMSERSDK_METHOD7NAME)
   COMSERSDK_SERVERBASE_TEMPMETH(COMSERSDK_METHOD7NAME)
#endif

#if defined(COMSERSDK_METHOD8NAME)
   COMSERSDK_SERVERBASE_TEMPMETH(COMSERSDK_METHOD8NAME)
#endif

#if defined(COMSERSDK_METHOD9NAME)
   COMSERSDK_SERVERBASE_TEMPMETH(COMSERSDK_METHOD9NAME)
#endif

#if defined(COMSERSDK_METHOD10NAME)
   COMSERSDK_SERVERBASE_TEMPMETH(COMSERSDK_METHOD10NAME)
#endif

#if defined(COMSERSDK_METHOD11NAME)
   COMSERSDK_SERVERBASE_TEMPMETH(COMSERSDK_METHOD11NAME)
#endif

#if defined(COMSERSDK_METHOD12NAME)
   COMSERSDK_SERVERBASE_TEMPMETH(COMSERSDK_METHOD12NAME)
#endif

#undef COMSERSDK_SERVERBASE_TEMPMETH

/**
The definition of COMSERSDK_SERVERBASE_TEMPMETH, above, defines a member function that invokes a library-user's
member function.  That member function is declared here as pure-virtual, forcing the library user to define it.
*/
#define COMSERSDK_SERVERBASE_PUREVIRT(methname) virtual void methname(const std::string & sHandle,       \
                                                                      const CInBoundByteArray & oDataIn, \
                                                                      COutBoundByteArray & oDataOut) = 0;

#if defined(COMSERSDK_METHOD1NAME)
   COMSERSDK_SERVERBASE_PUREVIRT(COMSERSDK_METHOD1NAME)
#endif

#if defined(COMSERSDK_METHOD2NAME)
   COMSERSDK_SERVERBASE_PUREVIRT(COMSERSDK_METHOD2NAME)
#endif

#if defined(COMSERSDK_METHOD3NAME)
   COMSERSDK_SERVERBASE_PUREVIRT(COMSERSDK_METHOD3NAME)
#endif

#if defined(COMSERSDK_METHOD4NAME)
   COMSERSDK_SERVERBASE_PUREVIRT(COMSERSDK_METHOD4NAME)
#endif

#if defined(COMSERSDK_METHOD5NAME)
   COMSERSDK_SERVERBASE_PUREVIRT(COMSERSDK_METHOD5NAME)
#endif

#if defined(COMSERSDK_METHOD6NAME)
   COMSERSDK_SERVERBASE_PUREVIRT(COMSERSDK_METHOD6NAME)
#endif

#if defined(COMSERSDK_METHOD7NAME)
   COMSERSDK_SERVERBASE_PUREVIRT(COMSERSDK_METHOD7NAME)
#endif

#if defined(COMSERSDK_METHOD8NAME)
   COMSERSDK_SERVERBASE_PUREVIRT(COMSERSDK_METHOD8NAME)
#endif

#if defined(COMSERSDK_METHOD9NAME)
   COMSERSDK_SERVERBASE_PUREVIRT(COMSERSDK_METHOD9NAME)
#endif

#if defined(COMSERSDK_METHOD10NAME)
   COMSERSDK_SERVERBASE_PUREVIRT(COMSERSDK_METHOD10NAME)
#endif

#if defined(COMSERSDK_METHOD11NAME)
   COMSERSDK_SERVERBASE_PUREVIRT(COMSERSDK_METHOD11NAME)
#endif

#if defined(COMSERSDK_METHOD12NAME)
   COMSERSDK_SERVERBASE_PUREVIRT(COMSERSDK_METHOD12NAME)
#endif

/**
Not needed anywhere else.
*/
#undef COMSERSDK_SERVERBASE_PUREVIRT

   /**
      Creates an instance of a dictionary object; throws an exception if that operation fails.
   */
   static Scripting::IDictionaryPtr CreateDictionary()
   {
      Scripting::IDictionaryPtr spoDictionary;
      const HRESULT hResult = spoDictionary.CreateInstance(__uuidof(Scripting::Dictionary));
      if(FAILED(hResult))
      {
         throw CHresultException("Could not create Scripting.Dictionary object.",
                                 hResult,
                                 "IDictionaryPtr.CoCreateInstance(__uuidof(Scripting::Dictionary))",
                                 COMSERSDK_CODELOCATION);
      }
      return spoDictionary;
   }

protected:

   /**
      So that concretes can access the dictionary object.
   */
   Scripting::IDictionaryPtr & Dictionary()
   {
      return m_spoDictionary;
   }

private:

   /**
      The dictionary instance.
   */
   Scripting::IDictionaryPtr m_spoDictionary;
};

/**
   Acts as a less-restrictive, not-so-COM-oriented version of CServerBase, which results in being appropriate as a 
   base class for for 2 of the 3 server base classes defined in this library.
*/
class CServerCommonBase : public CServerBase
{
   /**
      So that concretes can OPTIONALLY provide an override of OnBeginJob(), and if overriden, would work with
      parameters that have types that are plain-old native C++ types instead of COM/microsoft-specific types.
   */
   virtual void OnBeginJob(const std::string & sHandle, unsigned uNumDevices){}
   
   /**
      So that concretes can OPTIONALLY provide an override of OnResult(), and if overriden, would work with
      parameters that have types that are plain-old native C++ types instead of COM/microsoft-specific types.
   */
   virtual void OnResult(const std::string & sHandle, bool bSuccess, unsigned uSite, unsigned uSocket){}

   virtual void OnSocketResult(const std::string & sHandle, bool bSuccess, unsigned uSite, unsigned uSocket,
                               const std::string & sSiteId, const std::string & sAdapter, const std::string & sSocketId){}

   virtual void OnRetry(const std::string & sHandle, unsigned uSite, unsigned uSocket,
                        const std::string & sSiteId, const std::string & sAdapter, const std::string & sSocketId){}

   /**
      So that concretes can OPTIONALLY provide an override of SetInitializationData(), and if overriden, would work
      with parameters that have types that are plain-old native C++ types instead of COM/microsoft-specific types.
   */
   virtual void SetInitializationData(const std::string & sInitializationData)
   {
      throw CLibraryException("BPWin sent initialization data to the ESS, but the ESS was not written to support "
                              "receiving initialization data. You must either configure BPWin to not send "
                              "initialization data to the ESS or, to support initialization data, please ask the "
                              "developer of the ESS to consult the SDK documentation for details on overriding the "
                              "SetInitializationData() member function.",
                              COMSERSDK_CODELOCATION);
   }

   /**
      /override
      Convert the parameters and forward our implementation to our concrete's OnBeginJob().
   */
   virtual HRESULT OnBeginJob(BSTR sLastHandle, LONG lNumDevices)
   {
      OnBeginJob(std::string(_bstr_t(sLastHandle)), lNumDevices);
      return S_OK;
   }

   /**
      /override
      Convert the parameters and forward our implementation to our concrete's OnResult().
   */
   virtual HRESULT OnResult(BSTR sHandle, VARIANT_BOOL bSuccess, LONG lSite, LONG lSocket)
   {
      OnResult(std::string(_bstr_t(sHandle)), bSuccess != VARIANT_FALSE, lSite, lSocket);
      return S_OK;
   }

   /**
      /override
      Convert the parameters and forward our implementation to our concrete's OnResult().
   */
   virtual HRESULT OnSocketResult(BSTR sHandle, VARIANT_BOOL bSuccess, LONG lSite, LONG lSocket, BSTR sSiteId, BSTR sAdapter,
                                  BSTR sSocketId)
   {
      OnSocketResult(std::string(_bstr_t(sHandle)),
                     bSuccess != VARIANT_FALSE,
                     lSite,
                     lSocket,
                     std::string(_bstr_t(sSiteId)),
                     std::string(_bstr_t(sAdapter)),
                     std::string(_bstr_t(sSocketId)));
      return S_OK;
   }

   /**
      /override 
      We need to do some extra processing ourselves before deferring to the concrete's implementation of
      DoGetSerialData().
   */
   virtual HRESULT GetSerialData(BSTR sCurrentNumber, IDispatch ** ppoDictionaryAsIDispatch)
   {
      RemoveLogTextKeyFromDictionary();
      const HRESULT hGetSerDataResult = DoGetSerialData(sCurrentNumber, ppoDictionaryAsIDispatch);
      if (SUCCEEDED(hGetSerDataResult))
      {
         auto oLastError = ComSerSdk::Detail::GetLastError();
         if (!oLastError.m_sDescription.empty() || FAILED(oLastError.m_hResult))
         {
            throw CHresultException(oLastError.m_sDescription,
                                    oLastError.m_hResult,
                                    "DoGetSerialData()",
                                    COMSERSDK_CODELOCATION);
         }
      }
      return DoGetSerialData(sCurrentNumber, ppoDictionaryAsIDispatch);
   }

   virtual HRESULT GetSerialAddresses(IDispatch ** ppoDictionaryAsIDispatch)
   {
      return DoGetSerialAddresses(ppoDictionaryAsIDispatch);
   }
   /**
      /override 
      We need to do some extra processing ourselves before deferring to the concrete's implementation of
      DoGetSerialData().
   */
   virtual HRESULT GetSocketSerialData(BSTR sCurrentNumber,
                                       long lSite,
                                       long lSocket,
                                       BSTR sSiteId,
                                       BSTR sAdapter,
                                       BSTR sSocketId,
                                       IDispatch ** ppoDictionaryAsIDispatch)
   {
      RemoveLogTextKeyFromDictionary();
      return DoGetSocketSerialData(sCurrentNumber, lSite, lSocket, sSiteId, sAdapter, sSocketId, ppoDictionaryAsIDispatch);
   }

   /**
      /override
      Convert the parameters and forward our implementation to our concrete's SetInitializationData().
   */
   virtual HRESULT SetInitializationData(BSTR sInitializationData)
   {
      SetInitializationData(std::string(_bstr_t(sInitializationData)));
      return S_OK;
   }

   virtual HRESULT OnRetry(BSTR sCurrentHandle, LONG lSite, LONG lSocket, BSTR sSiteId, BSTR sAdapter, BSTR sSocketId)
   {
      OnRetry(std::string(_bstr_t(sCurrentHandle)),
              lSite,
              lSocket,
              std::string(_bstr_t(sSiteId)),
              std::string(_bstr_t(sAdapter)),
              std::string(_bstr_t(sSocketId)));
      return S_OK;
   }

private:

   /**
      Concretes override this to implement their GetSerialData() logic.
   */
   virtual HRESULT DoGetSerialData(BSTR sCurrentHandle, IDispatch ** ppDictionaryAsDisp) = 0;
   virtual HRESULT DoGetSerialAddresses(IDispatch ** ppDictionaryAsDisp) = 0;
   
   virtual HRESULT DoGetSocketSerialData(BSTR sCurrentNumber, long lSite, long lSocket, 
                                         BSTR sSiteId,
                                         BSTR sAdapter,
                                         BSTR sSocketId,
                                         IDispatch ** ppoDictionaryAsIDispatch)
   {
      return S_OK;
   }

   /**
   Before each GetSerialData() call, with this function we will remove the log text item added to the dictionary on the
   previous call.  This helps ensure that log text does not remain stale between GetSerialData() invocations.  If it
   remained stale, it would cause BPWin to output the same log text after each GetSerialData() call, with the
   library-user having taken no action to indicate that such behavior on the part of BPWin is desired.
   */
   void RemoveLogTextKeyFromDictionary()
   {
      if(!Exists(Dictionary(), g_oLogTextKey, COMSERSDK_CODELOCATION)) return;
      
      const HRESULT hResult = Dictionary()->Remove(&_variant_t(g_oLogTextKey));

      if(FAILED(hResult))
      {
         std::ostringstream oOutStream;
         oOutStream << "Could not remove item " << g_oLogTextKey << " from scripting dictionary.";
         throw CHresultException(oOutStream.str(),
                                 hResult,
                                 "Dictionary()->Remove(&_variant_t(g_oLogTextKey))",
                                 COMSERSDK_CODELOCATION);
      }
   }

protected:
   
   /**
      Provided to the library user so that they can add log information to BPWin's blackbox.log and serial.log.
   */
   void Log(const std::string & sText)
   {
      Detail::PutItem(Dictionary(),
                      _variant_t(g_oLogTextKey),
                      _variant_t(sText.c_str()),
                      COMSERSDK_CODELOCATION);
   }
};

}// end namespace Detail
typedef Detail::CServerBase::CPtr CServerPtr;
}// end namespace ComSerSdk