
//              Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
//   (See accompanying file LICENSE.txt or copy at
//        https://www.boost.org/LICENSE_1_0.txt)

// SPDX-License-Identifier: BSL-1.0
#ifndef CATCH_TOSTRING_HPP_INCLUDED
#define CATCH_TOSTRING_HPP_INCLUDED

#include <ctime>
#include <vector>
#include <cstddef>
#include <type_traits>
#include <string>

#include <catch2/internal/catch_compiler_capabilities.hpp>
#include <catch2/internal/catch_config_wchar.hpp>
#include <catch2/internal/catch_reusable_string_stream.hpp>
#include <catch2/internal/catch_void_type.hpp>
#include <catch2/internal/catch_enum_info.hpp>
#include <catch2/internal/catch_stringref.hpp>

#ifdef CATCH_CONFIG_CPP17_STRING_VIEW
#include <string_view>
#endif

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless
#endif

// We need a dummy global operator<< so we can bring it into Catch namespace later
struct Catch_global_namespace_dummy{};
std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy);

namespace Catch {
    // Bring in global namespace operator<< for ADL lookup in
    // `IsStreamInsertable` below.
    using ::operator<<;

    namespace Detail {

        std::size_t catch_strnlen(const char *str, std::size_t n);

        std::string formatTimeT( std::time_t time );

        constexpr StringRef unprintableString = "{?}"_sr;

        //! Encases `string in quotes, and optionally escapes invisibles
        std::string convertIntoString( StringRef string, bool escapeInvisibles );

        //! Encases `string` in quotes, and escapes invisibles if user requested
        //! it via CLI
        std::string convertIntoString( StringRef string );

        std::string rawMemoryToString( const void *object, std::size_t size );

        template<typename T>
        std::string rawMemoryToString( const T& object ) {
          return rawMemoryToString( &object, sizeof(object) );
        }

        template<typename T,typename = void>
        static constexpr bool IsStreamInsertable_v = false;

        template <typename T>
        static constexpr bool IsStreamInsertable_v<
            T,
            decltype( void( std::declval<std::ostream&>() << std::declval<T>() ) )> =
            true;

        template<typename E>
        std::string convertUnknownEnumToString( E e );

        template<typename T>
        std::enable_if_t<
            !std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value,
        std::string> convertUnstreamable( T const& ) {
            return std::string(Detail::unprintableString);
        }
        template<typename T>
        std::enable_if_t<
            !std::is_enum<T>::value && std::is_base_of<std::exception, T>::value,
         std::string> convertUnstreamable(T const& ex) {
            return ex.what();
        }


        template<typename T>
        std::enable_if_t<
            std::is_enum<T>::value,
        std::string> convertUnstreamable( T const& value ) {
            return convertUnknownEnumToString( value );
        }

#if defined(_MANAGED)
        //! Convert a CLR string to a utf8 std::string
        template<typename T>
        std::string clrReferenceToString( T^ ref ) {
            if (ref == nullptr)
                return std::string("null");
            auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString());
            cli::pin_ptr<System::Byte> p = &bytes[0];
            return std::string(reinterpret_cast<char const *>(p), bytes->Length);
        }
#endif

    } // namespace Detail


    template <typename T, typename = void>
    struct StringMaker {
        template <typename Fake = T>
        static
        std::enable_if_t<::Catch::Detail::IsStreamInsertable_v<Fake>, std::string>
            convert(const Fake& value) {
                ReusableStringStream rss;
                // NB: call using the function-like syntax to avoid ambiguity with
                // user-defined templated operator<< under clang.
                rss.operator<<(value);
                return rss.str();
        }

        template <typename Fake = T>
        static
        std::enable_if_t<!::Catch::Detail::IsStreamInsertable_v<Fake>, std::string>
            convert( const Fake& value ) {
#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER)
            return Detail::convertUnstreamable(value);
#else
            return CATCH_CONFIG_FALLBACK_STRINGIFIER(value);
#endif
        }
    };

    namespace Detail {

        std::string makeExceptionHappenedString();

        // This function dispatches all stringification requests inside of Catch.
        // Should be preferably called fully qualified, like ::Catch::Detail::stringify
        template <typename T>
        std::string stringify( const T& e ) {
            CATCH_TRY {
                return ::Catch::StringMaker<
                    std::remove_cv_t<std::remove_reference_t<T>>>::convert( e );
            }
            CATCH_CATCH_ALL { return makeExceptionHappenedString(); }
        }

        template<typename E>
        std::string convertUnknownEnumToString( E e ) {
            return ::Catch::Detail::stringify(static_cast<std::underlying_type_t<E>>(e));
        }

#if defined(_MANAGED)
        template <typename T>
        std::string stringify( T^ e ) {
            return ::Catch::StringMaker<T^>::convert(e);
        }
#endif

    } // namespace Detail

    // Some predefined specializations

    template<>
    struct StringMaker<std::string> {
        static std::string convert(const std::string& str);
    };

