//  (C) Copyright David Abrahams 2001.
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

//  See http://www.boost.org for most recent version including documentation.

//  Revision History
//  1  Apr 2001 Fixes for ICL; use BOOST_STATIC_CONSTANT
//  11 Feb 2001 Fixes for Borland (David Abrahams)
//  23 Jan 2001 Added test for wchar_t (David Abrahams)
//  23 Jan 2001 Now statically selecting a test for signed numbers to avoid
//              warnings with fancy compilers. Added commentary and
//              additional dumping of traits data for tested types (David
//              Abrahams).
//  21 Jan 2001 Initial version (David Abrahams)

#include <boost/detail/numeric_traits.hpp>
#include <cassert>
#include <boost/type_traits/conditional.hpp>
#include <boost/type_traits/is_signed.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/static_assert.hpp>
#include <boost/cstdint.hpp>
#include <climits>
#include <typeinfo>
#include <iostream>
#include <sstream>
#include <string>
#ifndef BOOST_NO_LIMITS
#include <limits>
#endif

// =================================================================================
// template class complement_traits<Number> --
//
//    statically computes the max and min for 1s and 2s-complement binary
//    numbers. This helps on platforms without <limits> support. It also shows
//    an example of a recursive template that works with MSVC!
//

template <unsigned size> struct complement; // forward

// The template complement, below, does all the real work, using "poor man's
// partial specialization". We need complement_traits_aux<> so that MSVC doesn't
// complain about undefined min/max as we're trying to recursively define them.
template <class Number, unsigned size>
struct complement_traits_aux
{
    BOOST_STATIC_CONSTANT(Number, max = complement<size>::template traits<Number>::max);
    BOOST_STATIC_CONSTANT(Number, min = complement<size>::template traits<Number>::min);
};

template <unsigned size>
struct complement
{
    template <class Number>
    struct traits
    {
     private:
        // indirection through complement_traits_aux necessary to keep MSVC happy
        typedef complement_traits_aux<Number, size - 1> prev;
     public:
#if defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 0 && __GNUC_PATCHLEVEL__ == 2
        // GCC 4.0.2 ICEs on these C-style casts
        BOOST_STATIC_CONSTANT(Number, max =
                            Number((prev::max) << CHAR_BIT)
                            + Number(UCHAR_MAX));
        BOOST_STATIC_CONSTANT(Number, min = Number((prev::min) << CHAR_BIT));
#else
        // Avoid left shifting negative integers, use multiplication instead
        BOOST_STATIC_CONSTANT(Number, shift = 1u << CHAR_BIT);
        BOOST_STATIC_CONSTANT(Number, max =
                            Number(Number(prev::max) * shift)
                            + Number(UCHAR_MAX));
        BOOST_STATIC_CONSTANT(Number, min = Number(Number(prev::min) * shift));
#endif
    };
};

// Template class complement_base<> -- defines values for min and max for
// complement<1>, at the deepest level of recursion. Uses "poor man's partial
// specialization" again.
template <bool is_signed> struct complement_base;

template <> struct complement_base<false>
{
    template <class Number>
    struct values
    {
        BOOST_STATIC_CONSTANT(Number, min = 0);
        BOOST_STATIC_CONSTANT(Number, max = UCHAR_MAX);
    };
};

template <> struct complement_base<true>
{
    template <class Number>
    struct values
    {
        BOOST_STATIC_CONSTANT(Number, min = SCHAR_MIN);
        BOOST_STATIC_CONSTANT(Number, max = SCHAR_MAX);
    };
};

// Base specialization of complement, puts an end to the recursion.
template <>
struct complement<1>
{
    template <class Number>
    struct traits
    {
        BOOST_STATIC_CONSTANT(bool, is_signed = boost::is_signed<Number>::value);
        BOOST_STATIC_CONSTANT(Number, min =
                            complement_base<is_signed>::template values<Number>::min);
        BOOST_STATIC_CONSTANT(Number, max =
                            complement_base<is_signed>::template values<Number>::max);
    };
};

// Now here's the "pretty" template you're intended to actually use.
//   complement_traits<Number>::min, complement_traits<Number>::max are the
//   minimum and maximum values of Number if Number is a built-in integer type.
template <class Number>
struct complement_traits
{
    BOOST_STATIC_CONSTANT(Number, max = (complement_traits_aux<Number, sizeof(Number)>::max));
    BOOST_STATIC_CONSTANT(Number, min = (complement_traits_aux<Number, sizeof(Number)>::min));
};

// =================================================================================

// Support for streaming various numeric types in exactly the format I want. I
// needed this in addition to all the assertions so that I could see exactly
// what was going on.
//
// Numbers go through a 2-stage conversion process (by default, though, no real
// conversion).
//
template <class T> struct stream_as {
    typedef T t1;
    typedef T t2;
};

