#ifndef TEST_UNIT_PRETTY_PRINT_TYPE_HPP
#define TEST_UNIT_PRETTY_PRINT_TYPE_HPP

#include <cstddef>
#include <stdexcept>
#include <cstring>
#include <ostream>

namespace stan {
namespace math {
namespace test {

#ifndef _MSC_VER
#if __cplusplus < 201103
#define CONSTEXPR11_TN
#define CONSTEXPR14_TN
#define NOEXCEPT_TN
#elif __cplusplus < 201402
#define CONSTEXPR11_TN constexpr
#define CONSTEXPR14_TN
#define NOEXCEPT_TN noexcept
#else
#define CONSTEXPR11_TN constexpr
#define CONSTEXPR14_TN constexpr
#define NOEXCEPT_TN noexcept
#endif
#else  // _MSC_VER
#if _MSC_VER < 1900
#define CONSTEXPR11_TN
#define CONSTEXPR14_TN
#define NOEXCEPT_TN
#elif _MSC_VER < 2000
#define CONSTEXPR11_TN constexpr
#define CONSTEXPR14_TN
#define NOEXCEPT_TN noexcept
#else
#define CONSTEXPR11_TN constexpr
#define CONSTEXPR14_TN constexpr
#define NOEXCEPT_TN noexcept
#endif
#endif  // _MSC_VER

class static_string {
  const char* const p_;
  const std::size_t sz_;

 public:
  typedef const char* const_iterator;

  template <std::size_t N>
  CONSTEXPR11_TN static_string(const char (&a)[N]) NOEXCEPT_TN : p_(a),
                                                                 sz_(N - 1) {}

  CONSTEXPR11_TN static_string(const char* p, std::size_t N) NOEXCEPT_TN
      : p_(p),
        sz_(N) {}

  CONSTEXPR11_TN const char* data() const NOEXCEPT_TN { return p_; }
  CONSTEXPR11_TN std::size_t size() const NOEXCEPT_TN { return sz_; }

  CONSTEXPR11_TN const_iterator begin() const NOEXCEPT_TN { return p_; }
  CONSTEXPR11_TN const_iterator end() const NOEXCEPT_TN { return p_ + sz_; }

  CONSTEXPR11_TN char operator[](std::size_t n) const {
    return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
  }
};

inline std::ostream& operator<<(std::ostream& os, static_string const& s) {
  return os.write(s.data(), s.size());
}

template <typename... Types>
struct pack;
/**
 * Prints out an input type.
 * @tparam T The type to print out.
 */
template <typename... Arg>
inline CONSTEXPR14_TN static_string type_name() {
  if constexpr (sizeof...(Arg) > 1) {
    return type_name<pack<Arg...>>();
  } else {
#ifdef __clang__
    static_string p = __PRETTY_FUNCTION__;
    return static_string(p.data() + 31, p.size() - 31 - 1);
#elif defined(__GNUC__)
    static_string p = __PRETTY_FUNCTION__;
#if __cplusplus < 201402
    return static_string(p.data() + 36, p.size() - 36 - 1);
#else
    return static_string(p.data() + 83, p.size() - 83 - 1);
#endif
#elif defined(_MSC_VER)
    static_string p = __FUNCSIG__;
    return static_string(p.data() + 38, p.size() - 38 - 7);
#endif
  }
}

}  // namespace test
}  // namespace math
}  // namespace stan

#endif
