////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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/Vector.h 1.2 2009/06/05 11:44:26 arturoc Exp $
//                                                                                                                    //
///////// 120 columns, Tab = Insert 3 Spaces ///////////////////////////////////////////////////////////////////////////

/**
   This header file defines the CVectorBase class.
*/

#pragma once

#include <sstream>
#include "ScriptingRuntime.h"
#include "Exception.h"

namespace ComSerSdk
{
namespace Detail
{

/**
   Specifies the common functionality of all vector types.  Handles managing memory to hold vector data, as
   well as committing the user-modified vector data to the dictionary object upon destruction.
*/
class CVectorBase
{
public:

   /**
      Configures this instance with the arguments, preparing vector data for library-user modification.

      @param ullAddress The address in the data pattern to modify.  Here we convert it to a string to represent the
                        address in the dictionary object since those are the most compatible way of representing
                        64-bit numbers (VT_I8 and VT_UI8 variant types are not supported in Windows 2000).

      @param uSize The number of bytes to modify beginning at the address.

      @param spoDictionary Reference of a smart pointer to the dictionary object to commit the vector data to on
                           destruction.  In this ctor, we either create a COM SAFEARRAY that will hold the user's data
                           to place into the dictionary when this class' destructor executes, or we retrieve the 
                           current one in the dictionary if it exists, and update it with the modified version on
                           destruction.

      @param spoDestructorException The client code of this constructor should have a std::auto_ptr declared 
                                    and provide it as an argument for this parameter.  The auto_ptr will receive a
                                    pointer to a cloned exception that was caught during the execution of this class'
                                    destructor, if one was thrown during that time.  This way, we are able to not allow
                                    exceptions to propagate from the destructor (which we know to be more likely to 
                                    be thrown than usual since we invoke code in the destructor that we know throws
                                    exceptions) but we can, instead of squelching them, continue to detect and act on
                                    them, such as by throwing them in the client code of this class after we know
                                    the vector object has completed being destroyed.
                                    
   */
   CVectorBase(unsigned long long ullAddress,
               unsigned uSize,
               Scripting::IDictionaryPtr & spoDictionary,
               std::auto_ptr<CException> & spoDestructorException):
      m_ullAddress            (ullAddress),
      m_uSize                 (uSize),
      m_spoDictionary         (spoDictionary),
      m_oKey(AddressAsKey     (ullAddress)),
      m_bExistsInDictionary   (Exists(spoDictionary, m_oKey, COMSERSDK_CODELOCATION)),
      m_spoDestructorException(spoDestructorException)
   {
      if(m_bExistsInDictionary)
      {
         AttachArrayToExistingItem();
      }
      else
      {
         CreateNewArray();
      }
   }

   /**
      Provides a pointer to the first byte of the data array.
      Consult the COM Serialization SDK documentation for details.
   */
   unsigned char * GetRawPtr()
   {
      return reinterpret_cast<unsigned char *>(m_oByteArray.m_psa->pvData);
   }

   /**
      Sets a byte in the data array to the value specified in ucValue, at the index specified in uIndex.
      Consult the COM Serialization SDK documentation for details.
   */
   void SetAtRelativeIndex(unsigned uIndex, unsigned char ucValue)
   {
      const HRESULT hResult = m_oByteArray.SetAt(uIndex, ucValue);
      if(FAILED(hResult))
      {
         std::ostringstream oStrm;
         oStrm << "CComSafeArray::SetAt(" << uIndex << "," << static_cast<unsigned>(ucValue) << ")";
         throw CHresultException("Could not set byte at relative index.",
                                 hResult,
                                 oStrm.str(),
                                 COMSERSDK_CODELOCATION);
      }
   }

   /**
      Sets a byte in the data array to the value specified in ucValue, at the data pattern address specified in
      ullAddr.
      Consult the COM Serialization SDK documentation for details.
   */
   void SetAtAbsoluteAddr(unsigned long long ullAddr, unsigned char ucValue)
   {
      const long long llIndex = ullAddr - m_ullAddress;

      if(llIndex >= Size() || llIndex < 0)
      {
         ThrowAbsAddrError(ullAddr);
      }

      SetAtRelativeIndex(static_cast<unsigned>(llIndex), ucValue);
   }

   #pragma warning(push)
   #pragma warning(disable: 4297)// ComSerSdk::Detail::CVectorBase::~CVectorBase' : function assumed not to throw an exception but does