#ifdef CATCH_CONFIG_CPP17_STRING_VIEW
    template<>
    struct StringMaker<std::string_view> {
        static std::string convert(std::string_view str);
    };
#endif

    template<>
    struct StringMaker<char const *> {
        static std::string convert(char const * str);
    };
    template<>
    struct StringMaker<char *> {
        static std::string convert(char * str);
    };

#if defined(CATCH_CONFIG_WCHAR)
    template<>
    struct StringMaker<std::wstring> {
        static std::string convert(const std::wstring& wstr);
    };

# ifdef CATCH_CONFIG_CPP17_STRING_VIEW
    template<>
    struct StringMaker<std::wstring_view> {
        static std::string convert(std::wstring_view str);
    };
# endif

    template<>
    struct StringMaker<wchar_t const *> {
        static std::string convert(wchar_t const * str);
    };
    template<>
    struct StringMaker<wchar_t *> {
        static std::string convert(wchar_t * str);
    };
#endif // CATCH_CONFIG_WCHAR

    template<size_t SZ>
    struct StringMaker<char[SZ]> {
        static std::string convert(char const* str) {
            return Detail::convertIntoString(
                StringRef( str, Detail::catch_strnlen( str, SZ ) ) );
        }
    };
    template<size_t SZ>
    struct StringMaker<signed char[SZ]> {
        static std::string convert(signed char const* str) {
            auto reinterpreted = reinterpret_cast<char const*>(str);
            return Detail::convertIntoString(
                StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ)));
        }
    };
    template<size_t SZ>
    struct StringMaker<unsigned char[SZ]> {
        static std::string convert(unsigned char const* str) {
            auto reinterpreted = reinterpret_cast<char const*>(str);
            return Detail::convertIntoString(
                StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ)));
        }
    };

#if defined(CATCH_CONFIG_CPP17_BYTE)
    template<>
    struct StringMaker<std::byte> {
        static std::string convert(std::byte value);
    };
#endif // defined(CATCH_CONFIG_CPP17_BYTE)
    template<>
    struct StringMaker<int> {
        static std::string convert(int value);
    };
    template<>
    struct StringMaker<long> {
        static std::string convert(long value);
    };
    template<>
    struct StringMaker<long long> {
        static std::string convert(long long value);
    };
    template<>
    struct StringMaker<unsigned int> {
        static std::string convert(unsigned int value);
    };
    template<>
    struct StringMaker<unsigned long> {
        static std::string convert(unsigned long value);
    };
    template<>
    struct StringMaker<unsigned long long> {
        static std::string convert(unsigned long long value);
    };

    template<>
    struct StringMaker<bool> {
        static std::string convert(bool b) {
            using namespace std::string_literals;
            return b ? "true"s : "false"s;
        }
    };

    template<>
    struct StringMaker<char> {
        static std::string convert(char c);
    };
    template<>
    struct StringMaker<signed char> {
        static std::string convert(signed char value);
    };
    template<>
    struct StringMaker<unsigned char> {
        static std::string convert(unsigned char value);
    };

    template<>
    struct StringMaker<std::nullptr_t> {
        static std::string convert(std::nullptr_t) {
            using namespace std::string_literals;
            return "nullptr"s;
        }
    };

    template<>
    struct StringMaker<float> {
        static std::string convert(float value);
        CATCH_EXPORT static int precision;
    };

    template<>
    struct StringMaker<double> {
        static std::string convert(double value);
        CATCH_EXPORT static int precision;
    };

    template <typename T>
    struct StringMaker<T*> {
        template <typename U>
        static std::string convert(U* p) {
            if (p) {
                return ::Catch::Detail::rawMemoryToString(p);
            } else {
                return "nullptr";
            }
        }
    };

    template <typename R, typename C>
    struct StringMaker<R C::*> {
        static std::string convert(R C::* p) {
            if (p) {
                return ::Catch::Detail::rawMemoryToString(p);
            } else {
                return "nullptr";
            }
        }
    };

