////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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/rcs/Exception.h 1.5 2012/06/05 13:19:23 arturoc Exp $
//                                                                                                                    //
///////// 120 columns, Tab = Insert 3 Spaces ///////////////////////////////////////////////////////////////////////////

/**
   This header file defines all exception-related code used by the library user and the library implementation.
*/
#pragma once

#include <stdexcept>
#include <sstream>
#include <string>
#include <memory>
#include <tchar.h>
#include "Atl.h"
#include "Constants.h"

namespace ComSerSdk
{
namespace Detail
{

/**
Non-templated base class of the CScopeExitLogicT template.  Client code needs to declare local variables of the type
returned by MakeScopeExitLogic(), but those types are dependant on the type of arguments passed to MakeScopeExitLogic()
and wind up being different for (just about) every call of the function.  Instead of having client code specify each
different return type, MakeScopeExitLogic() returns a smart pointer to CScopeExitLogicBase.
*/
class CScopeExitLogicBase
{
public:
   virtual ~CScopeExitLogicBase(){}
};

/**
Concrete instantiated in MakeScopeExitLogic().  On destruction, executes a unary free function, a pointer to which was
passed to MakeScopeExitLogic(), passing to it the other argument passed to MakeScopeExitLogic().  This programming idiom
of once-and-only-once specifying code to execute when execution leaves a certain scope, regardless of the way that scope
is exited, including with C++ exceptions, is known as Resource Aquisition Is Initialization (RAII).
*/
template <class THandlerArg, class THandlerFuncPtr>
class CScopeExitLogicT : public CScopeExitLogicBase
{
public:

   /**
   Does nothing non-trivial.  Initializes state with the values of the parameters.
   */
   CScopeExitLogicT(const THandlerArg & oHandlerArg, THandlerFuncPtr pfnHandler):
      m_oHandlerArg(oHandlerArg),
      m_pfnHandler(pfnHandler)
   {}
   
   /**
   Executes the logic on scope exit.
   */
   ~CScopeExitLogicT()
   {
      m_pfnHandler(m_oHandlerArg);
   }

private:
   /**
   Argument to pass to the scope exit handler.
   */
   THandlerArg m_oHandlerArg;

   /**
   Pointer to the scope exit handler.
   */
   THandlerFuncPtr m_pfnHandler;
};

/**
For convenient reference in client code.
*/
typedef std::auto_ptr<CScopeExitLogicBase> CScopeExitLogic;

/**
Facilitates the once-and-only-once (OAOO) specification of code to execute when a certain scope is exited, regardless 
of the method of exiting (normal function return or C++ exception).  Can be used to specify a unary function to execute
and its single argument.  This fits almost all needs for OAOO-specified scope exit logic execution used in the
implementation of this SDK header lib.
*/
template<class THandlerFuncPtr, class THandlerArg>
CScopeExitLogic MakeScopeExitLogic(THandlerFuncPtr pfnFunc, THandlerArg oArg)
{
   return CScopeExitLogic(new CScopeExitLogicT<THandlerArg, THandlerFuncPtr>(oArg, pfnFunc));
}

/**
   Type of object created by the COMSERSDK_CODELOCATION macro.  Encapsulates information about a 
   location in the source code (filename, line, and function name).
*/
struct CCodeLocation
{
   /**
      The name of the file the code is located in.
   */
   const std::string m_sFile;

   /**
      The line number that identifies the location in the source file that the code is located in.
   */
   const unsigned m_uLine;

   /**
      The name of the function the code is located in.
   */
   const std::string m_sFunction;

   /**
      Initialize all data members with the constructor arguments.
   */
   CCodeLocation(const std::string & sFile, unsigned uLine, const std::string & sFunction):
      m_sFile(sFile),
      m_uLine(uLine),
      m_sFunction(sFunction)
   {}

   /**
      Accessor for the file name the code is located in.
   */
   std::string File() const
   {
      return m_sFile;
   }

