////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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/VectorServer.cpp 1.2 2009/02/03 16:22:42 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 "vector" server class which provides an API that will allow you to specify the device data
   pattern modifications in the form of “vectors”.  In the SDK documentation, the term “vector” is defined to mean a
   C++ object that encapsulates these pieces of data:
      1.	A data pattern address.
      2.	The data, in the form of an array of 8-bit bytes, where the elements contain the new values for a range of
         contiguous bytes in the device data pattern beginning at the address.
      3.	The number of elements in the array.

   The logic specified in this source code implements an ESS that modifies the data pattern based on the contents
   of a seperate ASCII text file named "DataPatternMods.txt" that resides in a subfolder of the current user's
   %APPDATA% path with the same name as this executable's file name.  For example, if the current user's username was
   "zzzz" and this was the user's %APPDATA% path:
      C:\Documents and Settings\zzzz\Application Data
   Then the logic here will expect that file to reside in this fully-qualified path:
      C:\Documents and Settings\zzzz\Application Data\VectorServer\DataPatternMods.txt
   If that subdirectory doesn't exist, then the logic here is structured to create it.  If the "DataPatternMods.txt"
   file does not exist in that directory, then the logic here is structured to create and use a sample file.  You can
   consult the source code below for the sample's contents.
   The logic here expects that file to be in a specific format, where the first line contains a list of
   white-space-delimited numbers in ASCII-hex (where each number represents a data pattern address) followed by a
   newline, followed by a newline-delimited set of data that represents the new values of the bytes at those addresses,
   where the value of each byte is represented by a number as two ASCII-hex characters, the set of data to program at
   an address is delimited by whitespace, and the number of bytes to modify starting at those addresses is calculated 
   by dividing the number of characters in the set for an address by 2; thus each of those lines represents all of the
   data required to modify the pattern for a single device:

      [ByteAddress1] [ByteAddress2] [etc...][newline]
      [ByteValForAddr1][ByteValFor(Addr1+1)][ByteValFor(Addr1+2)] [ByteValForAddr2][ByteValFor(Addr2+1)][newline]
      [ByteValForAddr1][ByteValFor(Addr1+1)][ByteValFor(Addr1+2)] [ByteValForAddr2][ByteValFor(Addr2+1)][newline]
      [ByteValForAddr1][ByteValFor(Addr1+1)][ByteValFor(Addr1+2)] [ByteValForAddr2][ByteValFor(Addr2+1)][newline]
      [ByteValForAddr1][ByteValFor(Addr1+1)][ByteValFor(Addr1+2)] [ByteValForAddr2][ByteValFor(Addr2+1)][newline]
      [ByteValForAddr1][ByteValFor(Addr1+1)][ByteValFor(Addr1+2)] [ByteValForAddr2][ByteValFor(Addr2+1)][newline]
      [ByteValForAddr1][ByteValFor(Addr1+1)][ByteValFor(Addr1+2)] [ByteValForAddr2][ByteValFor(Addr2+1)][newline]

   This example shows the entire contents of a file that will program 8 bytes starting at data pattern address 11Bh
   and 8 bytes starting at 221h:

      11B 221
      25FF59FF19FF37FF 38FF0cFF50FF0cFF
      44FF49FF1dFF37FF 0aFF3fFF2cFF4aFF
      7bFF40FF14FF50FF 68FF52FF09FF0aFF
      2bFF18FF76FF59FF 25FF59FF19FF37FF
      
   When the operation on a device fails, that will be detected here and recorded.  If by the end of the job session
   a set of data for a device was not used due to such a failure, a new file named "Retry.txt" will be created in the
   same directory as DataPatternMods.txt and will contain data in the same format.  If that file is used as the input
   file for a subsequent job session (by renaming it to DataPatternMods.txt) it can be used to retry the data that was
   unused in the previous job session.  As for data in DataPatternMods.txt that doesn't get attempted due to early
   job session termination, BPWin can be configured to specify a file in the "Data file" field of the serialization
   dialog, and the "Resume from last serial number in data file" box can be checked.  Then on a subsequent job session,
   the data to read from the file will begin where the previous job session ended.

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

   - Error checking and reporting.  Upon error detection, the logic here throws exceptions of class
     ComSerSdk::CUserException.  Those exception class instances are configured here with detailed error information.
     When those exceptions are thrown, BPWin will display the error information those exceptions are configured with in
     a dialog box and abort the job session.

   - The configuration of "vectors" based on the data in the DataPatternMods.txt file.  Below, this can be found in
     the defintion of CVectorServer::SetVectors().

   - Overriding the OnResult() virtual member function of the vector server's base class.  This is how we keep
     track of failures in order to retry them in a subsequent job session.

   - Overriding the OnBeginJob() virtual member function of the vector server's base class.  We use this
     to ensure the user has configured BPWin correctly so that they do not specify so many devices to operate on in 
     the job session that the amount of available data for those devices will be exceeded.

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

      cl /EHsc VectorServer.cpp

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