// char types first get converted to unsigned char, then to unsigned.
template <> struct stream_as<char> {
    typedef unsigned char t1;
    typedef unsigned t2;
};
template <> struct stream_as<unsigned char> {
    typedef unsigned char t1; typedef unsigned t2;
};
template <> struct stream_as<signed char>  {
    typedef unsigned char t1; typedef unsigned t2;
};

// C++20 ostream has deleted operator<< for wchar_t
template <> struct stream_as<wchar_t> {
    typedef unsigned int t1;
    typedef unsigned int t2;
};

#if defined(BOOST_MSVC_STD_ITERATOR) // No intmax streaming built-in

// With this library implementation, __int64 and __uint64 get streamed as strings
template <> struct stream_as<boost::uintmax_t> {
    typedef std::string t1;
    typedef std::string t2;
};

template <> struct stream_as<boost::intmax_t>  {
    typedef std::string t1;
    typedef std::string t2;
};
#endif

// Standard promotion process for streaming
template <class T> struct promote
{
    static typename stream_as<T>::t1 from(T x) {
        typedef typename stream_as<T>::t1 t1;
        return t1(x);
    }
};

#if defined(BOOST_MSVC_STD_ITERATOR) // No intmax streaming built-in

// On this platform, stream them as long/unsigned long if they fit.
// Otherwise, write a string.
template <> struct promote<boost::uintmax_t> {
    std::string static from(const boost::uintmax_t x) {
        if (x > ULONG_MAX)
            return std::string("large unsigned value");
        else {
            std::ostringstream strm;
            strm << (unsigned long)x;
            return strm.str();
        }
    }
};
template <> struct promote<boost::intmax_t> {
    std::string static from(const boost::intmax_t x) {
        if (x > boost::intmax_t(ULONG_MAX))
            return std::string("large positive signed value");
        else if (x >= 0) {
            std::ostringstream strm;
            strm << (unsigned long)x;
            return strm.str();
        }

        if (x < boost::intmax_t(LONG_MIN))
            return std::string("large negative signed value");
        else {
            std::ostringstream strm;
            strm << (long)x;
            return strm.str();
        }
    }
};
#endif

// This is the function which converts types to the form I want to stream them in.
template <class T>
typename stream_as<T>::t2 stream_number(T x)
{
    return promote<T>::from(x);
}
// =================================================================================

//
// Tests for built-in signed and unsigned types
//

// Tag types for selecting tests
struct unsigned_tag {};
struct signed_tag {};

// Tests for unsigned numbers. The extra default Number parameter works around
// an MSVC bug.
template <class Number>
void test_aux(unsigned_tag, Number*)
{
    typedef typename boost::detail::numeric_traits<Number>::difference_type difference_type;
    BOOST_STATIC_ASSERT(!boost::is_signed<Number>::value);
    BOOST_STATIC_ASSERT(
        (sizeof(Number) < sizeof(boost::intmax_t))
        | (boost::is_same<difference_type, boost::intmax_t>::value));

#if defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 0 && __GNUC_PATCHLEVEL__ == 2
    // GCC 4.0.2 ICEs on this C-style cases
    BOOST_STATIC_ASSERT((complement_traits<Number>::max) > Number(0));
    BOOST_STATIC_ASSERT((complement_traits<Number>::min) == Number(0));
#else
    // Force casting to Number here to work around the fact that it's an enum on MSVC
    BOOST_STATIC_ASSERT(Number(complement_traits<Number>::max) > Number(0));
    BOOST_STATIC_ASSERT(Number(complement_traits<Number>::min) == Number(0));
#endif

    const Number max = complement_traits<Number>::max;
    const Number min = complement_traits<Number>::min;

    const Number test_max = (sizeof(Number) < sizeof(boost::intmax_t))
        ? max
        : max / 2 - 1;

    std::cout << std::hex << "(unsigned) min = " << stream_number(min) << ", max = "
              << stream_number(max) << "..." << std::flush;
    std::cout << "difference_type = " << typeid(difference_type).name() << "..."
              << std::flush;

    difference_type d1 = boost::detail::numeric_distance(Number(0), test_max);
    difference_type d2 = boost::detail::numeric_distance(test_max, Number(0));

    std::cout << "0->" << stream_number(test_max) << "==" << std::dec << stream_number(d1) << "; "
              << std::hex << stream_number(test_max) << "->0==" << std::dec << stream_number(d2) << "..." << std::flush;

    assert(d1 == difference_type(test_max));
    assert(d2 == -difference_type(test_max));
}

// Tests for signed numbers. The extra default Number parameter works around an
// MSVC bug.
struct out_of_range_tag {};
struct in_range_tag {};

// This test morsel gets executed for numbers whose difference will always be
// representable in intmax_t
template <class Number>
void signed_test(in_range_tag, Number*)
{
    BOOST_STATIC_ASSERT(boost::is_signed<Number>::value);
    typedef typename boost::detail::numeric_traits<Number>::difference_type difference_type;
    const Number max = complement_traits<Number>::max;
    const Number min = complement_traits<Number>::min;

    difference_type d1 = boost::detail::numeric_distance(min, max);
    difference_type d2 = boost::detail::numeric_distance(max, min);

    std::cout << stream_number(min) << "->" << stream_number(max) << "==";
    std::cout << std::dec << stream_number(d1) << "; ";
    std::cout << std::hex << stream_number(max) << "->" << stream_number(min)
              << "==" << std::dec << stream_number(d2) << "..." << std::flush;
    assert(d1 == difference_type(max) - difference_type(min));
    assert(d2 == difference_type(min) - difference_type(max));
}

