//  Unit test for boost::lexical_cast.
//
//  See http://www.boost.org for most recent version, including documentation.
//
//  Copyright Antony Polukhin, 2011-2019.
//
//  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).


#include <boost/config.hpp>
#include <boost/test/unit_test.hpp>

#include <boost/lexical_cast.hpp>

#include <iostream>


///////////////////////// char streamable classes ///////////////////////////////////////////

struct streamable_easy { enum ENU {value = 0}; };
std::ostream& operator << (std::ostream& ostr, const streamable_easy&) {
    return ostr << streamable_easy::value;
}
std::istream& operator >> (std::istream& istr, const streamable_easy&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, streamable_easy::value);
    return istr;
}

struct streamable_medium { enum ENU {value = 1}; };
template <class CharT>
typename boost::enable_if<boost::is_same<CharT, char>, std::basic_ostream<CharT>&>::type 
operator << (std::basic_ostream<CharT>& ostr, const streamable_medium&) {
    return ostr << streamable_medium::value;
}
template <class CharT>
typename boost::enable_if<boost::is_same<CharT, char>, std::basic_istream<CharT>&>::type 
operator >> (std::basic_istream<CharT>& istr, const streamable_medium&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, streamable_medium::value);
    return istr;
}

struct streamable_hard { enum ENU {value = 2}; };
template <class CharT, class TraitsT>
typename boost::enable_if<boost::is_same<CharT, char>, std::basic_ostream<CharT, TraitsT>&>::type 
operator << (std::basic_ostream<CharT, TraitsT>& ostr, const streamable_hard&) {
    return ostr << streamable_hard::value;
}
template <class CharT, class TraitsT>
typename boost::enable_if<boost::is_same<CharT, char>, std::basic_istream<CharT, TraitsT>&>::type 
operator >> (std::basic_istream<CharT, TraitsT>& istr, const streamable_hard&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, streamable_hard::value);
    return istr;
}

struct streamable_hard2 { enum ENU {value = 3}; };
template <class TraitsT>
std::basic_ostream<char, TraitsT>& operator << (std::basic_ostream<char, TraitsT>& ostr, const streamable_hard2&) {
    return ostr << streamable_hard2::value;
}
template <class TraitsT>
std::basic_istream<char, TraitsT>& operator >> (std::basic_istream<char, TraitsT>& istr, const streamable_hard2&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, streamable_hard2::value);
    return istr;
}


///////////////////////// wchar_t streamable classes ///////////////////////////////////////////

struct wstreamable_easy { enum ENU {value = 4}; };
std::wostream& operator << (std::wostream& ostr, const wstreamable_easy&) {
    return ostr << wstreamable_easy::value;
}
std::wistream& operator >> (std::wistream& istr, const wstreamable_easy&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, wstreamable_easy::value);
    return istr;
}

struct wstreamable_medium { enum ENU {value = 5}; };
template <class CharT>
typename boost::enable_if<boost::is_same<CharT, wchar_t>, std::basic_ostream<CharT>& >::type 
operator << (std::basic_ostream<CharT>& ostr, const wstreamable_medium&) {
    return ostr << wstreamable_medium::value;
}
template <class CharT>
typename boost::enable_if<boost::is_same<CharT, wchar_t>, std::basic_istream<CharT>& >::type 
operator >> (std::basic_istream<CharT>& istr, const wstreamable_medium&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, wstreamable_medium::value);
    return istr;
}

struct wstreamable_hard { enum ENU {value = 6}; };
template <class CharT, class TraitsT>
typename boost::enable_if<boost::is_same<CharT, wchar_t>, std::basic_ostream<CharT, TraitsT>&>::type 
operator << (std::basic_ostream<CharT, TraitsT>& ostr, const wstreamable_hard&) {
    return ostr << wstreamable_hard::value;
}
template <class CharT, class TraitsT>
typename boost::enable_if<boost::is_same<CharT, wchar_t>, std::basic_istream<CharT, TraitsT>&>::type 
operator >> (std::basic_istream<CharT, TraitsT>& istr, const wstreamable_hard&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, wstreamable_hard::value);
    return istr;
}

