////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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/StaticVectorServer.cpp 1.3 2009/06/05 11:49:26 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.

   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 "static vector server" class, which provides a certain level of safety and optimization
   at the cost of flexibility; the data pattern addresses to modify must be known at compile time, but it is
   impossible for any addresses other than those to be modified at runtime; also, memory usage is minimized by
   the library, but you cannot determine when the memory for your data pattern modifications is allocated.  These
   aspects of a static vector server class exist in contrast to the flexibility provided by another type of server
   class the library allows you to implement called a "vector server".

   The logic specified in this source code implements an ESS that modifies 8MB of the data pattern beginning
   at data pattern byte address 0.  The data that represents the new values for bytes in the data pattern exists in a
   binary file named "DeviceData.bin" that resides in the same directory as this executable.  If that file doesn't
   exist then a sample file with pseudo-random data, enough for 10 devices, is generated.  The specific data that has
   been used from the file is tracked using a seperate text file named "ProgrammedPositions.txt", which holds a 
   newline-delimited list of ordinals (in ASCII-decimal) of used chunks from DeviceData.bin.  The programmed positions
   file is read and updated to ensure that the data for a device in DeviceData.bin is never re-used.

   This example also demonstrates error checking and reporting aspects of implementing a static vector server class.
   Upon error detection, the logic here throws exceptions of class ComSerSdk::CUserException and are configured with
   detailed error information.  As a result of throwing those exceptions, BPWin will display the error information in a
   dialog box and abort the job session.

   Also demonstrated is one of the major advantages COM Serialization has over Legacy Complex Serialization, which is
   that there is no need to ever have the pattern modifications be in some specific, non-binary format.  The data
   originates in binary and is delivered to BPWin in binary.  In Legacy Complex Serialization, for each device, an
   executable would need to be launched that would execute code to write the data (usually in a non-binary form) to a
   data file, the executable would shut down, then BPWin would have to read and interpret the data in the file to apply
   it to the pattern.  All of this extra processing that occurs for Legacy Complex Serialization is elided in COM
   Serialization.  This is particulary beneficial when dealing with relatively large amounts of data.

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

      cl /EHsc StaticVectorServer.cpp

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

#include <fstream>
#include <vector>
#include <set>
#include <strstream>
#include <iterator>
#include <direct.h>
#include <io.h>
#include <ComSerSdk/Application.h>
#include <ComSerSdk/StaticVectorServer.h>

typedef ComSerSdk::CStaticVector<0, 0x800000> CStaticVector;

class CStaticVectorServer : public ComSerSdk::CStaticVectorServerBase<CStaticVector>
{
public:
   CStaticVectorServer():
      m_sExeDirectory(DetermineDirectory()),
      m_sDataFileName(AppendToPath(m_sExeDirectory, "DeviceData.bin")),
      m_hDeviceData(OpenDeviceDataFile()),
      m_bFirst(true),
      m_ullFirstPos(0),
      m_sTrackerFname(AppendToPath(m_sExeDirectory, "ProgrammedPositions.txt"))
   {
      if(_access(m_sTrackerFname.c_str(), 0) == 0)
      {
         std::ifstream oProgrammedTracker(m_sTrackerFname.c_str());
         if(!oProgrammedTracker.is_open())
         {
            std::ostringstream oOutStrm;
            oOutStrm << "Could not open programmed data tracker file \"" << m_sTrackerFname << "\" for reading.";
            throw ComSerSdk::CUserException(oOutStrm.str(), COMSERSDK_CODELOCATION);
         }

         std::copy(std::istream_iterator<unsigned long long>(oProgrammedTracker),
                   std::istream_iterator<unsigned long long>(),
                   std::inserter(m_oPrammedPositions, m_oPrammedPositions.end()));
      }
   }

   ~CStaticVectorServer()
   {
      ::CloseHandle(m_hDeviceData);
   }

private:

   void SetVector(const std::string &, std::string & sNextHandle, CStaticVector & oVec)
   {
      GoToNextAvailable();
      const unsigned long long ullFilePosBeforeRead = CurrentFilePosition();
      if(m_bFirst)
      {
         m_ullFirstPos = ullFilePosBeforeRead;
      }
      DWORD dwNumberOfBytesRead = 0;
      if(0 == ::ReadFile(m_hDeviceData,
                         oVec.GetRawPtr(),
                         oVec.Size(),
                         &dwNumberOfBytesRead,
                         NULL))
      {
         const DWORD dwLastErr = ::GetLastError();
         std::ostringstream oOutStrm;
         oOutStrm << "Could not read " << oVec.Size() << " bytes from pattern data file \"" << m_sDataFileName <<
                     "\" at file position " << ullFilePosBeforeRead << ".  ::ReadFile() set last error to " <<
                     dwLastErr;
         throw ComSerSdk::CUserException(oOutStrm.str(), COMSERSDK_CODELOCATION);
      }

      if(dwNumberOfBytesRead != oVec.Size())
      {
         std::ostringstream oOutStrm;
         oOutStrm << "Could not read " << oVec.Size() << " bytes from pattern data file \"" << m_sDataFileName <<
                     "\" at file position " << ullFilePosBeforeRead << ".  Instead " << dwNumberOfBytesRead << " bytes"
                     " were read.";
         throw ComSerSdk::CUserException(oOutStrm.str(), COMSERSDK_CODELOCATION);
      }

      std::ostringstream oOutStringStrm;
      const unsigned long long ullCurrPos = CurrentFilePosition();
      oOutStringStrm << ullCurrPos;
      sNextHandle = oOutStringStrm.str();
   }

   void OnResult(const std::string & sHandle,  bool bSuccess, unsigned, unsigned)
   {
      if(!bSuccess) return;

      unsigned long long ullPosition = 0;
      if(m_bFirst)
      {
         ullPosition = m_ullFirstPos;
         m_bFirst = false;
      }
      else
      {
         if(!(std::istringstream(sHandle.c_str()) >> ullPosition))
         {
            std::ostringstream oOutStrm;
            oOutStrm << "Could not convert the handle \"" << sHandle << "\" to a number.";
            throw ComSerSdk::CUserException(oOutStrm.str(), COMSERSDK_CODELOCATION);
         }
      }

      m_oPrammedPositions.insert(ullPosition);

      if(!(std::ofstream(m_sTrackerFname.c_str(), std::ios::out | std::ios::app) << ullPosition << std::endl))
      {
         std::ostringstream oOutStrm;
         oOutStrm << "Could not update the programmed data tracker file \"" << m_sTrackerFname << "\" with"
                     " programmed data position " << ullPosition;
         throw ComSerSdk::CUserException(oOutStrm.str(), COMSERSDK_CODELOCATION);
      }
   }

   void CreateSampleInputDataFile()
   {
      std::basic_ofstream<unsigned char> oDeviceDataFile(m_sDataFileName.c_str(), std::ios::binary | std::ios::out);
      if(!oDeviceDataFile.is_open())
      {
         std::ostringstream oOutStrm;
         oOutStrm << "Could not create sample pattern data modification file " << m_sDataFileName;
         throw ComSerSdk::CUserException(oOutStrm.str(), COMSERSDK_CODELOCATION);
      }

      std::vector<unsigned char> oBuffer(CStaticVector::k_uSize, 0xFF);
      for(int i = 0; i < 10; ++i)
      {
         for(unsigned j = 1; (j + 1) < oBuffer.size(); j += 2)
         {
            oBuffer[j] = rand();
         }

         oDeviceDataFile.write(&oBuffer.at(0), oBuffer.size());
      }
   }

   HANDLE OpenDeviceDataFile()
   {
      if(_access(m_sDataFileName.c_str(), 0) != 0)
      {
         CreateSampleInputDataFile();
      }

      const HANDLE hReturn = ::CreateFile(m_sDataFileName.c_str(),
                                          FILE_READ_DATA,
                                          FILE_SHARE_READ,
                                          NULL,
                                          OPEN_EXISTING,
                                          FILE_FLAG_RANDOM_ACCESS,
                                          NULL);

      if(hReturn == INVALID_HANDLE_VALUE)
      {
         const DWORD dwLastErr = ::GetLastError();
         std::ostringstream oOutStrm;
         oOutStrm << "Could not open the pattern data modification file " << m_sDataFileName << " for reading. "
                     "::CreateFile() set the last error to " << dwLastErr;
         throw ComSerSdk::CUserException(oOutStrm.str(), COMSERSDK_CODELOCATION);
      }

      return hReturn;
   }