#include <ComSerSdk/Application.h>
#include <ComSerSdk/VectorServer.h>
#include <io.h>
#include <fstream>
#include <algorithm>
#include <set>
#include <iterator>
#include <shlobj.h>

class CVectorServer : public ComSerSdk::CVectorServerBase
{
public:

   CVectorServer():
      m_sAppDataPath      (ExeAppDataPath()),
      m_sInputDataFileName(SetupInputFile()),
      m_oInputDataFile    (m_sInputDataFileName.c_str()),
      m_oAddresses        (ReadAddresses()),
      m_uNumDevices       (0),
      m_uNumLines         (0)
   {}

#pragma warning(push)   
#pragma warning(disable: 4297)
   /**
      Follows C++ best practice of not allowing exceptions to propagate from destructors.  Error
      handling is done by catching exceptions here and reporting the exception information with a message box.
   */
   ~CVectorServer()
   try
   {
      if(!m_oLinesToRetry.empty())
      {
         CreateRetryFile();
      }
   }
   catch(std::exception & oException)
   {
      ::MessageBox(NULL, oException.what(), "Error", MB_OK);
   }
   catch(...)
   {
      ::MessageBox(NULL,
                   "An unknown exception occurred during destruction of the vector server class",
                   "Error",
                   MB_OK);
   }
#pragma warning(pop)      

private:

   /**
      The library calls this override once before the first device in the job session.
   */
   void OnBeginJob(const std::string & sLastHandle, unsigned uNumDevices)
   {
      m_uNumLines = CountLinesInDataFile();

      CheckLastHandle(sLastHandle);

      m_uNumDevices = uNumDevices;
   }

   /**
      The library calls this override once before each device operation in the job session.
   */
   void SetVectors(const std::string & sCurrentHandle,
                         std::string & sNextHandle,
                   ComSerSdk::CVectorSetter & oVectorSetter)
   {
      const unsigned uCurrentLine = ConvertCurrentHandleToNumber(sCurrentHandle);

      CheckLineAmountWillNotBeExceeded(uCurrentLine);

      SetVectorsForLine(GetCurrentLineFromDataFile(uCurrentLine), oVectorSetter, uCurrentLine);

      std::ostringstream oOutputStream;
      oOutputStream << (uCurrentLine + 1);
      sNextHandle = oOutputStream.str();
   }

   /**
      The library calls this override once after each device operation in the job session.
   */
   void OnResult(const std::string & sHandle, bool bSuccess, unsigned /*uSite*/, unsigned /*uSocket*/)
   {
      const unsigned uLine = ConvertOpResultHandleToNumber(sHandle);
      
      if(!bSuccess)
      {
         m_oLinesToRetry.insert(uLine);
      }
      else
      {
         m_oLinesToRetry.erase(uLine);
      }
   }