struct wstreamable_hard2 { enum ENU {value = 7}; };
template <class TraitsT>
std::basic_ostream<wchar_t, TraitsT>& operator << (std::basic_ostream<wchar_t, TraitsT>& ostr, const wstreamable_hard2&) {
    return ostr << wstreamable_hard2::value;
}
template <class TraitsT>
std::basic_istream<wchar_t, TraitsT>& operator >> (std::basic_istream<wchar_t, TraitsT>& istr, const wstreamable_hard2&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, wstreamable_hard2::value);
    return istr;
}

///////////////////////// char and wchar_t streamable classes ///////////////////////////////////////////


struct bistreamable_easy { enum ENU {value = 8}; };
std::ostream& operator << (std::ostream& ostr, const bistreamable_easy&) {
    return ostr << bistreamable_easy::value;
}
std::istream& operator >> (std::istream& istr, const bistreamable_easy&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, bistreamable_easy::value);
    return istr;
}

std::wostream& operator << (std::wostream& ostr, const bistreamable_easy&) {
    return ostr << bistreamable_easy::value + 100;
}
std::wistream& operator >> (std::wistream& istr, const bistreamable_easy&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, bistreamable_easy::value + 100);
    return istr;
}

struct bistreamable_medium { enum ENU {value = 9}; };
template <class CharT>
std::basic_ostream<CharT>& operator << (std::basic_ostream<CharT>& ostr, const bistreamable_medium&) {
    return ostr << bistreamable_medium::value + (sizeof(CharT) == 1 ? 0 : 100);
}
template <class CharT>
std::basic_istream<CharT>& operator >> (std::basic_istream<CharT>& istr, const bistreamable_medium&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, bistreamable_medium::value + (sizeof(CharT) == 1 ? 0 : 100));
    return istr;
}

struct bistreamable_hard { enum ENU {value = 10}; };
template <class CharT, class TraitsT>
std::basic_ostream<CharT, TraitsT>& operator << (std::basic_ostream<CharT, TraitsT>& ostr, const bistreamable_hard&) {
    return ostr << bistreamable_hard::value + (sizeof(CharT) == 1 ? 0 : 100);
}
template <class CharT, class TraitsT>
std::basic_istream<CharT, TraitsT>& operator >> (std::basic_istream<CharT, TraitsT>& istr, const bistreamable_hard&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, bistreamable_hard::value + (sizeof(CharT) == 1 ? 0 : 100));
    return istr;
}

struct bistreamable_hard2 { enum ENU {value = 11}; };
template <class TraitsT>
std::basic_ostream<char, TraitsT>& operator << (std::basic_ostream<char, TraitsT>& ostr, const bistreamable_hard2&) {
    return ostr << bistreamable_hard2::value;
}
template <class TraitsT>
std::basic_istream<char, TraitsT>& operator >> (std::basic_istream<char, TraitsT>& istr, const bistreamable_hard2&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, bistreamable_hard2::value);
    return istr;
}

template <class TraitsT>
std::basic_ostream<wchar_t, TraitsT>& operator << (std::basic_ostream<wchar_t, TraitsT>& ostr, const bistreamable_hard2&) {
    return ostr << bistreamable_hard2::value + 100;
}
template <class TraitsT>
std::basic_istream<wchar_t, TraitsT>& operator >> (std::basic_istream<wchar_t, TraitsT>& istr, const bistreamable_hard2&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, bistreamable_hard2::value + 100);
    return istr;
}


void test_ostream_character_detection();
void test_istream_character_detection();
void test_mixed_stream_character_detection();

boost::unit_test::test_suite *init_unit_test_suite(int, char *[])
{
    boost::unit_test::test_suite *suite = 
        BOOST_TEST_SUITE("lexical_cast stream character detection");
    suite->add(BOOST_TEST_CASE(&test_ostream_character_detection));
    suite->add(BOOST_TEST_CASE(&test_istream_character_detection));
    suite->add(BOOST_TEST_CASE(&test_mixed_stream_character_detection));
    
    return suite;
}

template <class T>
static void test_ostr_impl() {
    T streamable;
    BOOST_CHECK_EQUAL(T::value,  boost::lexical_cast<int>(streamable));
    BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(T::value),  boost::lexical_cast<std::string>(streamable));
}