   /**
      Accessor for the line number that identifies the location in the source file that the code is located in.
   */
   unsigned Line() const
   {
      return m_uLine;
   }

   /**
      Accessor for the name of the function the code is located in.
   */
   std::string Function() const
   {
      return m_sFunction;
   }

   /**
      @return A string containing a user-readable version of the aggregation of all of the data encapsulated by this
      class.
   */
   std::string AsFormattedString() const
   {
      std::ostringstream oStrStream;
      oStrStream << "File: "     << File() << '\n'
                 << "Line: "     << Line() << '\n'
                 << "Function: " << Function();
      return oStrStream.str();
   }
};

/**
   Used by the library user and the library implementation to instantiate an object that provides information 
   regarding the current location of the source code that is throwing an exception.
   See the COM Serialization SDK documentation for more information.
*/
#define COMSERSDK_CODELOCATION ComSerSdk::Detail::CCodeLocation(__FILE__, __LINE__, __FUNCTION__)

/**
   Base class of all exception types defined by the library.
*/
class CException : public std::runtime_error
{
   /**
      Contains information regarding the location in the code that the exception was thrown.
   */
   const Detail::CCodeLocation m_oCodeLocation;

   /**
      Custom exception information provided at the location the exception was thrown.
   */
   const std::string m_sMsg;

public:

   /**
      Initialize all data members, and the base class, with the constructor arguments.
   */
   CException(const std::string & sMessage, const Detail::CCodeLocation & oCodeLocation):
      std::runtime_error(("\"" + sMessage + "\"\n" + oCodeLocation.AsFormattedString()).c_str()),
      m_sMsg(sMessage),
      m_oCodeLocation(oCodeLocation)
   {}

   /**
      Type of "Smart pointer" to this class.
   */
   typedef std::auto_ptr<CException> CPtr;

   /**
      Support cloning so that we can store-off exceptions for processing at locations in the code
      that get executed after the exception is caught.
   */
   virtual CPtr Clone()
   {
      return CPtr(new CException(m_sMsg, m_oCodeLocation));
   }

   /**
      Helper for concretes to invoke in their override of the Clone() member function.
   */
   template<class TConcrete>
   CPtr CloneConcrete()
   {
      return CExceptionPtr(new TConcrete(m_sMsg, m_oCodeLocation));
   }

   /**
      Support for throwing an exception at a location of the code that occurs after the exception was caught and 
      then cloned.
   */
   virtual void Throw()
   {
      throw *this;
   }

   /**
      Accessor for the custom message this exception was configured with.
   */
   std::string Msg() const
   {
      return m_sMsg;
   }

   /**
      Accessor for the object that encapsulates information about the location in the code that the exception was
      thrown.
   */
   Detail::CCodeLocation CodeLocation() const
   {
      return m_oCodeLocation;
   }
};

/**
   For convenience.
*/
typedef CException::CPtr CExceptionPtr;

}//end namespace Detail

/**
   Type of exception thrown by the library-user when they want the error information to include data about the
   location in the code that the exception was thrown.
   Information on this class can be found in the COM Serialization SDK documentation.
*/
class CUserException : public Detail::CException
{
public:
   
   /**
      ctor compatible with the base class.
   */
   CUserException(const std::string & sMessage, const Detail::CCodeLocation & oCodeLocation):
      Detail::CException(sMessage, oCodeLocation)
   {}

private:

   /**
      /override
   */
   virtual CPtr Clone()
   {
      return CloneConcrete<CUserException>();
   }

   /**
      /override
   */
   virtual void Throw()
   {
      throw *this;
   }
};

/**
   Type of exception thrown only by the library.  The user has the option of catching this exception type.
   Information on this class can be found in the COM Serialization SDK documentation.
*/
class CLibraryException : public Detail::CException
{
public:

   /**
      ctor compatible with the base class.
   */
   CLibraryException(const std::string & sMessage, const Detail::CCodeLocation & oCodeLocation):
      Detail::CException(sMessage, oCodeLocation)
   {}

private:

   /**
      /override
   */
   virtual Detail::CExceptionPtr Clone()
   {
      return CloneConcrete<CLibraryException>();
   }

   /**
      /override
   */
   virtual void Throw()
   {
      throw *this;
   }
};

/**
   Supports BPWin 5.0 and above's feature of failing devices instead of job session abort.
*/
class CFailDeviceException : public CUserException
{
public:
   CFailDeviceException(const std::string & sMessage, const Detail::CCodeLocation & oCodeLocation):
      CUserException(sMessage, oCodeLocation)
   {}

private:

   virtual Detail::CExceptionPtr Clone()
   {
      return CloneConcrete<CFailDeviceException>();
   }

   virtual void Throw()
   {
      throw *this;
   }
};

/**
   Supports BPWin 5.26 and above's feature of specifying that the last serial number was exceeded.
*/
class CLastNumberExceeded : public CUserException
{
public:
   CLastNumberExceeded(const std::string & sMessage, const Detail::CCodeLocation & oCodeLocation):
      CUserException(sMessage, oCodeLocation)
   {}

private:

   virtual Detail::CExceptionPtr Clone()
   {
      return CloneConcrete<CLastNumberExceeded>();
   }

   virtual void Throw()
   {
      throw *this;
   }
};

namespace Detail
{

/**
   Helper for retrieving a system-defined, user-readable string version of a specific system error code.
*/
inline std::string GetWin32ErrorMessage(DWORD dwError)
{
   LPTSTR pszMsgBuf = NULL;

   const DWORD dwSize = 
      ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                      FORMAT_MESSAGE_FROM_SYSTEM     |
                      FORMAT_MESSAGE_IGNORE_INSERTS, 
                      NULL,
                      dwError,
                      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                      // The function places the pointer to the buffer at the address specified in this parameter
                      //
                      reinterpret_cast<LPTSTR>(&pszMsgBuf),
                      0,
                      NULL);
   
   std::ostringstream oStrStream;
   oStrStream << std::hex << dwError << "h \"";
   
   if(NULL != pszMsgBuf)
   {
      oStrStream << std::string(pszMsgBuf, pszMsgBuf + (dwSize - 2/*exclude "\r\n"*/)); 
      ::LocalFree(pszMsgBuf);
   }
   else
   {
      oStrStream << "Unrecognized";
   }
   
   oStrStream << "\"";

   return oStrStream.str();
}

/**
   Type of exception thrown only by the library implementation as a result of a COM-related call that returned
   an HRESULT that indicates an error.
*/
class CHresultException : public CLibraryException
{
public:
   
   /**
      Along with standard exception information (custom message and code location), this ctor configures this instance
      with the error-indicating HRESULT value and a string representation of the C++ expression that returned the
      HRESULT.
   */
   CHresultException(const std::string & sMessage,
                     HRESULT hResult,
                     const std::string & sExpr,
                     const Detail::CCodeLocation & oCodeLocation):
      CLibraryException(BuildErrorMessage(hResult, sExpr, sMessage), oCodeLocation)
   {}

   /**
      ctor compatible with the base class.
   */
   CHresultException(const std::string & sMsg, const Detail::CCodeLocation & oCodeLocation):
       CLibraryException(sMsg, oCodeLocation)
   {}

private:

   /**
      Helper for formatting the various bits of data that this exception was configured with into a single,
      user-readable string to provide to the base class as the custom error message.
   */
   std::string BuildErrorMessage(HRESULT hResult, const std::string & sExpr, const std::string & sMessage)
   {
      std::ostringstream oStream;
      oStream << "\"" << sMessage << "\"\n"
              << "Expression: " << sExpr << '\n'
              << "HRESULT: "    << GetWin32ErrorMessage(hResult);
      return oStream.str();
   }

   /**
      /override
   */
   virtual CExceptionPtr Clone()
   {
      return CloneConcrete<CHresultException>();
   }

