#include <tango/internal/parse.h>
#include <tango/server/except.h>

#include <charconv>
#include <sstream>

namespace Tango::detail
{

// We have a struct here so that if parse_as is used with an unsupported type it
// will produce an error.
template <typename T>
struct type_name_impl;

template <typename T>
constexpr const char *type_name = type_name_impl<T>::value;

#define SPECIALIZE_TYPE_NAME(t)                  \
    template <>                                  \
    struct type_name_impl<t>                     \
    {                                            \
        constexpr static const char *value = #t; \
    }

SPECIALIZE_TYPE_NAME(int);
SPECIALIZE_TYPE_NAME(unsigned int);
SPECIALIZE_TYPE_NAME(long);
SPECIALIZE_TYPE_NAME(unsigned long);
SPECIALIZE_TYPE_NAME(double);

template <typename T>
T parse_as(std::string_view str)
{
    T result{};
    const char *first = str.data();
    const char *last = str.data() + str.size();
    auto [ptr, ec] = std::from_chars(first, last, result);

    if(ec != std::errc{} || ptr != last)
    {
        std::stringstream ss;
        ss << "\"" << str << "\" cannot be entirely parsed into " << type_name<T>;
        TANGO_THROW_EXCEPTION(API_InvalidArgs, ss.str().c_str());
    }

    return result;
}

template int parse_as(std::string_view str);
template unsigned int parse_as(std::string_view str);
template long parse_as(std::string_view str);
template unsigned long parse_as(std::string_view str);

// GCC has no support for floats until GCC 11.
// Clang has no support for floats until Clang 20.
#ifdef TANGO_HAS_FLOATS_FROM_CHARS
template double parse_as(std::string_view str);
#else

static inline void strtoany(double &ret, const char *str, char **str_end)
{
    ret = strtod(str, str_end);
}

template <typename T>
static T parse_as_float(std::string_view str)
{
    char *end = nullptr;

    // We have to copy the string_view as it might not be nul-terminated.
    std::string copy{str};

    errno = 0;
    T result;
    strtoany(result, copy.c_str(), &end);

    if(str.size() == 0 || errno == ERANGE || end != copy.data() + copy.size())
    {
        std::stringstream ss;
        ss << "\"" << str << "\" cannot be entirely parsed into " << type_name<T>;
        TANGO_THROW_EXCEPTION(API_InvalidArgs, ss.str().c_str());
    }

    return result;
}

template <>
double parse_as<double>(std::string_view str)
{
    return parse_as_float<double>(str);
}
#endif

} // namespace Tango::detail