   unsigned long long CurrentFilePosition()
   {
      LARGE_INTEGER oDistanceToMove;
      oDistanceToMove.QuadPart = 0;
      LARGE_INTEGER oFilePos;
      if(0 == ::SetFilePointerEx(m_hDeviceData,
                                 oDistanceToMove,
                                 &oFilePos,
                                 FILE_CURRENT))
      {
         const DWORD dwLastErr = ::GetLastError();
         std::ostringstream oOutStrm;
         oOutStrm << "Could not determine current read position of the pattern data modification file. "
                     "::SetFilePointerEx() set the last error to " << dwLastErr;
         throw ComSerSdk::CUserException(oOutStrm.str(), COMSERSDK_CODELOCATION);
      }
      return oFilePos.QuadPart / CStaticVector::k_uSize;
   }

   void GoToNextAvailable()
   {
      while(m_oPrammedPositions.find(CurrentFilePosition()) != m_oPrammedPositions.end())
      {
         LARGE_INTEGER oFilePos;
         oFilePos.QuadPart = CStaticVector::k_uSize;
         if(0 == ::SetFilePointerEx(m_hDeviceData,
                                    oFilePos,
                                    NULL,
                                    FILE_CURRENT))
         {
            const DWORD dwLastErr = ::GetLastError();
            std::ostringstream oOutStrm;
            oOutStrm << "Could not move the current read position of the pattern data modification file forward by "
                     << oFilePos.QuadPart << ".  ::SetFilePointerEx() set the last error to " << dwLastErr;
            throw ComSerSdk::CUserException(oOutStrm.str(), COMSERSDK_CODELOCATION);
         }
      }
   }

   std::string DetermineDirectory()
   {
      std::vector<TCHAR> oExePath(_MAX_PATH + 1);
      if(0 == ::GetModuleFileName(NULL, &oExePath.at(0), _MAX_PATH))
      {
         const DWORD dwLastErr = ::GetLastError();
         std::ostringstream oOutStrm;
         oOutStrm << "Could not get path to currently running executable.  "
                     "::GetModuleFileName() set last error to " << dwLastErr;
         throw ComSerSdk::CUserException(oOutStrm.str(), COMSERSDK_CODELOCATION);
      }

      std::vector<TCHAR> oDrive(_MAX_DRIVE + 1);
      std::vector<TCHAR> oDir(_MAX_DIR + 1);
      if(0 != _splitpath_s(&oExePath.at(0),
                           &oDrive.at(0), oDrive.size(),
                           &oDir.at(0),   oDir.size(),
                           NULL,          0,
                           NULL,          0))
      {
         std::ostringstream oOutStrm;
         oOutStrm << "Could not split the executable path \"" << &oExePath.at(0)
                  << "\" into seperate path components.";
         throw ComSerSdk::CUserException(oOutStrm.str(), COMSERSDK_CODELOCATION);
      }

      return AppendToPath(&oDrive.at(0), &oDir.at(0));
   }

   std::string AppendToPath(const std::string & sTarget, const std::string & sSource)
   {
      std::vector<TCHAR> oResult(_MAX_PATH + 1);
      oResult.assign(sTarget.begin(), sTarget.end());

      if(FALSE == ::PathAppend(&oResult.at(0), sSource.c_str()))
      {
         std::ostringstream oOutStrm;
         oOutStrm << "Could not append \"" << sSource << "\" to the end of \"" << sTarget << "\"";
         throw ComSerSdk::CUserException(oOutStrm.str(), COMSERSDK_CODELOCATION);
      }

      return &oResult.at(0);
   }

   const std::string m_sExeDirectory;
   const std::string m_sDataFileName;
   const HANDLE m_hDeviceData;
   typedef std::set<unsigned long long> CPositions;
   CPositions m_oPrammedPositions;
   CPositions m_oAttemptedPositions;
   bool m_bFirst;
   unsigned long long m_ullFirstPos;
   const std::string m_sTrackerFname;
};

class CEssApp : public ComSerSdk::CApplicationBase
{
public:
   virtual ComSerSdk::CServerPtr CreateServer()
   {
      return ComSerSdk::CServerPtr(new CStaticVectorServer());
   }
};

CEssApp g_oEssApp;