//  (C) Copyright 2008-10 Anthony Williams 
//
//  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 <utility>
#include <memory>
#include <stdexcept>
#include <string>

#include <boost/test/unit_test.hpp>

#include <boost/fiber/all.hpp>

int gi = 7;

struct my_exception : public std::runtime_error {
    my_exception() :
        std::runtime_error("my_exception") {
    }
};

struct A {
    A() = default;

    A( A const&) = delete;
    A( A &&) = default;

    A & operator=( A const&) = delete;
    A & operator=( A &&) = default;

    int value;
};

struct B {
    bool    bset{ false };

    B() = default;

    B( bool set) :
        bset{ set } {
        gi = 3;
    }

    ~B() {
        if ( bset) {
            gi = -1;
        }
    }

    B( B && other) :
        bset{ other.bset } {
        other.bset = false;
    }

    B & operator=( B && other) {
        if ( this == & other) return * this;
        bset = other.bset;
        other.bset = false;
        return * this;
    }

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

void fn1( boost::fibers::promise< int > * p, int i) {
    boost::this_fiber::yield();
    p->set_value( i);
}

void fn2() {
    boost::fibers::promise< int > p;
    boost::fibers::future< int > f( p.get_future() );
    boost::this_fiber::yield();
    boost::fibers::fiber( boost::fibers::launch::dispatch, fn1, & p, 7).detach();
    boost::this_fiber::yield();
    BOOST_CHECK( 7 == f.get() );
}

int fn3() {
    return 3;
}

void fn4() {
}

int fn5() {
    boost::throw_exception( my_exception() );
    return 3;
}

void fn6() {
    boost::throw_exception( my_exception() );
}

int & fn7() {
    return gi;
}

int fn8( int i) {
    return i;
}

A fn9() {
     A a;
     a.value = 3;
     return a;
}

A fn10() {
    boost::throw_exception( my_exception() );
    return A();
}

B fn11( bool set) {
     B b( set);
     return b;
}

// packaged_task
void test_packaged_task_create() {
    // default constructed packaged_task is not valid
    boost::fibers::packaged_task< int() > t1;
    BOOST_CHECK( ! t1.valid() );

    // packaged_task from function
    boost::fibers::packaged_task< int() > t2( fn3);
    BOOST_CHECK( t2.valid() );
}

// packaged_task
void test_packaged_task_create_move() {
    // default constructed packaged_task is not valid
    boost::fibers::packaged_task< A() > t1;
    BOOST_CHECK( ! t1.valid() );

    // packaged_task from function
    boost::fibers::packaged_task< A() > t2( fn9);
    BOOST_CHECK( t2.valid() );
}

void test_packaged_task_create_void() {
    // default constructed packaged_task is not valid
    boost::fibers::packaged_task< void() > t1;
    BOOST_CHECK( ! t1.valid() );

    // packaged_task from function
    boost::fibers::packaged_task< void() > t2( fn4);
    BOOST_CHECK( t2.valid() );
}

void test_packaged_task_move() {
    boost::fibers::packaged_task< int() > t1( fn3);
    BOOST_CHECK( t1.valid() );

    // move construction
    boost::fibers::packaged_task< int() > t2( std::move( t1) );
    BOOST_CHECK( ! t1.valid() );
    BOOST_CHECK( t2.valid() );

    // move assignment
    t1 = std::move( t2);
    BOOST_CHECK( t1.valid() );
    BOOST_CHECK( ! t2.valid() );
}

void test_packaged_task_move_move() {
    boost::fibers::packaged_task< A() > t1( fn9);
    BOOST_CHECK( t1.valid() );

    // move construction
    boost::fibers::packaged_task< A() > t2( std::move( t1) );
    BOOST_CHECK( ! t1.valid() );
    BOOST_CHECK( t2.valid() );

    // move assignment
    t1 = std::move( t2);
    BOOST_CHECK( t1.valid() );
    BOOST_CHECK( ! t2.valid() );
}

void test_packaged_task_move_void() {
    boost::fibers::packaged_task< void() > t1( fn4);
    BOOST_CHECK( t1.valid() );

    // move construction
    boost::fibers::packaged_task< void() > t2( std::move( t1) );
    BOOST_CHECK( ! t1.valid() );
    BOOST_CHECK( t2.valid() );

    // move assignment
    t1 = std::move( t2);
    BOOST_CHECK( t1.valid() );
    BOOST_CHECK( ! t2.valid() );
}

void test_packaged_task_swap() {
    boost::fibers::packaged_task< int() > t1( fn3);
    BOOST_CHECK( t1.valid() );

    boost::fibers::packaged_task< int() > t2;
    BOOST_CHECK( ! t2.valid() );

    // swap
    t1.swap( t2);
    BOOST_CHECK( ! t1.valid() );
    BOOST_CHECK( t2.valid() );
}

void test_packaged_task_swap_move() {
    boost::fibers::packaged_task< A() > t1( fn9);
    BOOST_CHECK( t1.valid() );

    boost::fibers::packaged_task< A() > t2;
    BOOST_CHECK( ! t2.valid() );

    // swap
    t1.swap( t2);
    BOOST_CHECK( ! t1.valid() );
    BOOST_CHECK( t2.valid() );
}

void test_packaged_task_swap_void() {
    boost::fibers::packaged_task< void() > t1( fn4);
    BOOST_CHECK( t1.valid() );

    boost::fibers::packaged_task< void() > t2;
    BOOST_CHECK( ! t2.valid() );

    // swap
    t1.swap( t2);
    BOOST_CHECK( ! t1.valid() );
    BOOST_CHECK( t2.valid() );
}

void test_packaged_task_reset() {
    {
        boost::fibers::packaged_task< int() > p( fn3);
        boost::fibers::future< int > f( p.get_future() );
        BOOST_CHECK( p.valid() );

        p();
        BOOST_CHECK( 3 == f.get() );

        // reset
        p.reset();
        p();
        f = p.get_future();
        BOOST_CHECK( 3 == f.get() );
    }
    {
        boost::fibers::packaged_task< int() > p;

        bool thrown = false;
        try {
            p.reset();
        } catch ( boost::fibers::packaged_task_uninitialized const&) {
            thrown = true;
        }
        BOOST_CHECK( thrown);
    }
}

void test_packaged_task_reset_destruction() {
        gi = 0;
        boost::fibers::packaged_task< B( bool) > p( fn11);
        BOOST_CHECK( p.valid() );

        BOOST_CHECK( 0 == gi);
        p( true);
        BOOST_CHECK( 3 == gi);

        // reset
        p.reset();
        BOOST_CHECK( -1 == gi);
        p( false);
        BOOST_CHECK( 3 == gi);

        // reset
        p.reset();
        BOOST_CHECK( 3 == gi);
}

void test_packaged_task_reset_move() {
    {
        boost::fibers::packaged_task< A() > p( fn9);
        boost::fibers::future< A > f( p.get_future() );
        BOOST_CHECK( p.valid() );

        p();
        BOOST_CHECK( 3 == f.get().value);

        // reset
        p.reset();
        p();
        f = p.get_future();
        BOOST_CHECK( 3 == f.get().value);
    }
    {
        boost::fibers::packaged_task< A() > p;

        bool thrown = false;
        try {
            p.reset();
        } catch ( boost::fibers::packaged_task_uninitialized const&) {
            thrown = true;
        }
        BOOST_CHECK( thrown);
    }
}

void test_packaged_task_reset_void() {
    {
        boost::fibers::packaged_task< void() > p( fn4);
        boost::fibers::future< void > f( p.get_future() );
        BOOST_CHECK( p.valid() );

        p();
        f.get();

        // reset
        p.reset();
        p();
        f = p.get_future();
        f.get();
    }
    {
        boost::fibers::packaged_task< void() > p;

        bool thrown = false;
        try {
            p.reset();
        } catch ( boost::fibers::packaged_task_uninitialized const&) {
            thrown = true;
        }
        BOOST_CHECK( thrown);
    }
}

void test_packaged_task_get_future() {
    boost::fibers::packaged_task< int() > t1( fn3);
    BOOST_CHECK( t1.valid() );

    // retrieve future
    boost::fibers::future< int > f1 = t1.get_future();
    BOOST_CHECK( f1.valid() );

    // retrieve future a second time
    bool thrown = false;
    try {
        f1 = t1.get_future();
    } catch ( boost::fibers::future_already_retrieved const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);

    // move construction
    boost::fibers::packaged_task< int() > t2( std::move( t1) );
    BOOST_CHECK( ! t1.valid() );
    BOOST_CHECK( t2.valid() );

    // retrieve future from uninitialized
    thrown = false;
    try {
        f1 = t1.get_future();
    } catch ( boost::fibers::packaged_task_uninitialized const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);
}

void test_packaged_task_get_future_move() {
    boost::fibers::packaged_task< A() > t1( fn9);
    BOOST_CHECK( t1.valid() );

    // retrieve future
    boost::fibers::future< A > f1 = t1.get_future();
    BOOST_CHECK( f1.valid() );

    // retrieve future a second time
    bool thrown = false;
    try {
        f1 = t1.get_future();
    } catch ( boost::fibers::future_already_retrieved const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);

    // move construction
    boost::fibers::packaged_task< A() > t2( std::move( t1) );
    BOOST_CHECK( ! t1.valid() );
    BOOST_CHECK( t2.valid() );

    // retrieve future from uninitialized
    thrown = false;
    try {
        f1 = t1.get_future();
    } catch ( boost::fibers::packaged_task_uninitialized const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);
}

void test_packaged_task_get_future_void() {
    boost::fibers::packaged_task< void() > t1( fn4);
    BOOST_CHECK( t1.valid() );

    // retrieve future
    boost::fibers::future< void > f1 = t1.get_future();
    BOOST_CHECK( f1.valid() );

    // retrieve future a second time
    bool thrown = false;
    try {
        f1 = t1.get_future();
    } catch ( boost::fibers::future_already_retrieved const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);

    // move construction
    boost::fibers::packaged_task< void() > t2( std::move( t1) );
    BOOST_CHECK( ! t1.valid() );
    BOOST_CHECK( t2.valid() );

    // retrieve future from uninitialized
    thrown = false;
    try {
        f1 = t1.get_future();
    } catch ( boost::fibers::packaged_task_uninitialized const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);
}

void test_packaged_task_exec() {
    // promise takes a copyable as return type
    boost::fibers::packaged_task< int() > t1( fn3);
    BOOST_CHECK( t1.valid() );
    boost::fibers::future< int > f1 = t1.get_future();
    BOOST_CHECK( f1.valid() );

    // exec
    t1();
    BOOST_CHECK( 3 == f1.get() );

    // exec a second time
    bool thrown = false;
    try {
        t1();
    } catch ( boost::fibers::promise_already_satisfied const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);
}

void test_packaged_task_exec_move() {
    // promise takes a copyable as return type
    boost::fibers::packaged_task< A() > t1( fn9);
    BOOST_CHECK( t1.valid() );
    boost::fibers::future< A > f1 = t1.get_future();
    BOOST_CHECK( f1.valid() );

    // exec
    t1();
    BOOST_CHECK( 3 == f1.get().value);

    // exec a second time
    bool thrown = false;
    try {
        t1();
    } catch ( boost::fibers::promise_already_satisfied const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);
}

void test_packaged_task_exec_param() {
    // promise takes a copyable as return type
    boost::fibers::packaged_task< int( int) > t1( fn8);
    BOOST_CHECK( t1.valid() );
    boost::fibers::future< int > f1 = t1.get_future();
    BOOST_CHECK( f1.valid() );

    // exec
    t1( 3);
    BOOST_CHECK( 3 == f1.get() );

    // exec a second time
    bool thrown = false;
    try {
        t1( 7);
    } catch ( boost::fibers::promise_already_satisfied const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);

    //TODO: packaged_task returns a moveable-only as return type
}

void test_packaged_task_exec_ref() {
    // promise takes a copyable as return type
    boost::fibers::packaged_task< int&() > t1( fn7);
    BOOST_CHECK( t1.valid() );
    boost::fibers::future< int& > f1 = t1.get_future();
    BOOST_CHECK( f1.valid() );

    // exec
    t1();
    int & i = f1.get();
    BOOST_CHECK( &gi == &i);

    // exec a second time
    bool thrown = false;
    try {
        t1();
    } catch ( boost::fibers::promise_already_satisfied const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);

    //TODO: packaged_task returns a moveable-only as return type
}

void test_packaged_task_exec_void() {
    // promise takes a copyable as return type
    boost::fibers::packaged_task< void() > t1( fn4);
    BOOST_CHECK( t1.valid() );
    boost::fibers::future< void > f1 = t1.get_future();
    BOOST_CHECK( f1.valid() );

    // set void
    t1();
    f1.get();

    // exec a second time
    bool thrown = false;
    try {
        t1();
    } catch ( boost::fibers::promise_already_satisfied const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);
}

void test_packaged_task_exception() {
    // promise takes a copyable as return type
    boost::fibers::packaged_task< int() > t1( fn5);
    BOOST_CHECK( t1.valid() );
    boost::fibers::future< int > f1 = t1.get_future();
    BOOST_CHECK( f1.valid() );

    // exec
    t1();
    bool thrown = false;
    try {
        f1.get();
    } catch ( my_exception const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);

    boost::fibers::packaged_task< int() > t2( fn5);
    BOOST_CHECK( t2.valid() );
    boost::fibers::future< int > f2 = t2.get_future();
    BOOST_CHECK( f2.valid() );

    // exec
    t2();
    BOOST_CHECK( f2.get_exception_ptr() );
    thrown = false;
    try
    { std::rethrow_exception( f2.get_exception_ptr() ); }
    catch ( my_exception const&)
    { thrown = true; }
    BOOST_CHECK( thrown);
}

void test_packaged_task_exception_move() {
    // promise takes a moveable as return type
    boost::fibers::packaged_task< A() > t1( fn10);
    BOOST_CHECK( t1.valid() );
    boost::fibers::future< A > f1 = t1.get_future();
    BOOST_CHECK( f1.valid() );

    // exec
    t1();
    bool thrown = false;
    try {
        f1.get();
    } catch ( my_exception const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);

    boost::fibers::packaged_task< A() > t2( fn10);
    BOOST_CHECK( t2.valid() );
    boost::fibers::future< A > f2 = t2.get_future();
    BOOST_CHECK( f2.valid() );

    // exec
    t2();
    BOOST_CHECK( f2.get_exception_ptr() );
    thrown = false;
    try
    { std::rethrow_exception( f2.get_exception_ptr() ); }
    catch ( my_exception const&)
    { thrown = true; }
    BOOST_CHECK( thrown);
}

void test_packaged_task_exception_void() {
    // promise takes a copyable as return type
    boost::fibers::packaged_task< void() > t1( fn6);
    BOOST_CHECK( t1.valid() );
    boost::fibers::future< void > f1 = t1.get_future();
    BOOST_CHECK( f1.valid() );

    // set void
    t1();
    bool thrown = false;
    try {
        f1.get();
    } catch ( my_exception const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);
    
    boost::fibers::packaged_task< void() > t2( fn6);
    BOOST_CHECK( t2.valid() );
    boost::fibers::future< void > f2 = t2.get_future();
    BOOST_CHECK( f2.valid() );

    // exec
    t2();
    BOOST_CHECK( f2.get_exception_ptr() );
    thrown = false;
    try {
        std::rethrow_exception( f2.get_exception_ptr() );
    } catch ( my_exception const&) {
        thrown = true;
    }
    BOOST_CHECK( thrown);
}


boost::unit_test_framework::test_suite* init_unit_test_suite(int, char*[]) {
    boost::unit_test_framework::test_suite* test =
        BOOST_TEST_SUITE("Boost.Fiber: packaged_task test suite");

    test->add(BOOST_TEST_CASE(test_packaged_task_create));
    test->add(BOOST_TEST_CASE(test_packaged_task_create_move));
    test->add(BOOST_TEST_CASE(test_packaged_task_create_void));
    test->add(BOOST_TEST_CASE(test_packaged_task_move));
    test->add(BOOST_TEST_CASE(test_packaged_task_move_move));
    test->add(BOOST_TEST_CASE(test_packaged_task_move_void));
    test->add(BOOST_TEST_CASE(test_packaged_task_swap));
    test->add(BOOST_TEST_CASE(test_packaged_task_swap_move));
    test->add(BOOST_TEST_CASE(test_packaged_task_swap_void));
    test->add(BOOST_TEST_CASE(test_packaged_task_reset));
    test->add(BOOST_TEST_CASE(test_packaged_task_reset_destruction));
    test->add(BOOST_TEST_CASE(test_packaged_task_reset_move));
    test->add(BOOST_TEST_CASE(test_packaged_task_reset_void));
    test->add(BOOST_TEST_CASE(test_packaged_task_get_future));
    test->add(BOOST_TEST_CASE(test_packaged_task_get_future_move));
    test->add(BOOST_TEST_CASE(test_packaged_task_get_future_void));
    test->add(BOOST_TEST_CASE(test_packaged_task_exec));
    test->add(BOOST_TEST_CASE(test_packaged_task_exec_move));
    test->add(BOOST_TEST_CASE(test_packaged_task_exec_param));
    test->add(BOOST_TEST_CASE(test_packaged_task_exec_ref));
    test->add(BOOST_TEST_CASE(test_packaged_task_exec_void));
    test->add(BOOST_TEST_CASE(test_packaged_task_exception));
    test->add(BOOST_TEST_CASE(test_packaged_task_exception_move));
    test->add(BOOST_TEST_CASE(test_packaged_task_exception_void));

    return test;
}
