//  Unit test for boost::any.
//
//  See http://www.boost.org for most recent version, including documentation.
//
//  Copyright Antony Polukhin, 2013-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 <cstdlib>
#include <string>
#include <utility>

#include <boost/any.hpp>
#include "test.hpp"
#include <boost/move/move.hpp>

#ifdef BOOST_NO_CXX11_RVALUE_REFERENCES

int main() 
{
    return EXIT_SUCCESS;
}

#else 

namespace any_tests
{
    typedef test<const char *, void (*)()> test_case;
    typedef const test_case * test_case_iterator;

    extern const test_case_iterator begin, end;
}

int main()
{
    using namespace any_tests;
    tester<test_case_iterator> test_suite(begin, end);
    return test_suite() ? EXIT_SUCCESS : EXIT_FAILURE;
}

namespace any_tests // test suite
{
    void test_move_construction();
    void test_move_assignment();
    void test_copy_construction();
    void test_copy_assignment();
    
    void test_move_construction_from_value();
    void test_move_assignment_from_value();
    void test_copy_construction_from_value();
    void test_copy_assignment_from_value();
    void test_construction_from_const_any_rv();
    void test_cast_to_rv();
    

    const test_case test_cases[] =
    {
        { "move construction of any",             test_move_construction      },
        { "move assignment of any",               test_move_assignment        },
        { "copy construction of any",             test_copy_construction      },
        { "copy assignment of any",               test_copy_assignment        },

        { "move construction from value",         test_move_construction_from_value },
        { "move assignment from value",           test_move_assignment_from_value  },
        { "copy construction from value",         test_copy_construction_from_value },
        { "copy assignment from value",           test_copy_assignment_from_value },
        { "constructing from const any&&",        test_construction_from_const_any_rv },
        { "casting to rvalue reference",          test_cast_to_rv }
    };

    const test_case_iterator begin = test_cases;
    const test_case_iterator end =
        test_cases + (sizeof test_cases / sizeof *test_cases);

    
    class move_copy_conting_class {
    public:
        static unsigned int moves_count;
        static unsigned int copy_count;

        move_copy_conting_class(){}
        move_copy_conting_class(move_copy_conting_class&& /*param*/) {
            ++ moves_count;
        }

        move_copy_conting_class& operator=(move_copy_conting_class&& /*param*/) {
            ++ moves_count;
            return *this;
        }

        move_copy_conting_class(const move_copy_conting_class&) {
            ++ copy_count;
        }
        move_copy_conting_class& operator=(const move_copy_conting_class& /*param*/) {
            ++ copy_count;
            return *this;
        }
    };

    unsigned int move_copy_conting_class::moves_count = 0;
    unsigned int move_copy_conting_class::copy_count = 0;
}

namespace any_tests // test definitions
{
    using namespace boost;

    void test_move_construction()
    {
        any value0 = move_copy_conting_class();
        move_copy_conting_class::copy_count = 0; 
        move_copy_conting_class::moves_count = 0;
        any value(boost::move(value0)); 

        check(value0.empty(), "moved away value is empty");
        check_false(value.empty(), "empty");
        check_equal(value.type(), typeindex::type_id<move_copy_conting_class>(), "type");
        check_non_null(any_cast<move_copy_conting_class>(&value), "any_cast<move_copy_conting_class>");
        check_equal(
            move_copy_conting_class::copy_count, 0u, 
            "checking copy counts");
        check_equal(
            move_copy_conting_class::moves_count, 0u, 
            "checking move counts");
    }

    void test_move_assignment()
    {
        any value0 = move_copy_conting_class();
        any value = move_copy_conting_class();
        move_copy_conting_class::copy_count = 0; 
        move_copy_conting_class::moves_count = 0;
        value = boost::move(value0); 

        check(value0.empty(), "moved away is empty");
        check_false(value.empty(), "empty");
        check_equal(value.type(), typeindex::type_id<move_copy_conting_class>(), "type");
        check_non_null(any_cast<move_copy_conting_class>(&value), "any_cast<move_copy_conting_class>");
        check_equal(
            move_copy_conting_class::copy_count, 0u, 
            "checking copy counts");
        check_equal(
            move_copy_conting_class::moves_count, 0u, 
            "checking move counts");
    }

    void test_copy_construction()
    {
        any value0 = move_copy_conting_class();
        move_copy_conting_class::copy_count = 0; 
        move_copy_conting_class::moves_count = 0;
        any value(value0); 

        check_false(value0.empty(), "copied value is not empty");
        check_false(value.empty(), "empty");
        check_equal(value.type(), typeindex::type_id<move_copy_conting_class>(), "type");
        check_non_null(any_cast<move_copy_conting_class>(&value), "any_cast<move_copy_conting_class>");
        check_equal(
            move_copy_conting_class::copy_count, 1u, 
            "checking copy counts");
        check_equal(
            move_copy_conting_class::moves_count, 0u, 
            "checking move counts");
    }

