//Copyright (c) 2006-2015 Emil Dotchevski and Reverge Studios, Inc.

//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/exception/get_error_info.hpp>
#include <boost/exception/info_tuple.hpp>
#include <boost/exception_ptr.hpp>
#include <boost/detail/lightweight_test.hpp>
#include <boost/detail/workaround.hpp>

struct throws_on_copy;
struct non_printable { };

struct
user_data
    {
    int & count;

    explicit
    user_data( int & count ):
        count(count)
        {
        ++count;
        }

    user_data( user_data const & x ):
        count(x.count)
        {
        ++count;
        }

    ~user_data()
        {
        --count;
        }
    };

typedef boost::error_info<struct tag_test_1,int> test_1;
typedef boost::error_info<struct tag_test_2,unsigned int> test_2;
typedef boost::error_info<struct tag_test_3,float> test_3;
typedef boost::error_info<struct tag_test_4,throws_on_copy> test_4;
typedef boost::error_info<struct tag_test_5,std::string> test_5;
typedef boost::error_info<struct tag_test_6,non_printable> test_6;
typedef boost::error_info<struct tag_user_data,user_data> test_7;

struct
test_exception:
    boost::exception
    {
    };

struct
throws_on_copy
    {
    throws_on_copy()
        {
        }

    throws_on_copy( throws_on_copy const & )
        {
        throw test_exception();
        }
    };

void
basic_test()
    {
    try
        {
        test_exception x;
        add_info(add_info(add_info(x,test_1(1)),test_2(2u)),test_3(3.14159f));
        throw x;
        }
    catch(
    test_exception & x )
        {
        ++*boost::get_error_info<test_1>(x);
        ++*boost::get_error_info<test_2>(x);
        ++*boost::get_error_info<test_3>(x);
        BOOST_TEST(*boost::get_error_info<test_1>(x)==2);
        BOOST_TEST(*boost::get_error_info<test_2>(x)==3u);
        BOOST_TEST(*boost::get_error_info<test_3>(x)==4.14159f);
        BOOST_TEST(!boost::get_error_info<test_4>(x));
        }
    try
        {
        test_exception x;
        add_info(add_info(add_info(x,test_1(1)),test_2(2u)),test_3(3.14159f));
        throw x;
        }
    catch(
    test_exception const & x )
        {
        BOOST_TEST(*boost::get_error_info<test_1>(x)==1);
        BOOST_TEST(*boost::get_error_info<test_2>(x)==2u);
        BOOST_TEST(*boost::get_error_info<test_3>(x)==3.14159f);
        BOOST_TEST(!boost::get_error_info<test_4>(x));
        }
    }

void
exception_safety_test()
    {
    test_exception x;
    try
        {
        add_info(x,test_4(throws_on_copy()));
        BOOST_TEST(false);
        }
    catch(
    test_exception & )
        {
        BOOST_TEST(!boost::get_error_info<test_4>(x));
        }
    }

void
throw_empty()
    {
    throw test_exception();
    }

void
throw_test_1( char const * value )
    {
    throw add_info(test_exception(),test_5(std::string(value)));
    }

void
throw_test_2()
    {
    throw add_info(test_exception(),test_6(non_printable()));
    }

void
throw_catch_add_file_name( char const * name )
    {
    try
        {
        throw_empty();
        BOOST_TEST(false);
        }
    catch(
    boost::exception & x )
        {
        add_info(x,test_5(std::string(name)));
        throw;
        }     
    }

void
test_empty()
    {
    try
        {
        throw_empty();
        BOOST_TEST(false);
        }
    catch(
    boost::exception & x )
        {
#ifndef BOOST_NO_RTTI
        BOOST_TEST( dynamic_cast<test_exception *>(&x) );
#endif
        BOOST_TEST( !boost::get_error_info<test_1>(x) );
        }
    catch(
    ... )
        {
        BOOST_TEST(false);
        }

    try
        {
        throw_empty();
        BOOST_TEST(false);
        }
    catch(
    test_exception & x )
        {
#ifndef BOOST_NO_RTTI
        BOOST_TEST( dynamic_cast<boost::exception const *>(&x)!=0 );
#endif
        }
    catch(
    ... )
        {
        BOOST_TEST(false);
        }
    }