// This test morsel gets executed for numbers whose difference may exceed the
// capacity of intmax_t.
template <class Number>
void signed_test(out_of_range_tag, Number*)
{
    BOOST_STATIC_ASSERT(boost::is_signed<Number>::value);
    typedef typename boost::detail::numeric_traits<Number>::difference_type difference_type;
    const Number max = complement_traits<Number>::max;
    const Number min = complement_traits<Number>::min;

    difference_type min_distance = complement_traits<difference_type>::min;
    difference_type max_distance = complement_traits<difference_type>::max;

    const Number n1 = Number(min + max_distance);
    const Number n2 = Number(max + min_distance);
    difference_type d1 = boost::detail::numeric_distance(min, n1);
    difference_type d2 = boost::detail::numeric_distance(max, n2);

    std::cout << stream_number(min) << "->" << stream_number(n1) << "==";
    std::cout << std::dec << stream_number(d1) << "; ";
    std::cout << std::hex << stream_number(max) << "->" << stream_number(n2)
              << "==" << std::dec << stream_number(d2) << "..." << std::flush;
    assert(d1 == max_distance);
    assert(d2 == min_distance);
}

template <class Number>
void test_aux(signed_tag, Number*)
{
    typedef typename boost::detail::numeric_traits<Number>::difference_type difference_type;
    BOOST_STATIC_ASSERT(boost::is_signed<Number>::value);
    BOOST_STATIC_ASSERT(
        (sizeof(Number) < sizeof(boost::intmax_t))
        | (boost::is_same<difference_type, Number>::value));

#if defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 0 && __GNUC_PATCHLEVEL__ == 2
    // GCC 4.0.2 ICEs on this cast
    BOOST_STATIC_ASSERT((complement_traits<Number>::max) > Number(0));
    BOOST_STATIC_ASSERT((complement_traits<Number>::min) < Number(0));
#else
    // Force casting to Number here to work around the fact that it's an enum on MSVC
    BOOST_STATIC_ASSERT(Number(complement_traits<Number>::max) > Number(0));
    BOOST_STATIC_ASSERT(Number(complement_traits<Number>::min) < Number(0));
#endif
    const Number max = complement_traits<Number>::max;
    const Number min = complement_traits<Number>::min;

    std::cout << std::hex << "min = " << stream_number(min) << ", max = "
              << stream_number(max) << "..." << std::flush;
    std::cout << "difference_type = " << typeid(difference_type).name() << "..."
              << std::flush;

    typedef typename boost::conditional<
        (sizeof(Number) < sizeof(boost::intmax_t)),
        in_range_tag,
        out_of_range_tag
    >::type range_tag;
    signed_test<Number>(range_tag(), 0);
}


// Test for all numbers. The extra default Number parameter works around an MSVC
// bug.
template <class Number>
void test(Number* = 0)
{
    std::cout << "testing " << typeid(Number).name() << ":\n"
#ifndef BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS
              << "is_signed: " << (std::numeric_limits<Number>::is_signed ? "true\n" : "false\n")
              << "is_bounded: " << (std::numeric_limits<Number>::is_bounded ? "true\n" : "false\n")
              << "digits: " << std::numeric_limits<Number>::digits << "\n"
#endif
              << "..." << std::flush;

    // factoring out difference_type for the assert below confused Borland :(
    typedef boost::is_signed<
#if !defined(BOOST_MSVC) || BOOST_MSVC > 1300
        typename
#endif
        boost::detail::numeric_traits<Number>::difference_type
        > is_signed;
    BOOST_STATIC_ASSERT(is_signed::value);

    typedef typename boost::conditional<
        boost::is_signed<Number>::value,
        signed_tag,
        unsigned_tag
    >::type signedness;

    test_aux<Number>(signedness(), 0);
    std::cout << "passed" << std::endl;
}

int main()
{
    test<char>();
    test<unsigned char>();
    test<signed char>();
    test<wchar_t>();
    test<short>();
    test<unsigned short>();
    test<int>();
    test<unsigned int>();
    test<long>();
    test<unsigned long>();
#if defined(BOOST_HAS_LONG_LONG) && !defined(BOOST_NO_INTEGRAL_INT64_T)
    test< ::boost::long_long_type>();
    test< ::boost::ulong_long_type>();
#elif defined(BOOST_MSVC)
    // The problem of not having compile-time static class constants other than
    // enums prevents this from working, since values get truncated.
    // test<boost::uintmax_t>();
    // test<boost::intmax_t>();
#endif
    return 0;
}