#if defined(_MANAGED)
    template <typename T>
    struct StringMaker<T^> {
        static std::string convert( T^ ref ) {
            return ::Catch::Detail::clrReferenceToString(ref);
        }
    };
#endif

    namespace Detail {
        template<typename InputIterator, typename Sentinel = InputIterator>
        std::string rangeToString(InputIterator first, Sentinel last) {
            ReusableStringStream rss;
            rss << "{ ";
            if (first != last) {
                rss << ::Catch::Detail::stringify(*first);
                for (++first; first != last; ++first)
                    rss << ", " << ::Catch::Detail::stringify(*first);
            }
            rss << " }";
            return rss.str();
        }
    }

} // namespace Catch

//////////////////////////////////////////////////////
// Separate std-lib types stringification, so it can be selectively enabled
// This means that we do not bring in their headers

#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS)
#  define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER
#  define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER
#  define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER
#  define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER
#endif

// Separate std::pair specialization
#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER)
#include <utility>
namespace Catch {
    template<typename T1, typename T2>
    struct StringMaker<std::pair<T1, T2> > {
        static std::string convert(const std::pair<T1, T2>& pair) {
            ReusableStringStream rss;
            rss << "{ "
                << ::Catch::Detail::stringify(pair.first)
                << ", "
                << ::Catch::Detail::stringify(pair.second)
                << " }";
            return rss.str();
        }
    };
}
#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER

#if defined( CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER ) && \
    defined( CATCH_CONFIG_CPP17_OPTIONAL ) &&              \
    /* P3168 turned optional into a range, making this ambigous with the range support */ \
    !defined( __cpp_lib_optional_range_support )
#include <optional>
namespace Catch {
    template<typename T>
    struct StringMaker<std::optional<T> > {
        static std::string convert(const std::optional<T>& optional) {
            if (optional.has_value()) {
                return ::Catch::Detail::stringify(*optional);
            } else {
                return "{ }";
            }
        }
    };
    template <>
    struct StringMaker<std::nullopt_t> {
        static std::string convert(const std::nullopt_t&) {
            return "{ }";
        }
    };
}
#endif // CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER

// Separate std::tuple specialization
#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER)
#    include <tuple>
#    include <utility>
namespace Catch {
    namespace Detail {
        template <typename Tuple, std::size_t... Is>
        void PrintTuple( const Tuple& tuple,
                         std::ostream& os,
                         std::index_sequence<Is...> ) {
            // 1 + Account for when the tuple is empty
            char a[1 + sizeof...( Is )] = {
                ( ( os << ( Is ? ", " : " " )
                       << ::Catch::Detail::stringify( std::get<Is>( tuple ) ) ),
                  '\0' )... };
            (void)a;
        }

    } // namespace Detail

    template <typename... Types>
    struct StringMaker<std::tuple<Types...>> {
        static std::string convert( const std::tuple<Types...>& tuple ) {
            ReusableStringStream rss;
            rss << '{';
            Detail::PrintTuple(
                tuple,
                rss.get(),
                std::make_index_sequence<sizeof...( Types )>{} );
            rss << " }";
            return rss.str();
        }
    };
} // namespace Catch
#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER

#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT)
#include <variant>
namespace Catch {
    template<>
    struct StringMaker<std::monostate> {
        static std::string convert(const std::monostate&) {
            return "{ }";
        }
    };

    template<typename... Elements>
    struct StringMaker<std::variant<Elements...>> {
        static std::string convert(const std::variant<Elements...>& variant) {
            if (variant.valueless_by_exception()) {
                return "{valueless variant}";
            } else {
                return std::visit(
                    [](const auto& value) {
                        return ::Catch::Detail::stringify(value);
                    },
                    variant
                );
            }
        }
    };
}
#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER

namespace Catch {
    // Import begin/ end from std here
    using std::begin;
    using std::end;

    namespace Detail {
        template <typename T, typename = void>
        struct is_range_impl : std::false_type {};

        template <typename T>
        struct is_range_impl<T, void_t<decltype(begin(std::declval<T>()))>> : std::true_type {};
    } // namespace Detail

    template <typename T>
    struct is_range : Detail::is_range_impl<T> {};

#if defined(_MANAGED) // Managed types are never ranges
    template <typename T>
    struct is_range<T^> {
        static const bool value = false;
    };
#endif

    template<typename Range>
    std::string rangeToString( Range const& range ) {
        return ::Catch::Detail::rangeToString( begin( range ), end( range ) );
    }