template <class T>
static void test_wostr_impl() {
    T streamable;
    BOOST_CHECK_EQUAL(T::value,  boost::lexical_cast<int>(streamable));
    // BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(T::value), boost::lexical_cast<std::string>(streamable)); // Shall not compile???
    BOOST_CHECK(boost::lexical_cast<std::wstring>(T::value) == boost::lexical_cast<std::wstring>(streamable));
}

template <class T>
static void test_bistr_impl() {
    T streamable;

    BOOST_CHECK_EQUAL(T::value,  boost::lexical_cast<int>(streamable));
    BOOST_CHECK_EQUAL(boost::lexical_cast<std::string>(T::value),  boost::lexical_cast<std::string>(streamable));

    BOOST_CHECK(boost::lexical_cast<std::wstring>(T::value + 100) == boost::lexical_cast<std::wstring>(streamable));
}

void test_ostream_character_detection() {
    test_ostr_impl<streamable_easy>();
    test_ostr_impl<streamable_medium>();
    test_ostr_impl<streamable_hard>();
    test_ostr_impl<streamable_hard2>();
    
    test_wostr_impl<wstreamable_easy>();
    test_wostr_impl<wstreamable_medium>();
    test_wostr_impl<wstreamable_hard>();
    test_wostr_impl<wstreamable_hard2>();
    
    test_bistr_impl<bistreamable_easy>();
    test_bistr_impl<bistreamable_medium>();
    test_bistr_impl<bistreamable_hard>();
    test_bistr_impl<bistreamable_hard2>();
}


template <class T>
static void test_istr_impl() {
    boost::lexical_cast<T>(T::value);
    boost::lexical_cast<T>(boost::lexical_cast<std::string>(T::value));
}

template <class T>
static void test_wistr_impl() {
    boost::lexical_cast<T>(T::value);
    //boost::lexical_cast<T>(boost::lexical_cast<std::string>(T::value)); // Shall not compile???
    boost::lexical_cast<T>(boost::lexical_cast<std::wstring>(T::value));
}

template <class T>
static void test_bistr_instr_impl() {
    boost::lexical_cast<T>(T::value);
    boost::lexical_cast<T>(boost::lexical_cast<std::string>(T::value));
    boost::lexical_cast<T>(boost::lexical_cast<std::wstring>(T::value + 100));
}

void test_istream_character_detection() {
    test_istr_impl<streamable_easy>();
    test_istr_impl<streamable_medium>();
    test_istr_impl<streamable_hard>();
    test_istr_impl<streamable_hard2>();
    
    test_wistr_impl<wstreamable_easy>();
    test_wistr_impl<wstreamable_medium>();
    test_wistr_impl<wstreamable_hard>();
    test_wistr_impl<wstreamable_hard2>();
    
    test_bistr_instr_impl<bistreamable_easy>();
    test_bistr_instr_impl<bistreamable_medium>();
    test_bistr_instr_impl<bistreamable_hard>();
    test_bistr_instr_impl<bistreamable_hard2>();
}






struct wistreamble_ostreamable { enum ENU {value = 200}; };
std::ostream& operator << (std::ostream& ostr, const wistreamble_ostreamable&) {
    return ostr << wistreamble_ostreamable::value;
}
std::wistream& operator >> (std::wistream& istr, const wistreamble_ostreamable&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, wistreamble_ostreamable::value);
    return istr;
}

struct istreamble_wostreamable { enum ENU {value = 201}; };
std::wostream& operator << (std::wostream& ostr, const istreamble_wostreamable&) {
    return ostr << istreamble_wostreamable::value;
}
std::istream& operator >> (std::istream& istr, const istreamble_wostreamable&) {
    int i; istr >> i; BOOST_CHECK_EQUAL(i, istreamble_wostreamable::value);
    return istr;
}

void test_mixed_stream_character_detection() {
    //boost::lexical_cast<std::wstring>(std::string("qwe")); // TODO: ALLOW IT AS EXTENSION!
    
    boost::lexical_cast<wistreamble_ostreamable>(wistreamble_ostreamable::value);
    BOOST_CHECK_EQUAL(boost::lexical_cast<int>(wistreamble_ostreamable()), wistreamble_ostreamable::value);
    
    boost::lexical_cast<istreamble_wostreamable>(istreamble_wostreamable::value);
    BOOST_CHECK_EQUAL(boost::lexical_cast<int>(istreamble_wostreamable()), istreamble_wostreamable::value);
}
