// Copyright (c) 2018-2020 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/leaf/detail/config.hpp>
#include <boost/leaf/detail/optional.hpp>
#include "lightweight_test.hpp"

namespace leaf = boost::leaf;

int object_count=0;
int value_count=0;

class my_info
{
    my_info & operator=( my_info const & ) = delete;

public:

    int value;

    explicit my_info( int value ):
        value(value)
    {
        BOOST_TEST(++object_count>0);
        BOOST_TEST(++value_count>0);
    }

    my_info( my_info const & x ):
        value(x.value)
    {
        BOOST_TEST(++object_count>0);
        BOOST_TEST(++value_count>0);
    }

    my_info( my_info && x ):
        value(x.value)
    {
        x.value=-1;
        BOOST_TEST(++object_count>0);
    }
    ~my_info()
    {
        BOOST_TEST(--object_count>=0);
        if( value!=-1 )
            BOOST_TEST(--value_count>=0);
    }
};

#ifndef BOOST_LEAF_NO_EXCEPTIONS
class throws_on_copy
{
    throws_on_copy & operator=( throws_on_copy const & )=delete;

public:

    int value;

    throws_on_copy()
    {
        BOOST_TEST(++object_count>0);
    }

    throws_on_copy( throws_on_copy const & )
    {
        throw std::exception();
    }

    throws_on_copy( throws_on_copy && )
    {
        BOOST_TEST(++object_count>0);
    }

    ~throws_on_copy()
    {
        BOOST_TEST(--object_count>=0);
    }
};
#endif

