//-----------------------------------------------------------------------------
// boost-libs variant/test/variant_get_test.cpp source file
// See http://www.boost.org for updates, documentation, and revision history.
//-----------------------------------------------------------------------------
//
// Copyright (c) 2016-2020 Antony Polukhin
//
// 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)


// This test suite was created to cover issues reported in:
//      https://svn.boost.org/trac/boost/ticket/5871
//      https://svn.boost.org/trac/boost/ticket/11602

#include "boost/variant/variant.hpp"
#include "boost/variant/recursive_variant.hpp"
#include "boost/core/lightweight_test.hpp"

#include <string>
#include <list>

struct A{};
struct B{};
struct C{};
struct D{};


bool foo(const boost::variant<A, B>& ) {
    return false;
}

bool foo(const boost::variant<C, D>& ) {
    return true;
}

void test_overload_selection_variant_constructor() {
    D d;
    BOOST_TEST(foo(d));

    boost::variant<B, A> v;
    BOOST_TEST(!foo(v));
}

// Pre msvc-14.0 could not dustinguish between multiple assignment operators:
//      warning C4522: 'assignment_tester' : multiple assignment operators specified
//      error C2666: variant::operator =' : 3 overloads have similar conversions
// Old versions of GCC have same issue:
//      error: variant::operator=(const T&) cannot be overloaded
#if (defined(__GNUC__) && (__GNUC__ < 4)) || (defined(_MSC_VER) && _MSC_VER < 1900)

void test_overload_selection_variant_assignment() {
    BOOST_TEST(true);
}

#else

struct assignment_tester: boost::variant<C, D>, boost::variant<B, A> {
    using boost::variant<B, A>::operator=;
    using boost::variant<C, D>::operator=;
};

void test_overload_selection_variant_assignment() {
    A a;
    assignment_tester tester;
    tester = a;
    const int which0 = static_cast< boost::variant<B, A>& >(tester).which();
    BOOST_TEST(which0 == 1);

    boost::variant<A, B> b;
    b = B();
    tester = b;
    const int which1 = static_cast< boost::variant<B, A>& >(tester).which();
    BOOST_TEST(which1 == 0);
}

#endif

typedef boost::variant<int> my_variant;

struct convertible {
    operator my_variant() const {
        return my_variant();
    }
};

void test_implicit_conversion_operator() {
    // https://svn.boost.org/trac/boost/ticket/8555
    my_variant y = convertible();
    BOOST_TEST(y.which() == 0);
}

struct X: boost::variant< int > {};
class V1: public boost::variant<float,double> {};

struct AB: boost::variant<A, B> {};

void test_derived_from_variant_construction() {
    // https://svn.boost.org/trac/boost/ticket/7120
    X x;
    boost::variant<X> y(x);
    BOOST_TEST(y.which() == 0);

    // https://svn.boost.org/trac/boost/ticket/10278
    boost::variant<V1, std::string> v2 = V1();
    BOOST_TEST(v2.which() == 0);

    // https://svn.boost.org/trac/boost/ticket/12155
    AB ab;
    boost::variant<AB, C> ab_c(ab);
    BOOST_TEST(ab_c.which() == 0);

    boost::variant<A, B> a_b(ab);
    BOOST_TEST(a_b.which() == 0);

    boost::variant<B, C, A> b_c_a1(static_cast<boost::variant<A, B>& >(ab));
    BOOST_TEST(b_c_a1.which() == 2);


//  Following conversion seems harmful as it may lead to slicing:
//  boost::variant<B, C, A> b_c_a(ab);
//  BOOST_TEST(b_c_a.which() == 2);
}

void test_derived_from_variant_assignment() {
    // https://svn.boost.org/trac/boost/ticket/7120
    X x;
    boost::variant<X> y;
    y = x;
    BOOST_TEST(y.which() == 0);

    // https://svn.boost.org/trac/boost/ticket/10278
    boost::variant<V1, std::string> v2;
    v2 = V1();
    BOOST_TEST(v2.which() == 0);

    // https://svn.boost.org/trac/boost/ticket/12155
    AB ab;
    boost::variant<AB, C> ab_c;
    ab_c = ab;
    BOOST_TEST(ab_c.which() == 0);

    boost::variant<A, B> a_b;
    a_b = ab;
    BOOST_TEST(a_b.which() == 0);

    boost::variant<B, C, A> b_c_a1;
    b_c_a1 = static_cast<boost::variant<A, B>& >(ab);
    BOOST_TEST(b_c_a1.which() == 2);


//  Following conversion seems harmful as it may lead to slicing:
//  boost::variant<B, C, A> b_c_a;
//  b_c_a = ab;
//  BOOST_TEST(b_c_a.which() == 2);
}


// http://thread.gmane.org/gmane.comp.lib.boost.devel/267757
struct info {
    struct nil_ {};

    typedef
        boost::variant<
            nil_
          , std::string
          , boost::recursive_wrapper<info>
          , boost::recursive_wrapper<std::pair<info, info> >
          , boost::recursive_wrapper<std::list<info> >
        >
    value_type;
    value_type v;

    inline void test_on_incomplete_types() {
        info i0;
        i0.v = "Hello";
        BOOST_TEST(i0.v.which() == 1);

        info::value_type v0 = "Hello";
        BOOST_TEST(v0.which() == 1);

        info::value_type v1("Hello");
        BOOST_TEST(v1.which() == 1);

        info::value_type v2 = i0;
        BOOST_TEST(v2.which() == 2);

        info::value_type v3(i0);
        BOOST_TEST(v3.which() == 2);

        v0 = v3;
        BOOST_TEST(v0.which() == 2);

        v3 = v1;
        BOOST_TEST(v3.which() == 1);

        v3 = nil_();
        BOOST_TEST(v3.which() == 0);
    }
};



int main()
{
    test_overload_selection_variant_constructor();
    test_overload_selection_variant_assignment();
    test_implicit_conversion_operator();
    test_derived_from_variant_construction();
    test_derived_from_variant_assignment();
    info().test_on_incomplete_types();

    return boost::report_errors();
}