////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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++/Examples/SingleSourceFiles/rcs/SerialDataServer.cpp 1.3 2009/02/04 11:25:03 arturoc Exp $
//                                                                                                                    //
///////// 120 columns, Tab = Insert 3 Spaces ///////////////////////////////////////////////////////////////////////////
/**
   This source code specifically targets the Microsoft Visual Studio 2008 C++ compiler (non-express version).
   
   This source code is an example of an implementation of an External Serialization Server (ESS), which is a COM
   object that generates unique data pattern modifications for a device in a job session using the BPM Microsystems
   BPWin software to operate a BPM Microsystems device programming system, where the "COM Serialization" feature has
   been enabled for the job session.

   This implementation of an ESS utilizes the VC++ header-only library included in the BPM Microsystems COM
   Serialization Software Development Kit (SDK).  The SDK is freely available for download from the BPM Microsystems
   website (http://www.bpmmicro.com), and includes documentation, examples, and code libraries for several programming
   languages that can be used to implement COM objects.

   The library allows you to implement one of several types of "server classes"; which is a C++ class that abstracts
   the logic for generating the unique pattern modifications for a device.  This example demonstrates the
   implementation of a "serial" server class, which is intended for developers that have experience using VC++ to
   work with COM.  A serial server class exposes a certain level of the interface required by BPWin for COM 
   Serialization, which is described in the Appendix of the SDK documentation.

   Essentially, coding with a serial server class will result in the library taking care of implementing everything that
   is needed to create the type of COM object required by BPWin, and defers all other aspects of an ESS to the user of
   the library.  With a serial server class, the library provides no facilities for simplifying or optimizing the
   specification of unique data pattern modifications for a device.  It is instead up to you to correctly set the data
   in the “Scripting Dictionary” object, as described in the Appendix of the SDK documentation.

   The logic specified in this source code implements an ESS that modifies 8 bytes (64-bits) of the data
   pattern beginning at data pattern byte address 0.  The pattern of those bits is the same as the bit pattern used to
   represent the value of an unsigned 64-bit number on an Intel architecture (known as "Little Endian", where the
   Least Significant Byte comes before the Most Significant Byte).  To do this, the logic merely declares and uses
   an "unsigned long long" type to store the bit pattern and assumes the code is compiled for and will run on an Intel
   architecture system.  To generate the unique pattern, the logic is structured to increment the number stored in the
   "unsigned long long" by 1 each time the ESS is queried for data.

   This example also demonstrates the following aspects of implementing a serial server class:

   - Error checking and reporting.  The base class member-function-overrides defined here return HRESULT values that 
     uniquely identify certain errors.  They are also coded to invoke the Microsoft ATL library's AtlReportError()
     global function that sets the information in the global-to-logical-thread IErrorInfo object.  BPWin will detect
     these errors, abort the job session, and display a dialog with the error information supplied here.

   - Logging.  A log file is created in the same path as this executable and it is updated when the following events
     occur:
      - This executable is launched and shut down for any reason.
      - The serial server class is instantiated and destroyed.
      - The job session has started; information regarding some configuration parameters of the job session is
        included in the log.
      - The serial server object is queried for data; information regarding the 64-bit numbers being specified as
        containing the unique pattern data is included in the log.
      - The result of a device operation in the job session has been determined; information regarding the serial
        number used for the device, ordinal of the programmer site used to operate on the device, ordinal of the socket
        module socket used to operate on the device, and result of the operation are included in the log.

   - VC++ "Compiler COM Support" facilities are used to specify the unique data pattern modifications in the
     dictionary object.

   To demonstrate error detection, error reporting, and the termination of a job session as a result of an error, 
   specific values are used to represent arbitrary, hypothetical limits of a job session's configuration.  For example,
   the OnBeginJob() override, below, requires the "Last serial number" specified in the BPWin serialization dialog
   to be no larger than FFFFFFFFEEEEEEEE or an error is reported; also, the number of devices to operate on in a
   job session should not exceed 5000.  These values are not based on any rationale; they were randomly thought-up,
   merely for the purposes of demonstrating error detection based on a hypothetically-undesirable job session
   configuration.

   This source file can be compiled using the "Visual Studio 2008 Command Prompt" with the following command line:

      cl /EHsc SerialDataServer.cpp

   The path to the resulting executable, "SerialDataServer.exe", can be specified in the BPWin Serialization dialog
   as the ESS.  Please refer to the Serialization portion of BPWin Help for details on configuring BPWin.
*/

