//======================================================================
//-----------------------------------------------------------------------
/**
 * @file        iutest_printers.hpp
 * @brief       iris unit test print 出力ヘルパー ファイル
 *
 * @author      t.shirayanagi
 * @par         copyright
 * Copyright (C) 2011-2016, Takazumi Shirayanagi\n
 * This software is released under the new BSD License,
 * see LICENSE
*/
//-----------------------------------------------------------------------
//======================================================================
#ifndef INCG_IRIS_IUTEST_PRINTERS_HPP_A6A321C9_9279_4336_8167_058C59EC0FD0_
#define INCG_IRIS_IUTEST_PRINTERS_HPP_A6A321C9_9279_4336_8167_058C59EC0FD0_

//======================================================================
// include
#include "iutest_defs.hpp"
#include "internal/iutest_string.hpp"

namespace iutest
{

//======================================================================
// declare
template<typename T>
std::string PrintToString(const T& v);

namespace detail
{

inline void PrintBytesInObjectTo(const unsigned char* buf, size_t size, iu_ostream* os)
{
IUTEST_PRAGMA_CONSTEXPR_CALLED_AT_RUNTIME_WARN_DISABLE_BEGIN()
    const size_t kMaxCount = detail::kValues::MaxPrintContainerCount;
    *os << size << "-Byte object < ";
    for( size_t i=0; i < size; ++i )
    {
        const unsigned char n = buf[i];
        *os << detail::ToHex((n>>4)&0xF) << ToHex(n&0xF) << " ";
        if( i == kMaxCount )
        {
            *os << "... ";
            break;
        }
    }
    *os << ">";
IUTEST_PRAGMA_CONSTEXPR_CALLED_AT_RUNTIME_WARN_DISABLE_END()
}

namespace printer_internal
{

namespace formatter
{

/** @private */
template<bool convertible>
struct Printer
{
    template<typename T>
    static void Print(const T& value, iu_ostream* os)
    {
        const unsigned char* ptr = const_cast<const unsigned char*>(
            reinterpret_cast<const volatile unsigned char*>(&value));
        const size_t size = sizeof(T);
        PrintBytesInObjectTo(ptr, size, os);
    }
};

template<>
struct Printer<true>
{
    template<typename T>
    static void Print(const T& value, iu_ostream* os)
    {
#if IUTEST_HAS_BIGGESTINT_OSTREAM
        const BiggestInt v = value;
#else
        const Int32 v = value;
#endif
        *os << v;
    }
};

}   // end of namespace formatter

/** @private */
class TypeWithoutFormatter
{
public:
    template<typename T>
    static void PrintValue(const T& value, iu_ostream* os)
    {
        formatter::Printer<
            iutest_type_traits::is_convertible<const T&, BiggestInt>::value>::Print(value, os);
    }
};

#if !defined(IUTEST_NO_ARGUMENT_DEPENDENT_LOOKUP)

#if IUTEST_HAS_STRINGSTREAM || IUTEST_HAS_STRSTREAM
template<typename Elem, typename Traits, typename T>
::std::basic_ostream<Elem, Traits>& operator << (::std::basic_ostream<Elem, Traits>& os, const T& value)
{
    TypeWithoutFormatter::PrintValue(value, &os);
    return os;
}
#else
template<typename T>
iu_ostream& operator << (iu_ostream& os, const T& value)
{
    TypeWithoutFormatter::PrintValue(value, &os);
    return os;
}
#endif

#endif

}   // end of namespace printer_internal

namespace printer_internal2
{

// 解決順序
// foo::operator <<
// ::operator <<
// ::iutest::detail::printer_internal::operator <<
template<typename T>
void DefaultPrintNonContainerTo(const T& value, iu_ostream* os)
{
    using namespace ::iutest::detail::printer_internal; // NOLINT
    *os << value;
}

}   // end of namespace printer_internal2

//======================================================================
// declare
template<typename T>
void UniversalPrint(const T& value, iu_ostream* os);

//======================================================================
// function
/**
 * @brief   デフォルト文字列変換関数
*/
template<typename T>
inline void DefaultPrintTo(IsContainerHelper::yes_t
                           , iutest_type_traits::false_type
                           , const T& container, iu_ostream* os)
{
    const size_t kMaxCount = kValues::MaxPrintContainerCount;
    size_t count = 0;
    *os << "{";
    for( typename T::const_iterator it=container.begin(), end=container.end(); it != end; ++it, ++count)
    {
        if( count > 0 )
        {
            *os << ",";
            if( count == kMaxCount )
            {
                *os << " ...";
                break;
            }
        }
        *os << " ";
        UniversalPrint(*it, os);
    }
    if( count > 0 )
    {
        *os << " ";
    }
    *os << "}";
}
template<typename T>
inline void DefaultPrintTo(IsContainerHelper::no_t
                        , iutest_type_traits::false_type
                        , const T& value, iu_ostream* os)
{
#if !defined(IUTEST_NO_ARGUMENT_DEPENDENT_LOOKUP)
    printer_internal2::DefaultPrintNonContainerTo(value, os);
#else
    printer_internal::formatter::Printer<false>::Print(value, os);
#endif
}

#if !defined(IUTEST_NO_SFINAE) && !defined(IUTEST_NO_TEMPLATE_PARTIAL_SPECIALIZATION)

template<typename T>
inline void DefaultPtrPrintTo(T* ptr, iu_ostream* os
    , typename enable_if_t< is_convertible<T*, const void*> >::type*& = enabler::value)
{
    *os << ptr;
}
template<typename T>
inline void DefaultPtrPrintTo(T* ptr, iu_ostream* os
    , typename disable_if_t< is_convertible<T*, const void*> >::type*& = enabler::value)
{
    *os << reinterpret_cast<const void*>(reinterpret_cast<type_least_t<8>::UInt>(ptr));
}

#else

template<typename T>
inline void DefaultPtrPrintTo(T* ptr, iu_ostream* os)
{
    *os << reinterpret_cast<const void*>(reinterpret_cast<type_least_t<8>::UInt>(ptr));
}

#endif

template<typename T>
inline void DefaultPrintTo(IsContainerHelper::no_t
                        , iutest_type_traits::true_type
                        , T* ptr, iu_ostream* os)
{
    if( ptr == NULL )
    {
        *os << kStrings::Null;
    }
    else
    {
        DefaultPtrPrintTo<T>(ptr, os);
    }
}

/**
 * @brief   文字列変換関数
*/
template<typename T>
inline void PrintTo(const T& value, iu_ostream* os) {
    DefaultPrintTo(
#if !defined(IUTEST_NO_EXPLICIT_FUNCTION_TEMPLATE_ARGUMENTS)
        IsContainerHelper::IsContainer<T>(0)
#else
        IsContainerHelper::IsContainer(0, detail::explicit_type<T>())
#endif
        , iutest_type_traits::is_pointer<T>(), value, os);
}
inline void PrintTo(bool b, iu_ostream* os)         { *os << (b ? "true" : "false"); }
inline void PrintTo(const char* c, iu_ostream* os)  { *os << c; }
inline void PrintTo(char* c, iu_ostream* os)        { *os << c; }
inline void PrintTo(const ::std::string& str, iu_ostream* os)   { *os << str.c_str(); }
#if !defined(IUTEST_NO_FUNCTION_TEMPLATE_ORDERING)
template<typename T>
inline void PrintTo(const floating_point<T>& f, iu_ostream* os)
{
    *os << f.raw() << "(0x" << ToHexString(f.bits()) << ")";
}
template<typename T1, typename T2>
inline void PrintTo(const ::std::pair<T1, T2>& value, iu_ostream* os)
{
    *os << "(";
    UniversalPrint(value.first, os);
    *os << ", ";
    UniversalPrint(value.second, os);
    *os << ")";
}
#endif
// char or unsigned char の時に、 0 が NULL 文字にならないように修正
inline void PrintTo(const char value, iu_ostream* os)
{
    if( value == 0 )
    {
        *os << "\\0";
    }
    else if( value < 0x20 )
    {
        *os << static_cast<int>(value);
    }
    else
    {
        *os << "\'" << value << "\'";
    }
}
inline void PrintTo(const wchar_t value, iu_ostream* os)
{
    if( value == 0 )
    {
        *os << "\\0";
    }
    else if( value < 0x20 )
    {
        *os << static_cast<int>(value);
    }
    else
    {
        *os << "\'" << value << "\'";
    }
}
inline void PrintTo(const unsigned char value, iu_ostream* os)
{
    *os << static_cast<unsigned int>(value);
}

#if IUTEST_HAS_NULLPTR
inline void PrintTo(const ::std::nullptr_t&, iu_ostream* os) { *os << "nullptr"; }
#endif

#if IUTEST_HAS_TUPLE

template<typename T, int I, int SIZE>
inline void PrintTupleElemTo(const T& t, iu_ostream* os
    , typename detail::enable_if<I == 0, void>::type*& = detail::enabler::value)
{
    IUTEST_UNUSED_VAR(t);
    IUTEST_UNUSED_VAR(os);
}
template<typename T, int I, int SIZE>
inline void PrintTupleElemTo(const T& t, iu_ostream* os
    , typename detail::enable_if<I == 1, void>::type*& = detail::enabler::value)
{
    PrintTo(tuples::get<SIZE-I>(t), os);
}
template<typename T, int I, int SIZE>
inline void PrintTupleElemTo(const T& t, iu_ostream* os
    , typename detail::enable_if<(I&(~1)) != 0, void>::type*& = detail::enabler::value)
{
    PrintTo(tuples::get<SIZE-I>(t), os);
    *os << ", ";
    PrintTupleElemTo<T, I-1, SIZE>(t, os);
}

template<typename T>
inline void PrintTupleTo(const T& t, iu_ostream* os)
{
    *os << "(";
    PrintTupleElemTo<T, tuples::tuple_size<T>::value, tuples::tuple_size<T>::value>(t, os);
    *os << ")";
}

#if IUTEST_HAS_VARIADIC_TEMPLATES && IUTEST_HAS_TUPLE

template<typename ...Args>
inline void PrintTo(const tuples::tuple<Args...>& t, iu_ostream* os)
{
    PrintTupleTo(t, os);
}

#else

#define IIUT_DECL_TUPLE_PRINTTO(n)                                              \
    template<IUTEST_PP_ENUM_PARAMS(n, typename A)>inline void PrintTo(          \
        const tuples::tuple<IUTEST_PP_ENUM_PARAMS(n, A)>& t, iu_ostream* os) {  \
            PrintTupleTo(t, os); }

inline void PrintTo(const tuples::tuple<>& t, iu_ostream* os) { PrintTupleTo(t, os); }

IIUT_DECL_TUPLE_PRINTTO(1)
IIUT_DECL_TUPLE_PRINTTO(2)
IIUT_DECL_TUPLE_PRINTTO(3)
IIUT_DECL_TUPLE_PRINTTO(4)
IIUT_DECL_TUPLE_PRINTTO(5)
IIUT_DECL_TUPLE_PRINTTO(6)
IIUT_DECL_TUPLE_PRINTTO(7)
IIUT_DECL_TUPLE_PRINTTO(8)
IIUT_DECL_TUPLE_PRINTTO(9)

#undef IIUT_DECL_TUPLE_PRINTTO

#endif

#endif

/** @private */
template<typename T>
inline void IUTEST_ATTRIBUTE_UNUSED_ UniversalTersePrint(const T& value, iu_ostream* os)
{
    UniversalPrint(value, os);
}

inline void IUTEST_ATTRIBUTE_UNUSED_ UniversalTersePrint(const char* str, iu_ostream* os)
{
    if( str == NULL )
    {
        *os << kStrings::Null;
    }
    else
    {
        UniversalPrint(::std::string(str), os);
    }
}
inline void IUTEST_ATTRIBUTE_UNUSED_ UniversalTersePrint(char* str, iu_ostream* os)
{
    UniversalTersePrint(static_cast<const char*>(str), os);
}
inline void IUTEST_ATTRIBUTE_UNUSED_ UniversalTersePrint(const wchar_t* str, iu_ostream* os)
{
    UniversalPrint(detail::ShowWideCString(str), os);
}
inline void IUTEST_ATTRIBUTE_UNUSED_ UniversalTersePrint(wchar_t* str, iu_ostream* os)
{
    UniversalTersePrint(static_cast<const wchar_t*>(str), os);
}
#if IUTEST_HAS_CHAR16_T
inline void IUTEST_ATTRIBUTE_UNUSED_ UniversalTersePrint(const char16_t* str, iu_ostream* os)
{
    UniversalPrint(detail::ShowWideCString(str), os);
}
inline void IUTEST_ATTRIBUTE_UNUSED_ UniversalTersePrint(char16_t* str, iu_ostream* os)
{
    UniversalTersePrint(static_cast<const char16_t*>(str), os);
}
#endif
#if IUTEST_HAS_CHAR32_T && (IUTEST_HAS_HDR_UCHAR || IUTEST_HAS_CXX_HDR_CODECVT)
inline void IUTEST_ATTRIBUTE_UNUSED_ UniversalTersePrint(const char32_t* str, iu_ostream* os)
{
    UniversalPrint(detail::ShowWideCString(str), os);
}
inline void IUTEST_ATTRIBUTE_UNUSED_ UniversalTersePrint(char32_t* str, iu_ostream* os)
{
    UniversalTersePrint(static_cast<const char32_t*>(str), os);
}
#endif

/**
 * @brief   配列の出力
*/
template<typename T>
inline void PrintRawArrayTo(const T* a, size_t cnt, iu_ostream* os)
{
    UniversalPrint<T>(a[0], os);
    for( size_t i=1; i < cnt; ++i )
    {
        *os << ", ";
        UniversalPrint<T>(a[i], os);
    }
}

/**
 * @brief   配列の出力
*/
template<typename T>
inline void IUTEST_ATTRIBUTE_UNUSED_ UniversalPrintArray(const T* begin, size_t N, iu_ostream* os)
{
    if( N == 0 )
    {
        *os << "{}";
    }
    else
    {
        *os << "{";
        const size_t kThreshold = kValues::PrintArrayThreshold;
        const size_t kChunksize = kValues::PrintArrayChunksize;
        if( N <= kThreshold )
        {
            PrintRawArrayTo(begin, N, os);
        }
        else
        {
            PrintRawArrayTo(begin, kChunksize, os);
            *os << ", ..., ";
            PrintRawArrayTo(begin + N - kChunksize, kChunksize, os);
        }
        *os << "}";
    }
}
/**
 * @brief   配列の出力
*/
inline void IUTEST_ATTRIBUTE_UNUSED_ UniversalPrintArray(const char* begin, size_t N, iu_ostream* os)
{
    IUTEST_UNUSED_VAR(N);
    UniversalTersePrint(begin, os);
}
inline void IUTEST_ATTRIBUTE_UNUSED_ UniversalPrintArray(const wchar_t* begin, size_t N, iu_ostream* os)
{
    IUTEST_UNUSED_VAR(N);
    UniversalTersePrint(begin, os);
}

/** @private */
template<typename T>
inline void IUTEST_ATTRIBUTE_UNUSED_ UniversalPrintTo(const T& value, iu_ostream* os)
{
    PrintTo(value, os);
}

//======================================================================
// class
/** @private */
template<typename T>
class iuUniversalPrinter
{
public:
    static void Print(const T& value, iu_ostream* os)
    {
        UniversalPrintTo(value, os);
    }
};

#if !defined(IUTEST_NO_FUNCTION_TEMPLATE_ORDERING)

/** @private */
template<typename T, size_t SIZE>
class iuUniversalPrinter<T[SIZE]>
{
public:
    static void Print(const T(&a)[SIZE], iu_ostream* os)
    {
        UniversalPrintArray(a, SIZE, os);
    }
};

#endif

//======================================================================
// function
/** @private */
template<typename T>
inline void UniversalPrint(const T& value, iu_ostream* os)
{
#if !defined(IUTEST_NO_FUNCTION_TEMPLATE_ORDERING)
    iuUniversalPrinter<T>::Print(value, os);
#else
    UniversalPrintTo(value, os);
#endif
}

}   // end of namespace detail

//======================================================================
// function

/**
 * @brief   文字列化
*/
template<typename T>
inline ::std::string PrintToString(const T& v)
{
    iu_global_format_stringstream strm;
    detail::UniversalTersePrint(v, &strm);
    return strm.str();
}

#if IUTEST_HAS_VARIADIC_TEMPLATES
/**
 * @brief   文字列化
*/
template<typename T>
inline ::std::string PrintToStrings(const char* separate, const T& v)
{
    IUTEST_UNUSED_VAR(separate);
    return PrintToString(v);
}
/**
 * @brief   文字列化
*/
template<typename T, typename ...Args>
inline ::std::string PrintToStrings(const char* separate, const T& v, Args... args)
{
    ::std::string str = PrintToString(v);
    str += separate;
    str += PrintToStrings(separate, args...);
    return str;
}
#endif

}   // end of namespace iutest

#endif // INCG_IRIS_IUTEST_PRINTERS_HPP_A6A321C9_9279_4336_8167_058C59EC0FD0_
