
//          Copyright Oliver Kowalke 2014.
// 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 <algorithm>
#include <cstdio>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <tuple>
#include <vector>

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

#include <boost/coroutine2/coroutine.hpp>

namespace coro = boost::coroutines2;

int value1 = 0;
std::string value2 = "";
bool value3 = false;
double value4 = .0;
int * value5 = nullptr;
int& value6 = value1;
int& value7 = value1;
int value8 = 0;
int value9 = 0;

struct X
{
    X() { value1 = 7; }
    ~X() { value1 = 0; }

    X( X const&) = delete;
    X & operator=( X const&) = delete;
};

class copyable
{
public:
    bool    state;

    copyable() :
        state( false)
    {}

    copyable( int) :
        state( true)
    {}

    void operator()( coro::coroutine< int >::push_type &)
    { value3 = state; }
};

class moveable
{
public:
    bool    state;

    moveable() :
        state( false)
    {}

    moveable( int) :
        state( true)
    {}

    moveable( moveable const&) = delete;
    moveable & operator=( moveable const&) = delete;

    moveable( moveable && other) :
        state( false)
    { std::swap( state, other.state); }

    moveable & operator=( moveable && other)
    {
        if ( this != & other) {
            state = other.state;
            other.state = false;
        }
        return * this;
    }

    void operator()( coro::coroutine< int >::push_type &)
    { value3 = state; }
};

class movedata
{
public:
    int     i;

    movedata( int i_) :
        i( i_)
    {}

    movedata( movedata const&) = delete;
    movedata & operator=( movedata const&) = delete;

    movedata( movedata && other) :
        i( 0)
    { std::swap( i, other.i); }

    movedata & operator=( movedata && other)
    {
        if ( this != & other) {
            i = other.i;
            other.i = 0;
        }
        return * this;
    }
};

struct my_exception {};

void f1( coro::coroutine< void >::push_type & c)
{
    while ( c)
        c();
}

void f2( coro::coroutine< void >::push_type &)
{ ++value1; }

void f3( coro::coroutine< void >::push_type & c)
{
    ++value1;
    c();
    ++value1;
}

void f4( coro::coroutine< int >::push_type & c)
{
    c( 3);
    c( 7);
}

void f5( coro::coroutine< std::string >::push_type & c)
{
    std::string res("abc");
    c( res);
    res = "xyz";
    c( res);
}

void f6( coro::coroutine< int >::pull_type & c)
{ value1 = c.get(); }

void f7( coro::coroutine< std::string >::pull_type & c)
{ value2 = c.get(); }

void f8( coro::coroutine< std::tuple< double, double > >::pull_type & c)
{
    double x = 0, y = 0;
    std::tie( x, y) = c.get();
    value4 = x + y;
    c();
    std::tie( x, y) = c.get();
    value4 = x + y;
}

void f9( coro::coroutine< int * >::pull_type & c)
{ value5 = c.get(); }

void f91( coro::coroutine< int const* >::pull_type & c)
{ value5 = const_cast< int * >( c.get() ); }

void f10( coro::coroutine< int & >::pull_type & c)
{
    int & i = c.get();
    value5 = const_cast< int * >( & i);
}

void f101( coro::coroutine< int const& >::pull_type & c)
{
    int const& i = c.get();
    value5 = const_cast< int * >( & i);
}

void f11( coro::coroutine< std::tuple< int, int > >::pull_type & c)
{
    std::tie( value8, value9) = c.get();
}

void f12( coro::coroutine< void >::pull_type & c)
{
    value1 = 7;
    X x_;
    c();
    c();
}

void f16( coro::coroutine< int >::push_type & c)
{
    c( 1);
    c( 2);
    c( 3);
    c( 4);
    c( 5);
}

void f17( coro::coroutine< int >::pull_type & c, std::vector< int > & vec)
{
    int x = c.get();
    while ( 5 > x)
    {
        vec.push_back( x);
        x = c().get();
    }
}

void f20( coro::coroutine< int >::push_type &)
{}

void f21( coro::coroutine< int >::pull_type & c)
{
    while ( c)
    {
        value1 = c.get();
        c();
    }
}

void f22( coro::coroutine< movedata >::pull_type & c)
{
    movedata mv( c.get() );
    value1 = mv.i;
}

