#ifndef STR_HPP_
#define STR_HPP_


#include <cerrno>
#include <exception>
#include <functional>
#include <initializer_list>
#include <sstream>
#include <stdexcept>
#include <string>
#include <system_error>


#define STR(VALUE) \
    (((std::ostringstream &)(std::ostringstream{} << VALUE)).str())

#define STR_FUNC(VALUE) \
    ([=]() { return STR(VALUE); })

#define STR_JOIN(JOIN, ITERATOR, VALUE, ...) \
    ([&]() \
    { \
        auto const & str_join_  = JOIN; \
        auto         str_oss_   = std::ostringstream{}; \
        auto         str_first_ = true; \
        for (auto const & ITERATOR : __VA_ARGS__) \
        { \
            auto str_value_ = STR(VALUE); \
            if (str_value_.empty()) \
                continue; \
            if (!str_first_) \
                str_oss_ << str_join_; \
            str_first_ = false; \
            str_oss_ << str_value_; \
        } \
        return str_oss_.str(); \
    }())

#define STR_JOIN_INIT(JOIN, ITERATOR, VALUE, TYPE, ...) \
    STR_JOIN(JOIN, ITERATOR, VALUE, std::initializer_list<TYPE>__VA_ARGS__)

#define STR_JOIN_INITSTR(JOIN, ITERATOR, VALUE, ...) \
    STR_JOIN_INIT(JOIN, ITERATOR, VALUE, std::string, __VA_ARGS__)


#define STR_CASE(CASE) \
    case (CASE): return (#CASE);

#define STR_COND(VALUE, CASE) \
    ((VALUE) == (CASE)) ? (#CASE) :

#define STR_INIT(CASE) \
    {(CASE), (#CASE)},

#define STR_ARGS(ARG) \
    ARG, #ARG

#define STR_ARGS_PREFIX(PREFIX, ARG) \
    PREFIX##ARG, #ARG


#define STR_HERE(VALUE) \
    STR(__FILE__ << ":" << __LINE__ << ":" << __func__ << ": " << VALUE)


#ifndef STR_EXCEPTION
    #define STR_EXCEPTION std::runtime_error
#endif

#define STR_THROW(VALUE) \
    (throw STR_EXCEPTION{STR(VALUE)})

#define STR_THROW_ERRNO() \
    STR_THROW(std::generic_category().message(errno) << ".")

#define STR_RETHROW(VALUE) \
    try \
    { \
        throw; \
    } \
    catch (std::exception const & str_exception_) \
    { \
        STR_THROW(VALUE << str_exception_.what()); \
    }

#define STR_NESTED_THROW(VALUE) \
    (std::throw_with_nested(STR_EXCEPTION{STR(VALUE)}))

#define STR_NESTED_THROW_ERRNO() \
    STR_NESTED_THROW(std::generic_category().message(errno) << ".")

#define STR_NESTED_WHAT(JOIN, EXCEPTION) \
    ([&]() \
    { \
        auto const & str_join_   = JOIN; \
        auto         str_oss_    = std::ostringstream{}; \
        auto         str_first_  = true; \
        auto str_what_ = std::function<void(std::exception const &)>{}; \
        str_what_ = [&](std::exception const & str_exception_) \
        { \
            if (!str_first_) \
                str_oss_ << str_join_; \
            str_first_ = false; \
            str_oss_ << str_exception_.what(); \
            try \
            { \
                std::rethrow_if_nested(str_exception_); \
            } \
            catch (std::exception const & str_exception_nested_) \
            { \
                str_what_(str_exception_nested_); \
            } \
        }; \
        str_what_(EXCEPTION); \
        return str_oss_.str(); \
    }())


#endif // STR_HPP_