void run_tests()
{
    using leaf::leaf_detail::optional;
    {
        optional<my_info> x;
        BOOST_TEST(x.empty());
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
    {
        my_info a(42);
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        optional<my_info> x(10, a);
        BOOST_TEST_EQ(object_count, 2);
        BOOST_TEST_EQ(value_count, 2);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST_EQ(x.value(10).value, 42);
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
#ifndef BOOST_LEAF_NO_EXCEPTIONS
    {
        throws_on_copy a;
        BOOST_TEST_EQ(object_count, 1);
        try
        {
            optional<throws_on_copy> x(10, a);
            BOOST_TEST(false);
        }
        catch( std::exception & )
        {
        }
    }
#endif
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
    {
        my_info a(42);
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        optional<my_info> x(10, std::move(a));
        BOOST_TEST_EQ(object_count, 2);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST_EQ(x.value(10).value, 42);
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
    {
        optional<my_info> x(10, my_info(42));
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST_EQ(x.value(10).value, 42);
        optional<my_info> y(x);
        BOOST_TEST_EQ(object_count, 2);
        BOOST_TEST_EQ(value_count, 2);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST_EQ(x.value(10).value, 42);
        BOOST_TEST(!y.empty());
        BOOST_TEST(y.has_value(10));
        BOOST_TEST(!y.has_value(11));
        BOOST_TEST_EQ(y.value(10).value, 42);
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
    {
        optional<my_info> x(10, my_info(42));
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST_EQ(x.value(10).value, 42);
        optional<my_info> y(std::move(x));
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(x.empty());
        BOOST_TEST(!x.has_value(10));
        BOOST_TEST(!y.empty());
        BOOST_TEST(y.has_value(10));
        BOOST_TEST(!y.has_value(11));
        BOOST_TEST_EQ(y.value(10).value, 42);
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
    {
        optional<my_info> x(10, my_info(42));
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST_EQ(x.value(10).value, 42);
        optional<my_info> y;
        BOOST_TEST(y.empty());
        BOOST_TEST_EQ(&(y=x), &y);
        BOOST_TEST_EQ(object_count, 2);
        BOOST_TEST_EQ(value_count, 2);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST_EQ(x.value(10).value, 42);
        BOOST_TEST(!y.empty());
        BOOST_TEST(y.has_value(10));
        BOOST_TEST(!y.has_value(11));
        BOOST_TEST_EQ(y.value(10).value, 42);
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
    {
        optional<my_info> x(10, my_info(42));
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST_EQ(x.value(10).value, 42);
        optional<my_info> y(11, my_info(43));
        BOOST_TEST_EQ(object_count, 2);
        BOOST_TEST_EQ(value_count, 2);
        BOOST_TEST(!y.empty());
        BOOST_TEST_EQ(&(y=x), &y);
        BOOST_TEST_EQ(object_count, 2);
        BOOST_TEST_EQ(value_count, 2);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST_EQ(x.value(10).value, 42);
        BOOST_TEST(!y.empty());
        BOOST_TEST(y.has_value(10));
        BOOST_TEST(!y.has_value(11));
        BOOST_TEST_EQ(y.value(10).value, 42);
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
#ifndef BOOST_LEAF_NO_EXCEPTIONS
    {
        optional<throws_on_copy> x(10, throws_on_copy());
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        optional<throws_on_copy> y;
        try
        {
            (void) (y=x);
        }
        catch( std::exception & )
        {
        }
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST(y.empty());
        BOOST_TEST(!y.has_value(10));
    }
#endif
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
    {
        optional<my_info> x(10, my_info(42));
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST_EQ(x.value(10).value, 42);
        optional<my_info> y;
        BOOST_TEST_EQ(&(y=std::move(x)), &y);
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(x.empty());
        BOOST_TEST(!x.has_value(10));
        BOOST_TEST(!y.empty());
        BOOST_TEST(y.has_value(10));
        BOOST_TEST(!y.has_value(11));
        BOOST_TEST_EQ(y.value(10).value, 42);
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
    {
        optional<my_info> x(10, my_info(42));
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST_EQ(x.value(10).value, 42);
        optional<my_info> y(11, my_info(43));
        BOOST_TEST_EQ(object_count, 2);
        BOOST_TEST_EQ(value_count, 2);
        BOOST_TEST(!y.empty());
        BOOST_TEST_EQ(&(y=std::move(x)), &y);
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(x.empty());
        BOOST_TEST(!x.has_value(10));
        BOOST_TEST(!y.empty());
        BOOST_TEST(y.has_value(10));
        BOOST_TEST(!y.has_value(11));
        BOOST_TEST_EQ(y.value(10).value, 42);
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
    {
        optional<my_info> x;
        my_info a(42);
        x.put(10, a);
        BOOST_TEST_EQ(object_count, 2);
        BOOST_TEST_EQ(value_count, 2);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST_EQ(x.value(10).value, 42);
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
    {
        optional<my_info> x(10, my_info(43));
        BOOST_TEST(!x.empty());
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        my_info a(42);
        x.put(10, a);
        BOOST_TEST_EQ(object_count, 2);
        BOOST_TEST_EQ(value_count, 2);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST_EQ(x.value(10).value, 42);
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
    {
        optional<my_info> x;
        BOOST_TEST(x.empty());
        x.put(10, my_info(42));
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST_EQ(x.value(10).value, 42);
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
    {
        optional<my_info> x(11, my_info(43));
        BOOST_TEST(!x.empty());
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        x.put(10, my_info(42));
        BOOST_TEST(!x.empty());
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        BOOST_TEST_EQ(x.value(10).value, 42);
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
    {
        optional<my_info> x(10, my_info(42));
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(!x.empty());
        my_info a = std::move(x).value(10);
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(x.empty());
        BOOST_TEST(!x.has_value(10));
        BOOST_TEST_EQ(a.value, 42);
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
    {
        optional<my_info> x(10, my_info(42));
        BOOST_TEST_EQ(object_count, 1);
        BOOST_TEST_EQ(value_count, 1);
        BOOST_TEST(!x.empty());
        BOOST_TEST(x.has_value(10));
        BOOST_TEST(!x.has_value(11));
        x.reset();
        BOOST_TEST(x.empty());
        BOOST_TEST(!x.has_value(10));
    }
    BOOST_TEST(!object_count);
    BOOST_TEST(!value_count);
}

int main()
{
    run_tests();
    return boost::report_errors();
}
