#ifndef TEST_SUPPORT_FORMAT_STRING_H
#define TEST_SUPPORT_FORMAT_STRING_H

#include <array>
#include <cstdarg>
#include <cstdio>
#include <memory>
#include <string>

namespace format_string_detail
{
inline std::string format_string_imp(const char* msg, ...)
{
  // we might need a second shot at this, so pre-emptivly make a copy
  struct GuardVAList
  {
    va_list& xtarget;
    bool active;
    GuardVAList(va_list& val)
        : xtarget(val)
        , active(true)
    {}

    void clear()
    {
      if (active)
      {
        va_end(xtarget);
      }
      active = false;
    }
    ~GuardVAList()
    {
      if (active)
      {
        va_end(xtarget);
      }
    }
  };
  va_list args;
  va_start(args, msg);
  GuardVAList args_guard(args);

  va_list args_cp;
  va_copy(args_cp, args);
  GuardVAList args_copy_guard(args_cp);

  std::array<char, 256> local_buff;
  std::size_t size = local_buff.size();
  auto ret         = ::vsnprintf(local_buff.data(), size, msg, args_cp);

  args_copy_guard.clear();

  // handle empty expansion
  if (ret == 0)
  {
    return std::string{};
  }
  if (static_cast<std::size_t>(ret) < size)
  {
    return std::string(local_buff.data());
  }

  // we did not provide a long enough buffer on our first attempt.
  // add 1 to size to account for null-byte in size cast to prevent overflow
  size          = static_cast<std::size_t>(ret) + 1;
  auto buff_ptr = std::unique_ptr<char[]>(new char[size]);
  ret           = ::vsnprintf(buff_ptr.get(), size, msg, args);
  return std::string(buff_ptr.get());
}

const char* unwrap(std::string& s)
{
  return s.c_str();
}
template <class Arg>
Arg const& unwrap(Arg& a)
{
  static_assert(!std::is_class<Arg>::value, "cannot pass class here");
  return a;
}

} // namespace format_string_detail

template <class... Args>
std::string format_string(const char* fmt, Args const&... args)
{
  return format_string_detail::format_string_imp(fmt, format_string_detail::unwrap(const_cast<Args&>(args))...);
}

#endif // TEST_SUPPORT_FORMAT_STRING_HPP