void test_move()
{
    {
        coro::coroutine< int >::pull_type coro1( f20);
        coro::coroutine< int >::pull_type coro2( f16);
        BOOST_CHECK( ! coro1);
        BOOST_CHECK( coro2);
        BOOST_CHECK_EQUAL( 1, coro2.get() );
        coro2();
        BOOST_CHECK_EQUAL( 2, coro2.get() );
        coro1 = std::move( coro2);
        BOOST_CHECK( coro1);
        BOOST_CHECK( ! coro2);
        coro1();
        BOOST_CHECK_EQUAL( 3, coro1.get() );
        BOOST_CHECK( ! coro2);
    }

    {
        value3 = false;
        copyable cp( 3);
        BOOST_CHECK( cp.state);
        BOOST_CHECK( ! value3);
        coro::coroutine< int >::pull_type coro( cp);
        BOOST_CHECK( cp.state);
        BOOST_CHECK( value3);
    }

    {
        value3 = false;
        moveable mv( 7);
        BOOST_CHECK( mv.state);
        BOOST_CHECK( ! value3);
        coro::coroutine< int >::pull_type coro( std::move( mv) );
        BOOST_CHECK( ! mv.state);
        BOOST_CHECK( value3);
    }

    {
        value1 = 0;
        movedata mv( 7);
        BOOST_CHECK_EQUAL( 0, value1);
        BOOST_CHECK_EQUAL( 7, mv.i);
        coro::coroutine< movedata >::push_type coro( f22);
        coro( std::move( mv) );
        BOOST_CHECK_EQUAL( 7, value1);
        BOOST_CHECK_EQUAL( 0, mv.i);
    }
}

void test_complete()
{
    value1 = 0;

    coro::coroutine< void >::pull_type coro( f2);
    BOOST_CHECK( ! coro);
    BOOST_CHECK_EQUAL( ( int)1, value1);
}

void test_bind()
{
    value1 = 0;

    coro::coroutine< void >::pull_type coro( std::bind( f2, std::placeholders::_1) );
    BOOST_CHECK( ! coro);
    BOOST_CHECK_EQUAL( ( int)1, value1);
}

void test_jump()
{
    value1 = 0;

    coro::coroutine< void >::pull_type coro( f3);
    BOOST_CHECK( coro);
    BOOST_CHECK_EQUAL( ( int)1, value1);
    coro();
    BOOST_CHECK( ! coro);
    BOOST_CHECK_EQUAL( ( int)2, value1);
}

void test_result_int()
{
    coro::coroutine< int >::pull_type coro( f4);
    BOOST_CHECK( coro);
    int result = coro.get();
    BOOST_CHECK( coro);
    BOOST_CHECK_EQUAL( 3, result);
    result = coro().get();
    BOOST_CHECK( coro);
    BOOST_CHECK_EQUAL( 7, result);
    coro();
    BOOST_CHECK( ! coro);
}

void test_result_string()
{
    coro::coroutine< std::string >::pull_type coro( f5);
    BOOST_CHECK( coro);
    std::string result = coro.get();
    BOOST_CHECK( coro);
    BOOST_CHECK_EQUAL( std::string("abc"), result);
    result = coro().get();
    BOOST_CHECK( coro);
    BOOST_CHECK_EQUAL( std::string("xyz"), result);
    coro();
    BOOST_CHECK( ! coro);
}

void test_arg_int()
{
    value1 = 0;

    coro::coroutine< int >::push_type coro( f6);
    BOOST_CHECK( coro);
    coro( 3);
    BOOST_CHECK( ! coro);
    BOOST_CHECK_EQUAL( 3, value1);
}

void test_arg_string()
{
    value2 = "";

    coro::coroutine< std::string >::push_type coro( f7);
    BOOST_CHECK( coro);
    coro( std::string("abc") );
    BOOST_CHECK( ! coro);
    BOOST_CHECK_EQUAL( std::string("abc"), value2);
}

void test_fp()
{
    value4 = 0;

    coro::coroutine< std::tuple< double, double > >::push_type coro( f8);
    BOOST_CHECK( coro);
    coro( std::make_tuple( 7.35, 3.14) );
    BOOST_CHECK( coro);
    BOOST_CHECK_EQUAL( ( double) 10.49, value4);

    value4 = 0;
    coro( std::make_tuple( 1.15, 3.14) );
    BOOST_CHECK( ! coro);
    BOOST_CHECK_EQUAL( ( double) 4.29, value4);
}

void test_ptr()
{
    value5 = nullptr;

    int a = 3;
    coro::coroutine< int * >::push_type coro( f9);
    BOOST_CHECK( coro);
    coro( & a);
    BOOST_CHECK( ! coro);
    BOOST_CHECK_EQUAL( & a, value5);
}

void test_const_ptr()
{
    value5 = nullptr;

    int a = 3;
    coro::coroutine< int const* >::push_type coro( f91);
    BOOST_CHECK( coro);
    coro( & a);
    BOOST_CHECK( ! coro);
    BOOST_CHECK_EQUAL( & a, value5);
}