   std::string AppDataPath()
   {
      std::vector<TCHAR> oAppDataPath(_MAX_PATH + 1);
      const HRESULT hGetFldrPathResult = ::SHGetFolderPath(NULL,
                                                           CSIDL_APPDATA,
                                                           NULL,
                                                           SHGFP_TYPE_CURRENT,
                                                           &oAppDataPath.at(0));

      if(FAILED(hGetFldrPathResult))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not get path to %APPDATA% folder.  ::SHGetFolderPath() returned HRESULT "
                     << std::hex << hGetFldrPathResult;
         throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
      }

      return &oAppDataPath.at(0);
   }

   std::string ExePath()
   {
      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);
      }

      return &oExePath.at(0);
   }

   std::string AppendTitleFromSourcePath(const std::string & sDestinationPath, const std::string & sSourcePath)
   {
      std::vector<TCHAR> oTitle(_MAX_FNAME + 1);
      _splitpath(sSourcePath.c_str(), NULL, NULL, &oTitle.at(0), NULL);

      std::vector<TCHAR> oResult(_MAX_PATH + 1);
      oResult.assign(sDestinationPath.begin(), sDestinationPath.end());
      if(FALSE == ::PathAppend(&oResult.at(0), &oTitle.at(0)))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not append \"" << &oTitle.at(0) << "\" to \"" << sDestinationPath << "\".";
         throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
      }

      return &oResult.at(0);
   }

   std::string ExeAppDataPath()
   {
      const std::string sExeAppDataPath = AppendTitleFromSourcePath(AppDataPath(), ExePath());
      
      // No need to check for error here.  If the directory already exists, then we have what we want.  If it 
      // doesn't exist and creation failed the check below handles it.
      //
      ::CreateDirectory(sExeAppDataPath.c_str(), NULL);

      if(_access(sExeAppDataPath.c_str(), 0) != 0)
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not create directory \"" << sExeAppDataPath << "\".";
         throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
      }

      return sExeAppDataPath;
   }

   std::string SetupInputFile()
   {
      std::vector<CHAR> oDataFilePath(_MAX_PATH + 1);
      oDataFilePath.assign(m_sAppDataPath.begin(), m_sAppDataPath.end());

      const std::string sPatternModsFname = "DataPatternMods.txt";

      if(FALSE == ::PathAppend(&oDataFilePath.at(0), sPatternModsFname.c_str()))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not append \"" << sPatternModsFname << "\" to \"" << &oDataFilePath.at(0) << "\".";
         throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
      }

      if(_access(&oDataFilePath.at(0), 0) != 0)
      {
         std::ofstream oSampleFile(&oDataFilePath.at(0));
         if(!oSampleFile.is_open())
         {
            std::ostringstream oOutStrStrm;
            oOutStrStrm << "Could not create sample list file \"" << &oDataFilePath.at(0) << "\" for writing.";
            throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
         }

         oSampleFile <<
           "11B 221\n"
           "25FF59FF19FF37FF 38FF0cFF50FF0cFF\n"
           "44FF49FF1dFF37FF 0aFF3fFF2cFF4aFF\n"
           "7bFF40FF14FF50FF 68FF52FF09FF0aFF\n"
           "2bFF18FF76FF59FF 25FF59FF19FF37FF\n"
           "7fFF3aFF7fFF16FF 7fFF3aFF7fFF16FF\n"
           "38FF0cFF50FF0cFF 29FF13FF5fFF6cFF\n"
           "25FF72FF06FF77FF 6fFF0dFF16FF27FF\n"
           "19FF34FF62FF1dFF 05FF5dFF4eFF74FF\n"
           "3bFF39FF4dFF50FF 38FF0cFF50FF0cFF\n"
           "6aFF6dFF51FF4cFF 48FF28FF7cFF55FF\n"
           "30FF48FF5cFF4fFF 7fFF3aFF7fFF16FF\n"
           "73FF47FF5eFF17FF 6fFF49FF13FF66FF\n"
           "59FF4dFF18FF1fFF 7fFF3aFF7fFF16FF\n"
           "0aFF3fFF2cFF4aFF 25FF59FF19FF37FF\n"
           "58FF4fFF4eFF5eFF 68FF52FF09FF0aFF\n"
           "6fFF49FF13FF66FF 3bFF39FF4dFF50FF\n"
           "05FF5dFF4eFF74FF 30FF48FF5cFF4fFF\n"
           "48FF28FF7cFF55FF 25FF59FF19FF37FF\n"
           "6fFF0dFF16FF27FF 05FF5dFF4eFF74FF\n"
           "29FF13FF5fFF6cFF 30FF48FF5cFF4fFF\n"
           "68FF52FF09FF0aFF 38FF0cFF50FF0cFF\n"
           "26FF34FF39FF45FF 19FF34FF62FF1dFF\n"
           "29FF13FF40FF3bFF 25FF72FF06FF77FF\n";
      }

      return &oDataFilePath.at(0);
   }

   typedef std::vector<unsigned long long> CAddresses;

   CAddresses ReadAddresses()
   {
      if(!std::getline(m_oInputDataFile, m_sAddressesLine))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not read addresses line from \"" << m_sInputDataFileName << "\".";
         throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
      }

      std::istringstream oInputStringStrm(m_sAddressesLine.c_str());
      CAddresses oReturn;

      unsigned long long ullAddress = 0;
      while(oInputStringStrm >> std::hex >> ullAddress)
      {
         oReturn.push_back(ullAddress);
      }
      
      return oReturn;
   }

   void SkipLine(std::istream & oInputStrm)
   {
      std::find(std::istream_iterator<char>(oInputStrm >> std::noskipws),
                std::istream_iterator<char>(),
                '\n');
   }

   std::string BuildRetryFileName()
   {
      std::vector<TCHAR> oRetryFileName(_MAX_PATH + 1);
      oRetryFileName.assign(m_sAppDataPath.begin(), m_sAppDataPath.end());

      const std::string sRetryFname = "Retry.txt";

      if(FALSE == ::PathAppend(&oRetryFileName.at(0), sRetryFname.c_str()))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not write file with list of lines to retry because could not append \"" << sRetryFname
                     << "\" to \"" << &oRetryFileName.at(0) << "\".";
         throw std::runtime_error(oOutStrStrm.str().c_str());
      }

      return &oRetryFileName.at(0);
   }

   void OpenRetryFile(std::ofstream & oRetryFile, const std::string & sRetryFname)
   {
      oRetryFile.open(sRetryFname.c_str());
      if(!oRetryFile.is_open())
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not write file with list of lines to retry because could not open \"" <<
                        sRetryFname << "\" for writing.";
         throw std::runtime_error(oOutStrStrm.str().c_str());
      }
   }

   void WriteAddressLineToRetryFile(std::ofstream & oRetryFile, const std::string & sRetryFname)
   {
      if(!(oRetryFile << m_sAddressesLine << '\n'))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not write file with list of lines to retry because could not write addresses \"" <<
                        m_sAddressesLine << "\" to \"" << sRetryFname << "\".";
         throw std::runtime_error(oOutStrStrm.str().c_str());
      }
   }

   void OpenDataFileForReadingRetryLines(std::ifstream & oDataFile)
   {
      oDataFile.open(m_sInputDataFileName.c_str());

      if(!oDataFile.is_open())
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not write file with list of lines to retry because could not open \"" <<
                        m_sInputDataFileName << "\" for reading.";
         throw std::runtime_error(oOutStrStrm.str().c_str());
      }
   }

   std::string ReadCurrentRetryLineFromDataFile(std::ifstream & oDataFile, unsigned uCurrentLine)
   {
      std::string sLine;
      if(!std::getline(oDataFile, sLine))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not write file with list of lines to retry because could not read line number " <<
                        uCurrentLine << " from source file \"" << m_sInputDataFileName << "\".";
         throw std::runtime_error(oOutStrStrm.str().c_str());
      }
      return sLine;
   }

   void WriteCurrentLineToRetryFile(std::ifstream & oDataFile,
                                    std::ofstream & oRetryFile,
                                    const std::string & sRetryFname,
                                    unsigned uCurrentLine)
   {
      if(!(oRetryFile << ReadCurrentRetryLineFromDataFile(oDataFile, uCurrentLine) << '\n'))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not write file with list of lines to retry because could not write line number " <<
                        uCurrentLine << " from source file \"" << m_sInputDataFileName << "\" to " <<
                        sRetryFname << ".";
         throw std::runtime_error(oOutStrStrm.str().c_str());
      }
   }

   void WriteRetryLinesToRetryFile(std::ifstream & oDataFile,
                                   std::ofstream & oRetryFile,
                                   const std::string & sRetryFname)
   {
      unsigned uCurrentLine = 0;
      for(std::set<unsigned>::const_iterator it  = m_oLinesToRetry.begin();
                                             it != m_oLinesToRetry.end();
                                           ++it)
      {
         for(;uCurrentLine != (*it + 1); ++uCurrentLine)
         {
            SkipLine(oDataFile);
         }

         uCurrentLine++;

         WriteCurrentLineToRetryFile(oDataFile, oRetryFile, sRetryFname, uCurrentLine);
      }
   }

   unsigned CountLinesInDataFile()
   {
      std::ifstream oInputDataFile(m_sInputDataFileName.c_str());
      if(!oInputDataFile.is_open())
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not open input data file \"" << m_sInputDataFileName << "\" for reading.";
         throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
      }

      return std::count(std::istream_iterator<char>(oInputDataFile >> std::noskipws),
                        std::istream_iterator<char>(),
                        '\n');
   }

   void CheckLastHandle(const std::string & sLastHandle)
   {
      unsigned uLastLine = 0;
      if(!(std::istringstream(sLastHandle.c_str()) >> uLastLine))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not convert the \"last serial number\" \"" << sLastHandle << "\" to a number.";
         throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
      }

      if(uLastLine > (m_uNumLines - 1))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "The last handle (\"" << sLastHandle << "\") represents a value that excedes the number of"
                        " available lines (" << (m_uNumLines - 1) << ") in the input data file \"" << 
                        m_sInputDataFileName << "\"";
         throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
      }
   }

   unsigned ConvertOpResultHandleToNumber(const std::string & sHandle)
   {
      unsigned uResult = 0;
      if(!(std::istringstream(sHandle.c_str()) >> uResult))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "While processing an operation result, could not convert the handle \"" << sHandle << "\" to "
                        "a number.";
         throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
      }

      return uResult;
   }

   unsigned ConvertCurrentHandleToNumber(const std::string & sCurrentHandle)
   {
      unsigned uResult = 0;
      if(!(std::istringstream(sCurrentHandle.c_str()) >> uResult))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not convert the current handle \"" << sCurrentHandle << "\" to a number.";
         throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
      }

      return uResult;
   }

   void CheckLineAmountWillNotBeExceeded(unsigned uCurrentLine)
   {
      if((uCurrentLine + m_uNumDevices) > (m_uNumLines - 1))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "The number of devices to operate on will result in exceding the number of "
                        "available lines in " << m_sInputDataFileName;
         throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
      }
   }

   std::string GetCurrentLineFromDataFile(unsigned uCurrentLine)
   {
      std::string sLine;
      if(!std::getline(m_oInputDataFile, sLine))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not read line " << uCurrentLine << " from input data file \"" << m_sInputDataFileName 
                     << "\".";
         throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
      }
      return sLine;
   }

   std::string GetVecDataInAsciiHex(std::istringstream & oLineInputStream, unsigned uCurrentLine)
   {
      std::string sVecDataInAsciiHex;
      oLineInputStream >> sVecDataInAsciiHex;

      if(sVecDataInAsciiHex.empty())
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not get ASCII HEX vector data from line " << uCurrentLine << " in input data "
                        "file \"" << m_sInputDataFileName << "\".";
         throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
      }

      return sVecDataInAsciiHex;
   }

   unsigned char ConvertAsciiHexByteToNumber(const std::string & sAsciiHexByte, unsigned uCurrentLine)
   {
      unsigned short nByteVal = 0;
      if(!(std::istringstream(sAsciiHexByte) >> std::hex >> nByteVal))
      {
         std::ostringstream oOutStrStrm;
         oOutStrStrm << "Could not convert ASCII HEX byte \"" << sAsciiHexByte << "\" (from vector "
                        "data from line " << uCurrentLine << " of input data file \"" << m_sInputDataFileName << 
                        "\") to a number.";
         throw ComSerSdk::CUserException(oOutStrStrm.str(), COMSERSDK_CODELOCATION);
      }
      return nByteVal;
   }

   void SetBytesInVector(ComSerSdk::CLockedVector & oLockedVector,
                         const std::string & sVecDataInAsciiHex,
                         unsigned uCurrentLine)
   {
      std::istringstream oVecDataAsAsciiHexIStream(sVecDataInAsciiHex.c_str());
      std::vector<TCHAR> oByteAsAsciiHex(3);

      for(unsigned uIndex = 0;
          oVecDataAsAsciiHexIStream.read(&oByteAsAsciiHex.at(0), oByteAsAsciiHex.size() - 1);
          ++uIndex)
      {
         oLockedVector.SetAtRelativeIndex(uIndex, ConvertAsciiHexByteToNumber(&oByteAsAsciiHex.at(0), uCurrentLine));
      }
   }

   void SetVector(unsigned long long ullAddress,
                  std::istringstream & oLineInputStream,
                  ComSerSdk::CVectorSetter & oVectorSetter,
                  unsigned uCurrentLine)
   {
      const std::string sVecDataInAsciiHex = GetVecDataInAsciiHex(oLineInputStream, uCurrentLine);

      SetBytesInVector(oVectorSetter.Lock(ullAddress, sVecDataInAsciiHex.length() / 2),
                       sVecDataInAsciiHex,
                       uCurrentLine);
   }

   void SetVectorsForLine(const std::string & sLine, ComSerSdk::CVectorSetter & oVectorSetter, unsigned uCurrentLine)
   {
      std::istringstream oLineInputStream(sLine);

      for(CAddresses::const_iterator it  = m_oAddresses.begin();
                                     it != m_oAddresses.end();
                                   ++it)
      {
         SetVector(*it, oLineInputStream, oVectorSetter, uCurrentLine);
      }
   }

   void CreateRetryFile()
   {
      const std::string sRetryFname = BuildRetryFileName();

      std::ofstream oRetryFile;
      OpenRetryFile(oRetryFile, sRetryFname);
      WriteAddressLineToRetryFile(oRetryFile, sRetryFname);

      std::ifstream oDataFile;
      OpenDataFileForReadingRetryLines(oDataFile);
      WriteRetryLinesToRetryFile(oDataFile, oRetryFile, sRetryFname);
   }

   const std::string m_sAppDataPath;
   const std::string m_sInputDataFileName;
   std::fstream m_oInputDataFile;

   std::string m_sAddressesLine;
   const CAddresses m_oAddresses;
   unsigned m_uNumDevices;
   unsigned m_uNumLines;
   std::set<unsigned> m_oLinesToRetry;
};

class CEssApp : public ComSerSdk::CApplicationBase
{
public:
   
   /**
      The library calls this override at the beginning of the job session so that we can supply it with an instance
      of a server class.
   */
   ComSerSdk::CServerPtr CreateServer()
   {
      return ComSerSdk::CServerPtr(new CVectorServer());
   }
};

CEssApp g_oEssApp;