#include <strstream>
#include <fstream>
#include <ComSerSdk/Application.h>
#include <ComSerSdk/SerialServer.h>

class CSerialDataServer : public ComSerSdk::CSerialServerBase
{
public:

   explicit CSerialDataServer(std::wofstream & oLog):
      m_oLog(oLog)
   {
      m_oLog << "Serial data server object created." << std::endl;
   }

   ~CSerialDataServer()
   {
      m_oLog << "Serial data server object destroyed." << std::endl;
   }

private:

   static const HRESULT ESS_E_LASTNUMTOOLARGE = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x300);
   static const HRESULT ESS_E_TOOMANYDEVICES  = ESS_E_LASTNUMTOOLARGE + 1;
   static const HRESULT ESS_E_GENERAL         = ESS_E_TOOMANYDEVICES  + 1;

   HRESULT OnBeginJob(BSTR sLastNumber, LONG lNumDevices)
   {
      std::wistringstream oInputStringStrm(sLastNumber);
      unsigned long long ullLastNumber = 0;
      if(!(oInputStringStrm >> std::hex >> ullLastNumber))
      {
         std::wostringstream oOutStrm;
         oOutStrm << __FUNCTION__": Could not convert the \"last serial number\" string \"" <<
                     std::wstring(sLastNumber) << "\" to a number.";
         AtlReportError(GUID_NULL, oOutStrm.str().c_str());
         return ESS_E_GENERAL;
      }

      if(ullLastNumber >  0xFFFFFFFFEEEEEEEE)
      {
         AtlReportError(GUID_NULL, __FUNCTION__": The last number is too large.");
         return ESS_E_LASTNUMTOOLARGE;
      }

      if(lNumDevices > 5000)
      {
         AtlReportError(GUID_NULL, __FUNCTION__": BPWin is configured to operate on too many devices.");
         return ESS_E_TOOMANYDEVICES;
      }

      if(!(m_oLog << "A session has started where the last serial number is " << std::hex << ullLastNumber <<
                     "h and the number of devices to operate on is " << std::dec << lNumDevices << ".\n"))
      {
         AtlReportError(GUID_NULL, __FUNCTION__": Could not write entry to log file.");
         return ESS_E_GENERAL;
      }

      return S_OK;
   }

   HRESULT OnResult(BSTR sHandle, VARIANT_BOOL bSuccess, LONG lSite, LONG lSocket)
   {
      if(!(m_oLog << "Device with handle \"" << std::wstring(sHandle) << "\"" 
                     " on site "             << lSite   << 
                     " socket "              << lSocket << " " <<
                     (bSuccess == VARIANT_TRUE ? "passed" : "failed") <<
                     ".\n"))
      {
         AtlReportError(GUID_NULL, __FUNCTION__": Could not write entry to log file.");
         return ESS_E_GENERAL;
      }

      return S_OK;
   }

   HRESULT GetSerialData(BSTR sCurrentNumber, Scripting::IDictionaryPtr & spoDictionary)
   {
      m_oLog << "Serial server object is being queried for unique pattern data.  Current number = " << 
                 std::wstring(sCurrentNumber) << std::endl;

      std::wistringstream oInputStringStrm(sCurrentNumber);
      unsigned long long ullCurrentNumber = 0;
      if(!(oInputStringStrm >> std::hex >> ullCurrentNumber))
      {
         std::wostringstream oOutStrm;
         oOutStrm << __FUNCTION__": Could not convert the \"current serial number\" string \"" <<
                  std::wstring(sCurrentNumber) << "\" to a number.";
         AtlReportError(GUID_NULL, oOutStrm.str().c_str());
         return ESS_E_GENERAL;
      }

      CComSafeArray<BYTE> oByteArray(sizeof ullCurrentNumber);
      memcpy(oByteArray.m_psa->pvData, &ullCurrentNumber, oByteArray.GetCount());

      VARIANT oVar;
      oVar.vt     = VT_ARRAY | VT_UI1;
      oVar.parray = oByteArray.m_psa;

      HRESULT hPutItemResult = spoDictionary->put_Item(&_variant_t("0"), &oVar);
      if(FAILED(hPutItemResult))
      {
         std::ostringstream oOutStrm;
         oOutStrm << __FUNCTION__": Could not put item with key \"0\" and value " << std::hex << ullCurrentNumber
            << "h into dictionary.  IDictionary::put_Item() returned HRESULT " << hPutItemResult;
         AtlReportError(GUID_NULL, oOutStrm.str().c_str());
         return ESS_E_GENERAL;
      }

      std::wostringstream oOutputStringStrm;
      oOutputStringStrm << std::hex << (ullCurrentNumber + 1);
      const std::wstring sNextHandle = oOutputStringStrm.str();
      hPutItemResult = spoDictionary->put_Item(&_variant_t("NextHandle"), &_variant_t(sNextHandle.c_str()));
      if(FAILED(hPutItemResult))
      {
         std::wostringstream oOutStrm;
         oOutStrm << __FUNCTION__": Could not put item with key \"NextHandle\" and value \"" << sNextHandle
                  << "\" into dictionary.  IDictionary::put_Item() returned HRESULT " << hPutItemResult;
         AtlReportError(GUID_NULL, oOutStrm.str().c_str());
         return ESS_E_GENERAL;
      }

      m_oLog << "Unique pattern generation complete.  Next number was set to " << sNextHandle << std::endl;
      
      return S_OK;
   }

   std::wofstream & m_oLog;
};