void test_ref()
{
    value5 = nullptr;

    int a_ = 3;
    int & a = a_;
    coro::coroutine< int & >::push_type coro( f10);
    BOOST_CHECK( coro);
    coro( std::ref( a) );
    BOOST_CHECK( ! coro);
    BOOST_CHECK_EQUAL( & a, value5);
}

void test_const_ref()
{
    value5 = nullptr;

    int a = 3;
    coro::coroutine< int const& >::push_type coro( f101);
    BOOST_CHECK( coro);
    coro( a);
    BOOST_CHECK( ! coro);
    BOOST_CHECK_EQUAL( & a, value5);
}

void test_no_result()
{
    coro::coroutine< int >::pull_type coro( f20);
    BOOST_CHECK( ! coro);
}

void test_tuple()
{
    value8 = 0;
    value9 = 0;

    int a = 3, b = 7;
    std::tuple< int, int > tpl( a, b);
    BOOST_CHECK_EQUAL( a, std::get< 0 >( tpl) );
    BOOST_CHECK_EQUAL( b, std::get< 1 >( tpl) );
    coro::coroutine< std::tuple< int, int > >::push_type coro( f11);
    BOOST_CHECK( coro);
    coro( tpl);
    BOOST_CHECK( ! coro);
    BOOST_CHECK_EQUAL( a, value8);
    BOOST_CHECK_EQUAL( b, value9);
}

void test_unwind()
{
    value1 = 0;
    {
        coro::coroutine< void >::push_type coro( f12);
        BOOST_CHECK( coro);
        BOOST_CHECK_EQUAL( ( int) 0, value1);
        coro();
        BOOST_CHECK( coro);
        BOOST_CHECK_EQUAL( ( int) 7, value1);
        coro();
        BOOST_CHECK_EQUAL( ( int) 7, value1);
    }
    BOOST_CHECK_EQUAL( ( int) 0, value1);
    int i = 0;
    {
        coro::coroutine< void >::push_type coro(
            [&i](coro::coroutine< void >::pull_type &) mutable {
                i = 7;
        });
    }
    {
        BOOST_CHECK_EQUAL( ( int) 0, value1);
        auto * coro = new coro::coroutine< void >::pull_type(
            [](coro::coroutine< void >::push_type & coro) mutable {
                X x;
                coro();
            });
        BOOST_CHECK_EQUAL( ( int) 7, value1);
        delete coro;
        BOOST_CHECK_EQUAL( ( int) 0, value1);
    }
    {
        BOOST_CHECK_EQUAL( ( int) 0, value1);
        auto * coro = new coro::coroutine< void >::push_type(
            [](coro::coroutine< void >::pull_type & coro) mutable {
                X x;
                coro();
            });
        ( * coro)();
        BOOST_CHECK_EQUAL( ( int) 7, value1);
        delete coro;
        BOOST_CHECK_EQUAL( ( int) 0, value1);
    }
}

void test_exceptions()
{
    std::string msg("abc"), value;
    std::runtime_error ex( msg);
    try
    {
        coro::coroutine< void >::push_type coro(
                [&msg]( coro::coroutine< void >::pull_type &) {
                    throw std::runtime_error( msg);
                });
        BOOST_CHECK( coro);
        coro();
        BOOST_CHECK( ! coro);
        BOOST_CHECK( false);
    }
    catch ( std::runtime_error const& ex)
    { value = ex.what(); }
    BOOST_CHECK_EQUAL( value, msg);
}

void test_input_iterator()
{
    {
        using std::begin;
        using std::end;

        std::vector< int > vec;
        coro::coroutine< int >::pull_type coro( f16);
        coro::coroutine< int >::pull_type::iterator e = end( coro);
        for (
            coro::coroutine< int >::pull_type::iterator i = begin( coro);
            i != e; ++i)
        { vec.push_back( * i); }
        BOOST_CHECK_EQUAL( ( std::size_t)5, vec.size() );
        BOOST_CHECK_EQUAL( ( int)1, vec[0] );
        BOOST_CHECK_EQUAL( ( int)2, vec[1] );
        BOOST_CHECK_EQUAL( ( int)3, vec[2] );
        BOOST_CHECK_EQUAL( ( int)4, vec[3] );
        BOOST_CHECK_EQUAL( ( int)5, vec[4] );
    }
    {
        std::vector< int > vec;
        coro::coroutine< int >::pull_type coro( f16);
        for ( auto i : coro)
        { vec.push_back( i); }
        BOOST_CHECK_EQUAL( ( std::size_t)5, vec.size() );
        BOOST_CHECK_EQUAL( ( int)1, vec[0] );
        BOOST_CHECK_EQUAL( ( int)2, vec[1] );
        BOOST_CHECK_EQUAL( ( int)3, vec[2] );
        BOOST_CHECK_EQUAL( ( int)4, vec[3] );
        BOOST_CHECK_EQUAL( ( int)5, vec[4] );
    }
    {
        int i1 = 1, i2 = 2, i3 = 3;
        coro::coroutine< int& >::pull_type coro(
            [&i1,&i2,&i3](coro::coroutine< int& >::push_type & c){
                c( i1);
                c( i2);
                c( i3);
            });

        int counter = 1;
        for ( int & i : coro) {
            switch ( counter) {
            case 1:
                BOOST_CHECK_EQUAL( & i1, & i);
                break;
            case 2:
                BOOST_CHECK_EQUAL( & i2, & i);
                break;
            case 3:
                BOOST_CHECK_EQUAL( & i3, & i);
                break;
            default:
                BOOST_ASSERT( false);
            }
            ++counter;
        }
    }
}