   /**
      /override
   */
   virtual void Throw()
   {
      throw *this;
   }

};

/**
   Helper for retrieving the error information currently set in the global-to-logical-thread IErrorInfo 
   COM subsystem global instance.
*/
inline std::wstring GetErrorInfo()
{
   IErrorInfoPtr spoErrInfo;
   if(FAILED(::GetErrorInfo(0, &spoErrInfo)))
   {
      return std::wstring();
   }

   if(!static_cast<bool>(spoErrInfo))
   {
      return std::wstring();
   }

   BSTR psErrInfo;
   if(FAILED(spoErrInfo->GetDescription(&psErrInfo)))
   {
      return std::wstring();
   }

   const std::wstring sReturn = psErrInfo;

   ::SysFreeString(psErrInfo);

   return sReturn;
}

/**
   Used throughout the library implementation as the one place defined to handle all exceptions.
   Allows us to catch all ("try{}catch(...){}") exceptions throughout the library implementation and in those catch
   blocks call this function that rethrows the exception so that we can re-catch it based on its specific exception
   type.
*/
inline HRESULT HandleExceptions(const CLSID & oClsid = GUID_NULL, const bool bShowDlg = false)
{
   const std::string sPrefix = "An exception was thrown in the External Serialization Server (ESS).  ";

   HRESULT hReturn = E_FAIL;

   try
   {
      throw;
   }
   catch(CFailDeviceException & oFailDevExc)
   {
      AtlReportError(oClsid, (sPrefix + oFailDevExc.what()).c_str());
      hReturn = g_hDevFailResult;
   }
   catch(CLastNumberExceeded & oLastSnExceededExc)
   {
      AtlReportError(oClsid, (sPrefix + oLastSnExceededExc.what()).c_str());
      hReturn = g_hLastNumberExceededResult;
   }
   catch(CUserException & oUserException)
   {
      hReturn = AtlReportError(oClsid,
                           (sPrefix + "It originated in the client code of the COM Serialization "
                                      "C++ library:\n" +
                            oUserException.what()).c_str());
   }
   catch(CLibraryException & oLibraryException)
   {
      hReturn = AtlReportError(oClsid, (sPrefix +
                                     "It originated in the COM Serialization C++ library:\n" +
                                     oLibraryException.what()).c_str());
   }
   catch(ComSerSdk::Detail::CException & oException)
   {
      hReturn = AtlReportError(oClsid, (sPrefix + oException.what()).c_str());
   }
   catch(std::exception & oStdException)
   {
      hReturn = AtlReportError(oClsid, (sPrefix + "It is a C++ Standard Library exception type:\n"
                                     "\"" + oStdException.what() + "\"").c_str());
   }
   catch(CAtlException & oAtlException)
   {
      hReturn = AtlReportError(oClsid, (sPrefix + "It is an Active Template Library exception type:\n"
                                     "\"" + GetWin32ErrorMessage(oAtlException.m_hr) + "\"").c_str());
                                    
   }
   catch(_com_error & oComErr)
   {
      hReturn = AtlReportError(
         oComErr.GUID(),
         (_bstr_t(sPrefix.c_str()) + "It was thrown by a Visual C++ COM facility used in the ESS:\n" +
          oComErr.Description()).operator BSTR(),
         oComErr.HelpContext(),
         oComErr.HelpFile(),
         GUID_NULL,
         oComErr.Error());
   }
   catch(...)
   {
      hReturn = AtlReportError(oClsid, (sPrefix + "The exception type is unknown.").c_str());
   }
   
   if(bShowDlg)
   {
      const std::wstring sErrInfo = GetErrorInfo();
      if(!sErrInfo.empty())
      {
         ::MessageBoxW(NULL, sErrInfo.c_str(), L"ERROR", MB_OK);
      }
   }

   return hReturn;
}

}// end namespace Detail

}// end namespace ComSerSdk