   /**
      Commits the vector data to the dictionary, cloning and storing off any exceptions into the auto_ptr
      this instance was configured with on construction.
   */
   ~CVectorBase()
   try
   {
      _variant_t oArrayAsVariant;
      oArrayAsVariant.vt     = VT_ARRAY | VT_UI1;
      oArrayAsVariant.parray = m_oByteArray.m_psa;

   //////////////////////////////////////////////////////////////////////////////////////////
   // Negative test
      //unsigned short nSrc = 0;
      //PutItem(m_spoDictionary, nSrc, oArrayAsVariant, COMSERSDK_CODELOCATION);
   // End Negative test
   //////////////////////////////////////////////////////////////////////////////////////////
   
      PutItem(m_spoDictionary, m_oKey, oArrayAsVariant, COMSERSDK_CODELOCATION, false/*do not throw on error*/);

      if(m_bExistsInDictionary)
      {
         m_oByteArray.Detach();
      }
   }
   catch(ComSerSdk::Detail::CException & oException)
   {
      m_spoDestructorException = oException.Clone();
   }
   catch(...)
   {
      m_spoDestructorException.reset(new CLibraryException("An unknown exception was thrown and caught at the "
                                                           "location specified.",
                                                            COMSERSDK_CODELOCATION));
   }
   
   #pragma warning(pop)

   /**
      The data pattern address this vector was configured with.
      Consult the COM Serialization SDK documentation for details.
   */
   unsigned long long Address() const
   {
      return m_ullAddress;
   }
   
   /**
      The number of bytes this vector was configured to modify in the data pattern.
      Consult the COM Serialization SDK documentation for details.
   */
   unsigned Size() const
   {
      return m_uSize;
   }

private:

   /**
      Retrieves the existing array in the dictionary and attaches our array member to that instance.
   */
   void AttachArrayToExistingItem()
   {
      _variant_t oItem;
      const HRESULT hGetItemResult = m_spoDictionary->get_Item(&m_oKey, &oItem);
      if(FAILED(hGetItemResult))
      {
         std::ostringstream oStrm;
         oStrm << "IDictionary.get_Item(Key=" << m_oKey << ")";
         throw CHresultException("Could not get item from dictionary.",
                                 hGetItemResult,
                                 oStrm.str(),
                                 COMSERSDK_CODELOCATION);
      }

      const HRESULT hAttachResult = m_oByteArray.Attach(oItem.parray);
      if(FAILED(hAttachResult))
      {
         throw CHresultException("CComSafeArray could not attach to array retrieved from dictionary.",
                                 hAttachResult,
                                 "CComSafeArray::Attach()",
                                 COMSERSDK_CODELOCATION);
      }

      const HRESULT hResizeResult = m_oByteArray.Resize(m_uSize);
      if(FAILED(hResizeResult))
      {
         throw CHresultException("CComSafeArray could not resize array retrieved from dictionary.",
                                 hResizeResult,
                                 "CComSafeArray::Resize()",
                                 COMSERSDK_CODELOCATION);
      }
   }

   /**
      Since the array this vector is configured to work with is not in the dictionary, this function will
      configure our array data member to create a new array to add to the dictionary on destruction.
   */
   void CreateNewArray()
   {
      const HRESULT hResult = m_oByteArray.Create(m_uSize);
      if(FAILED(hResult))
      {
         std::ostringstream oStrm;
         oStrm << "CComSafeArray::Create(Size=" << m_uSize << ")";
         throw CHresultException("CComSafeArray could not create SAFEARRAY.",
                                 hResult,
                                 oStrm.str(),
                                 COMSERSDK_CODELOCATION);
      }
   }

   /**
      Helper for converting the address to a string in a variant.
   */
   _variant_t AddressAsKey(unsigned long long ullAddress) const
   {
      std::ostringstream oStrm;
      oStrm << ullAddress;
      return oStrm.str().c_str();
   }

   /**
      Helper for throwing an exception in the case where a user tried to set the value of a byte in the data array
      using an absolute data pattern address that this vector was not configured to work with.
   */
   void ThrowAbsAddrError(unsigned long long ullAddr) const
   {
      std::ostringstream oStrm;
      oStrm << "Could not set byte at absolute address " << std::hex << ullAddr
            << "h.  The vector is configured to set absolute byte addresses " << m_ullAddress
            << "h through " << m_ullAddress + m_uSize << "h";
      throw CLibraryException(oStrm.str(), COMSERSDK_CODELOCATION);
   }

   /**
      The dictionary object supplied on construction and will be updated with the vector data this vector was used
      to set.
   */
   Scripting::IDictionaryPtr & m_spoDictionary;
   
   /**
      The key that represents the address to modify in the data pattern and will be used in the dictionary object.
      Configured on construction, however it cannot be const since it is used as a parameter to methods of the
      dictionary interface that require it to be non-const.
   */
   _variant_t m_oKey;

   /**
      The COM SAFEARRAY that we defer vector memory management to and that we will add to the dictionary object
      upon destruction.
   */
   CComSafeArray<BYTE> m_oByteArray;

   /**
      Whether the address this vector was configured with was found to already represent a key in the dictionary
      upon construction.
   */
   const bool m_bExistsInDictionary;
   
   /**
      The beginning address in the data pattern to modify.
   */
   const unsigned long long m_ullAddress;

   /**
      The number of bytes to modify in the data pattern beginning at the address.
   */
   const unsigned m_uSize;

   /**
      See the description of the spoDestructorException parameter of the CVectorBase constructor, above.
   */
   std::auto_ptr<CException> & m_spoDestructorException;
};
}
}