    // Handle vector<bool> specially
    template<typename Allocator>
    std::string rangeToString( std::vector<bool, Allocator> const& v ) {
        ReusableStringStream rss;
        rss << "{ ";
        bool first = true;
        for( bool b : v ) {
            if( first )
                first = false;
            else
                rss << ", ";
            rss << ::Catch::Detail::stringify( b );
        }
        rss << " }";
        return rss.str();
    }

    template<typename R>
    struct StringMaker<R, std::enable_if_t<is_range<R>::value && !::Catch::Detail::IsStreamInsertable_v<R>>> {
        static std::string convert( R const& range ) {
            return rangeToString( range );
        }
    };

    template <typename T, size_t SZ>
    struct StringMaker<T[SZ]> {
        static std::string convert(T const(&arr)[SZ]) {
            return rangeToString(arr);
        }
    };


} // namespace Catch

// Separate std::chrono::duration specialization
#include <ctime>
#include <ratio>
#include <chrono>


namespace Catch {

template <class Ratio>
struct ratio_string {
    static std::string symbol() {
        Catch::ReusableStringStream rss;
        rss << '[' << Ratio::num << '/'
            << Ratio::den << ']';
        return rss.str();
    }
};

template <>
struct ratio_string<std::atto> {
    static char symbol() { return 'a'; }
};
template <>
struct ratio_string<std::femto> {
    static char symbol() { return 'f'; }
};
template <>
struct ratio_string<std::pico> {
    static char symbol() { return 'p'; }
};
template <>
struct ratio_string<std::nano> {
    static char symbol() { return 'n'; }
};
template <>
struct ratio_string<std::micro> {
    static char symbol() { return 'u'; }
};
template <>
struct ratio_string<std::milli> {
    static char symbol() { return 'm'; }
};

    ////////////
    // std::chrono::duration specializations
    template<typename Value, typename Ratio>
    struct StringMaker<std::chrono::duration<Value, Ratio>> {
        static std::string convert(std::chrono::duration<Value, Ratio> const& duration) {
            ReusableStringStream rss;
            rss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's';
            return rss.str();
        }
    };
    template<typename Value>
    struct StringMaker<std::chrono::duration<Value, std::ratio<1>>> {
        static std::string convert(std::chrono::duration<Value, std::ratio<1>> const& duration) {
            ReusableStringStream rss;
            rss << duration.count() << " s";
            return rss.str();
        }
    };
    template<typename Value>
    struct StringMaker<std::chrono::duration<Value, std::ratio<60>>> {
        static std::string convert(std::chrono::duration<Value, std::ratio<60>> const& duration) {
            ReusableStringStream rss;
            rss << duration.count() << " m";
            return rss.str();
        }
    };
    template<typename Value>
    struct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> {
        static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const& duration) {
            ReusableStringStream rss;
            rss << duration.count() << " h";
            return rss.str();
        }
    };

    ////////////
    // std::chrono::time_point specialization
    // Generic time_point cannot be specialized, only std::chrono::time_point<system_clock>
    template<typename Clock, typename Duration>
    struct StringMaker<std::chrono::time_point<Clock, Duration>> {
        static std::string convert(std::chrono::time_point<Clock, Duration> const& time_point) {
            return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch";
        }
    };
    // std::chrono::time_point<system_clock> specialization
    template<typename Duration>
    struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> {
        static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) {
            const auto systemish = std::chrono::time_point_cast<
                std::chrono::system_clock::duration>( time_point );
            const auto as_time_t = std::chrono::system_clock::to_time_t( systemish );
            return ::Catch::Detail::formatTimeT( as_time_t );
        }
    };
}

#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... )                       \
    namespace Catch {                                                       \
        template <>                                                         \
        struct StringMaker<enumName> {                                      \
            static std::string convert( enumName value ) {                  \
                CATCH_INTERNAL_START_WARNINGS_SUPPRESSION                   \
                CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS                    \
                static const auto enumInfo = ::Catch::Detail::makeEnumInfo( \
                    #enumName, #__VA_ARGS__, { __VA_ARGS__ } );             \
                CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION                    \
                return static_cast<std::string>(                            \
                    enumInfo.lookup( static_cast<int64_t>( value ) ) );     \
            }                                                               \
        };                                                                  \
    }

#define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ )

#ifdef _MSC_VER
#pragma warning(pop)
#endif

#endif // CATCH_TOSTRING_HPP_INCLUDED
