#pragma once

#pragma warning(push, 3)
#include <Windows.h>
#pragma warning(pop)

#define PREFIX_TYPE(prefix) ((prefix) & ~TYPE_MASK)
#define PREFIX_SIZE(prefix) ((prefix) & TYPE_MASK)

enum {
  TYPE_MASK = 0x00FFFFFF,
};

template <typename T>
class MAPISerializer
{
public:
  MAPISerializer(void) {}
  virtual ~MAPISerializer(void) {}
  
  DWORD Prefix( DWORD dwLength, T type)
  {
    DWORD prefix = dwLength;
    prefix &= TYPE_MASK;    // clear type bits
    prefix |= (DWORD)type; 

    return prefix;
  }
  
  DWORD ReadPrefix(LPBYTE lpData, LPDWORD lpdwPrefix)
  {
     ASSERT(lpdwPrefix);
     CopyMemory(lpdwPrefix, lpData, sizeof(DWORD));

     return sizeof(DWORD);
  }
  
  DWORD SerializeString( LPBYTE lpDest, LPSTR lpString, T type)
  {
    ASSERT(lpDest);
    DWORD cbStringLength = 0;

    if (lpString == NULL)
      return 0;

    cbStringLength = strlen(lpString);
    if (cbStringLength == 0)
      return 0;

    return SerializeObject(lpDest, (LPBYTE)lpString, cbStringLength, type);
  }
  
  DWORD UnserializeString( LPBYTE lpSrc, DWORD cbSrcSize, LPSTR* lpDest )
  {
    ASSERT( lpSrc );
    ASSERT( cbSrcSize > 0 );
    
    // account for NULL terminator
    (*lpDest) = (LPSTR) LocalAlloc(LPTR, cbSrcSize + 1);
    if (*lpDest == NULL)
      return NULL;
    
    CopyMemory(lpDest, lpSrc, cbSrcSize);
    
    return cbSrcSize;
  }
  
  DWORD SerializeWString( LPBYTE lpDest, LPTSTR lpString, T type )
  {
    ASSERT(lpDest);
    
    DWORD cbStringLength = 0;
    
    // if string is invalid, ignore and return 0 as length
    if (lpString == NULL)
      return 0;
    
    // if string length == 0, ignore and return 0 as length
    cbStringLength = wcslen(lpString) * sizeof(WCHAR);
    if (cbStringLength == 0)
      return 0;

    LPBYTE ptr = lpDest;

    // copy prefix and string *** WITHOUT NULL TERMINATOR ***
    DWORD prefix = Prefix(cbStringLength, type);
    CopyMemory(ptr, &prefix, sizeof(prefix));
    ptr += sizeof(prefix);
    CopyMemory(ptr, lpString, cbStringLength);
    ptr += cbStringLength;
    
    return ptr - lpDest;
  }

  DWORD UnserializeWString( LPBYTE lpSrc, DWORD cbSrcSize, LPWSTR* lpDest )
  {
    ASSERT( lpSrc );
    ASSERT( cbSrcSize > 0 && ( (cbSrcSize % sizeof(TCHAR)) == 0 ) );  // check for correct size of Unicode strings
    
    // account for NULL terminator
    (*lpDest) = (LPWSTR) LocalAlloc(LPTR, cbSrcSize + (1 * sizeof(WCHAR)));
    if (*lpDest == NULL)
      return NULL;
    
    CopyMemory(*lpDest, lpSrc, cbSrcSize);
    
    return cbSrcSize;
  }
  
  DWORD SerializeObject( LPBYTE lpDest, LPBYTE lpObject, DWORD cbObjectSize, T type )
  {
    ASSERT( lpDest );

    if (cbObjectSize == 0)
      return 0;

    DWORD prefix = Prefix(cbObjectSize, type);
    CopyMemory(lpDest, &prefix, sizeof(prefix));
    CopyMemory(lpDest + sizeof(prefix), lpObject, cbObjectSize);
    
    return sizeof(prefix) + cbObjectSize;
  }
  

  DWORD UnserializeObject( LPBYTE lpSrc, DWORD cbSrcSize, LPBYTE* lpDest)
  {
    ASSERT( lpSrc );
    ASSERT( lpDest );
    ASSERT( cbSrcSize > 0 );
    
    (*lpDest) = (LPBYTE) LocalAlloc(LPTR, cbSrcSize);
    CopyMemory(*lpDest, lpSrc, cbSrcSize);
    
    return cbSrcSize;
  }


  inline DWORD SerializedWStringLength( LPTSTR lpString )
  {
    // if string is null
    if (lpString == NULL)
      return 0;
    
    // account for string length field
    DWORD cbStringLength = 0;
    cbStringLength += sizeof(DWORD);
    cbStringLength += wcslen(lpString) * sizeof(WCHAR);

    return cbStringLength;
  }
  
  inline DWORD SerializedStringLength ( LPSTR lpString )
  {
    if (lpString == NULL)
      return 0;
    else if (strlen(lpString) == 0)
      return 0;

    DWORD cbStringLength = 0;
    cbStringLength += sizeof(DWORD);
    cbStringLength += strlen(lpString);

    return cbStringLength;
  }
  
  inline DWORD SerializedObjectLength( DWORD dwObjectLength )
  {
    if (dwObjectLength == 0) return 0;
    return dwObjectLength + sizeof(DWORD);
  }
};