    void test_copy_assignment()
    {
        any value0 = move_copy_conting_class();
        any value = move_copy_conting_class();
        move_copy_conting_class::copy_count = 0; 
        move_copy_conting_class::moves_count = 0;
        value = value0; 

        check_false(value0.empty(), "copied value is not empty");
        check_false(value.empty(), "empty");
        check_equal(value.type(), typeindex::type_id<move_copy_conting_class>(), "type");
        check_non_null(any_cast<move_copy_conting_class>(&value), "any_cast<move_copy_conting_class>");
        check_equal(
            move_copy_conting_class::copy_count, 1u, 
            "checking copy counts");
        check_equal(
            move_copy_conting_class::moves_count, 0u, 
            "checking move counts");
    }

     void test_move_construction_from_value()
    {
        move_copy_conting_class value0;
        move_copy_conting_class::copy_count = 0; 
        move_copy_conting_class::moves_count = 0;
#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION
        any value(boost::move(value0)); 
#else
        any value(value0); 
#endif

        check_false(value.empty(), "empty");
        check_equal(value.type(), typeindex::type_id<move_copy_conting_class>(), "type");
        check_non_null(any_cast<move_copy_conting_class>(&value), "any_cast<move_copy_conting_class>");
        
#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION
        check_equal(
            move_copy_conting_class::copy_count, 0u, 
            "checking copy counts");
        check_equal(
            move_copy_conting_class::moves_count, 1u, 
            "checking move counts");
#endif

     }

    void test_move_assignment_from_value()
    {
        move_copy_conting_class value0;
        any value;
        move_copy_conting_class::copy_count = 0; 
        move_copy_conting_class::moves_count = 0;
#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION
        value = boost::move(value0); 
#else
        value = value0;
#endif 

        check_false(value.empty(), "empty");
        check_equal(value.type(), typeindex::type_id<move_copy_conting_class>(), "type");
        check_non_null(any_cast<move_copy_conting_class>(&value), "any_cast<move_copy_conting_class>");

#ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION
        check_equal(
            move_copy_conting_class::copy_count, 0u, 
            "checking copy counts");
        check_equal(
            move_copy_conting_class::moves_count, 1u, 
            "checking move counts");
#endif

    }

    void test_copy_construction_from_value()
    {
        move_copy_conting_class value0;
        move_copy_conting_class::copy_count = 0; 
        move_copy_conting_class::moves_count = 0;
        any value(value0); 

        check_false(value.empty(), "empty");
        check_equal(value.type(), typeindex::type_id<move_copy_conting_class>(), "type");
        check_non_null(any_cast<move_copy_conting_class>(&value), "any_cast<move_copy_conting_class>");

        check_equal(
            move_copy_conting_class::copy_count, 1u, 
            "checking copy counts");
        check_equal(
            move_copy_conting_class::moves_count, 0u, 
            "checking move counts");
     }

    void test_copy_assignment_from_value()
    {
        move_copy_conting_class value0;
        any value;
        move_copy_conting_class::copy_count = 0; 
        move_copy_conting_class::moves_count = 0;
        value = value0;

        check_false(value.empty(), "empty");
        check_equal(value.type(), typeindex::type_id<move_copy_conting_class>(), "type");
        check_non_null(any_cast<move_copy_conting_class>(&value), "any_cast<move_copy_conting_class>");

        check_equal(
            move_copy_conting_class::copy_count, 1u, 
            "checking copy counts");
        check_equal(
            move_copy_conting_class::moves_count, 0u, 
            "checking move counts");
    }

    const any helper_method() {
        return true;
    }

    const bool helper_method1() {
        return true;
    }

    void test_construction_from_const_any_rv()
    {
        any values[] = {helper_method(), helper_method1() };
        (void)values;
    }

    void test_cast_to_rv()
    {
        move_copy_conting_class value0;
        any value;
        value = value0;
        move_copy_conting_class::copy_count = 0; 
        move_copy_conting_class::moves_count = 0;

        move_copy_conting_class value1 = any_cast<move_copy_conting_class&&>(value);

        check_equal(
            move_copy_conting_class::copy_count, 0u, 
            "checking copy counts");
        check_equal(
            move_copy_conting_class::moves_count, 1u, 
            "checking move counts");
        (void)value1;
/* Following code shall fail to compile
        const any cvalue = value0;
        move_copy_conting_class::copy_count = 0; 
        move_copy_conting_class::moves_count = 0;

        move_copy_conting_class value2 = any_cast<move_copy_conting_class&&>(cvalue);

        check_equal(
            move_copy_conting_class::copy_count, 1u, 
            "checking copy counts");
        check_equal(
            move_copy_conting_class::moves_count, 0u, 
            "checking move counts");
        (void)value2;
*/
    }
    
}

#endif