void test_output_iterator()
{
    using std::begin;
    using std::end;

    int counter = 0;
    std::vector< int > vec;
    coro::coroutine< int >::push_type coro(
        [&vec]( coro::coroutine< int >::pull_type & c) {
            int x = c.get();
            while ( 5 > x)
            {
                vec.push_back( x);
                x = c().get();
            }
        });
    coro::coroutine< int >::push_type::iterator e( end( coro) );
    for ( coro::coroutine< int >::push_type::iterator i( begin( coro) );
          i != e; ++i)
    {
        i = ++counter;
    }
    BOOST_CHECK_EQUAL( ( std::size_t)4, vec.size() );
    BOOST_CHECK_EQUAL( ( int)1, vec[0] );
    BOOST_CHECK_EQUAL( ( int)2, vec[1] );
    BOOST_CHECK_EQUAL( ( int)3, vec[2] );
    BOOST_CHECK_EQUAL( ( int)4, vec[3] );
}

std::vector< int > vec;
coro::coroutine< void >::pull_type * child = nullptr;

void start_child_coroutine() {
    child = new coro::coroutine< void >::pull_type([](coro::coroutine< void >::push_type & yield) {
            vec.push_back( 2);
            yield();
            vec.push_back( 2);
            yield();
            vec.push_back( 2);
            yield();
            vec.push_back( 2);
            yield();
            vec.push_back( 2);
            yield();
            vec.push_back( 2);
            });
}

coro::coroutine< void >::pull_type start_parent_coroutine() {
    return coro::coroutine< void >::pull_type([=](coro::coroutine< void >::push_type & yield) {
            vec.push_back( 1);
            start_child_coroutine();
            yield();
            vec.push_back( 1);
            });
}

void test_chaining()
{
    auto parent = start_parent_coroutine();
    while ( * child) {
        ( * child)();
    }
    BOOST_CHECK_EQUAL( 7, vec.size() );
    BOOST_CHECK_EQUAL( 1, vec[0]);
    BOOST_CHECK_EQUAL( 2, vec[1]);
    BOOST_CHECK_EQUAL( 2, vec[2]);
    BOOST_CHECK_EQUAL( 2, vec[3]);
    BOOST_CHECK_EQUAL( 2, vec[4]);
    BOOST_CHECK_EQUAL( 2, vec[5]);
    BOOST_CHECK_EQUAL( 2, vec[6]);
}

boost::unit_test::test_suite * init_unit_test_suite( int, char* [])
{
    boost::unit_test::test_suite * test =
        BOOST_TEST_SUITE("Boost.Coroutine2: coroutine test suite");

    test->add( BOOST_TEST_CASE( & test_move) );
    test->add( BOOST_TEST_CASE( & test_complete) );
    test->add( BOOST_TEST_CASE( & test_bind) );
    test->add( BOOST_TEST_CASE( & test_jump) );
    test->add( BOOST_TEST_CASE( & test_result_int) );
    test->add( BOOST_TEST_CASE( & test_result_string) );
    test->add( BOOST_TEST_CASE( & test_arg_int) );
    test->add( BOOST_TEST_CASE( & test_arg_string) );
    test->add( BOOST_TEST_CASE( & test_fp) );
    test->add( BOOST_TEST_CASE( & test_ptr) );
    test->add( BOOST_TEST_CASE( & test_const_ptr) );
    test->add( BOOST_TEST_CASE( & test_no_result) );
    test->add( BOOST_TEST_CASE( & test_ref) );
    test->add( BOOST_TEST_CASE( & test_const_ref) );
    test->add( BOOST_TEST_CASE( & test_tuple) );
    test->add( BOOST_TEST_CASE( & test_unwind) );
    test->add( BOOST_TEST_CASE( & test_exceptions) );
    test->add( BOOST_TEST_CASE( & test_input_iterator) );
    test->add( BOOST_TEST_CASE( & test_output_iterator) );
    test->add( BOOST_TEST_CASE( & test_chaining) );

    return test;
}