class CEssApp : public ComSerSdk::CApplicationBase
{
public:

   /**
      Do not let exceptions that we know we throw propagate here since the global instance of this class causes this 
      to execute before main() and would result in an uncaught exception causing the executable to terminate early.
      Instead, in the event of an exception, we allow the object to be constructed, but later on when the
      CreateServer() override is invoked by the library, where we know it's ok to propagate an exception, we do so.
   */
   CEssApp()
   try
   :
      m_oLog(LogPath().c_str(), std::ios_base::app | std::ios_base::out)
   {
      m_oLog << "Serial data server executable started.\n";
   }
   catch(std::exception & oException)
   {
      ::MessageBox(NULL, oException.what(), "Error", MB_OK);
   }

   ~CEssApp()
   {
      m_oLog << "Serial data server executable shutting down.\n";
   }

private:

   ComSerSdk::CServerPtr CreateServer()
   {
      if(!m_oLog.is_open())
      {
         throw ComSerSdk::CUserException("Could not open log file.", COMSERSDK_CODELOCATION);
      }
      return ComSerSdk::CServerPtr(new CSerialDataServer(m_oLog));
   }

   std::string LogPath()
   {
      std::vector<TCHAR> oFileName(_MAX_PATH + 1);
      if(0 == ::GetModuleFileName(NULL, &oFileName.at(0), _MAX_PATH))
      {
         const DWORD dwLastErr = ::GetLastError();
         std::ostringstream oOutStrm;
         oOutStrm << "Could not generate a log file name because could not get path to currently running "
                     "executable.  ::GetModuleFileName() set last error to " << dwLastErr;
         throw std::runtime_error(oOutStrm.str().c_str());
      }
      return std::string(&oFileName.at(0)) + ".log";
   }

   std::wofstream m_oLog;
};

CEssApp g_oEssApp;