#include "stdafx.h"
#include "string_format.h"

namespace
{
  int VSNPRINTF_S(char* buffer, size_t sizeOfBuffer, size_t count, const char* format, va_list argptr)
  {
    return vsnprintf_s(buffer, sizeOfBuffer, count, format, argptr);
  }

  int VSNPRINTF_S(wchar_t* buffer, size_t sizeOfBuffer, size_t count, const wchar_t* format, va_list argptr)
  {
    return _vsnwprintf_s(buffer, sizeOfBuffer, count, format, argptr);
  }
}

template <typename Type>
bool string_format(std::basic_string<Type, std::char_traits<Type>, std::allocator<Type>>& outString, const Type* format,
  ...)
{
  // to avoid heap allocation, see if string will fit in a static buffer
  static Type buffer[128];
  va_list arg_ptr;
  va_start(arg_ptr, format);
  int numCharsWritten = VSNPRINTF_S(buffer, _countof(buffer), _TRUNCATE, format, arg_ptr);
  va_end(arg_ptr);
  if (numCharsWritten > -1 && numCharsWritten < _countof(buffer))
  {
    outString = buffer;
    return true;
  }

  /* String didn't fit in the static buffer. Allocate progressively bigger buffers until the string
  fits. */
  static const size_t maxBufferSize = 1 << 16;
  size_t size = _countof(buffer) * 2;
  Type* pBuffer = nullptr;
  for (pBuffer = (Type*)malloc(size * sizeof(Type)); size <= maxBufferSize;
    pBuffer = (Type*)realloc(pBuffer, size * sizeof(Type)))
  {
    va_start(arg_ptr, format);
    numCharsWritten = VSNPRINTF_S(pBuffer, size, _TRUNCATE, format, arg_ptr);
    va_end(arg_ptr);
    if (numCharsWritten > -1 && numCharsWritten < static_cast<int>(size))
    {
      outString = pBuffer;
      free(pBuffer);
      pBuffer = nullptr;
      return true;
    }
    if (numCharsWritten == -1)
    {
      size *= 2;
    }
    else if (numCharsWritten < -1)
    {
      // error with format or parameters
      break;
    }
    else // numCharsWritten > -1
    {
      size = numCharsWritten + 1; // +1 for '\0'
    }
  }
  return false;
}

// explicit instantiation
template bool string_format(std::string& outString, const char* format, ...);
template bool string_format(std::wstring& outString, const wchar_t* format, ...);