void
test_basic_throw_catch()
    {
    try
        {
        throw_test_1("test");
        BOOST_ASSERT(false);
        }
    catch(
    boost::exception & x )
        {
        BOOST_TEST(*boost::get_error_info<test_5>(x)==std::string("test"));
        }
    catch(
    ... )
        {
        BOOST_TEST(false);
        }

    try
        {
        throw_test_2();
        BOOST_ASSERT(false);
        }
    catch(
    boost::exception & x )
        {
        BOOST_TEST(boost::get_error_info<test_6>(x));
        }
    catch(
    ... )
        {
        BOOST_TEST(false);
        }
    }

void
test_catch_add_info()
    {
    try
        {
        throw_catch_add_file_name("test");
        BOOST_TEST(false);
        }
    catch(
    boost::exception & x )
        {
        BOOST_TEST(*boost::get_error_info<test_5>(x)==std::string("test"));
        }
    catch(
    ... )
        {
        BOOST_TEST(false);
        }
    }

void
test_add_tuple()
    {
    typedef boost::tuple<> tuple_test_;
    typedef boost::tuple<test_1> tuple_test_1;
    typedef boost::tuple<test_1,test_2> tuple_test_12;
    typedef boost::tuple<test_1,test_2,test_3> tuple_test_123;
    typedef boost::tuple<test_1,test_2,test_3,test_5> tuple_test_1235;
    try
        {
        throw add_info(test_exception(),tuple_test_());
        }
    catch(
    test_exception & )
        {
        }
    catch(
    ... )
        {
        BOOST_TEST(false);
        }
    try
        {
        throw add_info(test_exception(),tuple_test_1(42));
        }
    catch(
    test_exception & x )
        {
        BOOST_TEST( *boost::get_error_info<test_1>(x)==42 );
        }
    catch(
    ... )
        {
        BOOST_TEST(false);
        }
    try
        {
        throw add_info(test_exception(),tuple_test_12(42,42u));
        }
    catch(
    test_exception & x )
        {
        BOOST_TEST( *boost::get_error_info<test_1>(x)==42 );
        BOOST_TEST( *boost::get_error_info<test_2>(x)==42u );
        }
    catch(
    ... )
        {
        BOOST_TEST(false);
        }
    try
        {
        throw add_info(test_exception(),tuple_test_123(42,42u,42.0f));
        }
    catch(
    test_exception & x )
        {
        BOOST_TEST( *boost::get_error_info<test_1>(x)==42 );
        BOOST_TEST( *boost::get_error_info<test_2>(x)==42u );
        BOOST_TEST( *boost::get_error_info<test_3>(x)==42.0f );
        }
    catch(
    ... )
        {
        BOOST_TEST(false);
        }
    try
        {
        throw add_info(test_exception(),tuple_test_1235(42,42u,42.0f,std::string("42")));
        }
    catch(
    test_exception & x )
        {
        BOOST_TEST( *boost::get_error_info<test_1>(x)==42 );
        BOOST_TEST( *boost::get_error_info<test_2>(x)==42u );
        BOOST_TEST( *boost::get_error_info<test_3>(x)==42.0f );
        BOOST_TEST( *boost::get_error_info<test_5>(x)=="42" );
        }
    catch(
    ... )
        {
        BOOST_TEST(false);
        }
    }

void
test_lifetime1()
    {
    int count=0;
    try
        {
        throw add_info(test_exception(),test_7(user_data(count)));
        }
    catch(
    boost::exception & x )
        {
        BOOST_TEST(count==1);
        BOOST_TEST( boost::get_error_info<test_7>(x) );
        }
    catch(
    ... )
        {
        BOOST_TEST(false);
        }
    BOOST_TEST(!count);
    }

void
test_lifetime2()
    {
    int count=0;
        {
        boost::exception_ptr ep;
        test_exception e; add_info(e,test_7(user_data(count)));
        ep=boost::copy_exception(e);
        BOOST_TEST(count>0);
        }
    BOOST_TEST(!count);
    }

bool
is_const( int const * )
    {
    return true;
    }

bool
is_const( int * )
    {
    return false;
    }

void
test_const()
    {
    test_exception e;
    boost::exception const & c(e);
    boost::exception & m(e);
    BOOST_TEST(is_const(boost::get_error_info<test_1>(c)));
    BOOST_TEST(!is_const(boost::get_error_info<test_1>(m)));
    }

int
main()
    {
    basic_test();
    exception_safety_test();
    test_empty();
    test_basic_throw_catch();
    test_catch_add_info();
    test_add_tuple();
    test_lifetime1();
    test_lifetime2();
    test_const();
    return boost::report_errors();
    }
