
//          Copyright Oliver Kowalke 2009.
// 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 <stdio.h>
#include <stdlib.h>

#include <cmath>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <thread>
#include <utility>
#include <vector>

#include <boost/array.hpp>
#include <boost/assert.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/utility.hpp>
#include <boost/variant.hpp>

#include <boost/context/fiber.hpp>
#include <boost/context/detail/config.hpp>

#ifdef BOOST_WINDOWS
#include <windows.h>
#endif

#if defined(BOOST_MSVC)
# pragma warning(push)
# pragma warning(disable: 4702 4723 4996)
#endif

typedef boost::variant<int,std::string> variant_t;

namespace ctx = boost::context;

int value1 = 0;
std::string value2;
double value3 = 0.;

struct X {
    ctx::fiber foo( ctx::fiber && f, int i) {
        value1 = i;
        return std::move( f);
    }
};

struct Y {
    Y() {
        value1 = 3;
    }

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

    ~Y() {
        value1 = 7;
    }
};

class moveable {
public:
    bool    state;
    int     value;

    moveable() :
        state( false),
        value( -1) {
    }

    moveable( int v) :
        state( true),
        value( v) {
    }

    moveable( moveable && other) :
        state( other.state),
        value( other.value) {
        other.state = false;
        other.value = -1;
    }

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

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

    void operator()() {
        value1 = value;
    }
};

struct my_exception : public std::runtime_error {
    ctx::fiber   f;
    my_exception( ctx::fiber && f_, char const* what) :
        std::runtime_error( what),
        f{ std::move( f_) } {
    }
};

#ifdef BOOST_MSVC
// Optimizations can remove the integer-divide-by-zero here.
#pragma optimize("", off)
void seh( bool & catched) {
    __try {
        int i = 1;
        i /= 0;
    } __except( EXCEPTION_EXECUTE_HANDLER) {
        catched = true;
    }
}
#pragma optimize("", on)
#endif

void test_move() {
    value1 = 0;
    int i = 1;
    BOOST_CHECK_EQUAL( 0, value1);
    ctx::fiber f1{
        [&i](ctx::fiber && f) {
            value1 = i;
            f = std::move( f).resume();
            value1 = i;
            return std::move( f);
        }};
    f1 = std::move( f1).resume();
    BOOST_CHECK_EQUAL( 1, value1);
    BOOST_CHECK( f1);
    ctx::fiber f2;
    BOOST_CHECK( ! f2);
    f2 = std::move( f1);
    BOOST_CHECK( ! f1);
    BOOST_CHECK( f2);
    i = 3;
    f2 = std::move( f2).resume();
    BOOST_CHECK_EQUAL( 3, value1);
    BOOST_CHECK( ! f1);
    BOOST_CHECK( ! f2);
}

void test_bind() {
    value1 = 0;
    X x;
    ctx::fiber f{ std::bind( & X::foo, x, std::placeholders::_1, 7) };
    f = std::move( f).resume();
    BOOST_CHECK_EQUAL( 7, value1);
}

void test_exception() {
    {
        const char * what = "hello world";
        ctx::fiber f{
            [&what](ctx::fiber && f) {
                try {
                    throw std::runtime_error( what);
                } catch ( std::runtime_error const& e) {
                    value2 = e.what();
                }
                return std::move( f);
            }};
        f = std::move( f).resume();
        BOOST_CHECK_EQUAL( std::string( what), value2);
        BOOST_CHECK( ! f);
    }
#ifdef BOOST_MSVC
    {
        bool catched = false;
        std::thread([&catched](){
                ctx::fiber f{ [&catched](ctx::fiber && f){
                            seh( catched);
                            return std::move( f);
                        }};
            BOOST_CHECK( f);
            f = std::move( f).resume();
        }).join();
        BOOST_CHECK( catched);
    }
#endif
}

void test_fp() {
    value3 = 0.;
    double d = 7.13;
    ctx::fiber f{
        [&d]( ctx::fiber && f) {
            d += 3.45;
            value3 = d;
            return std::move( f);
        }};
    f = std::move( f).resume();
    BOOST_CHECK_EQUAL( 10.58, value3);
    BOOST_CHECK( ! f);
}

void test_stacked() {
    value1 = 0;
    value3 = 0.;
    ctx::fiber f{
        [](ctx::fiber && f) {
            ctx::fiber f1{
                [](ctx::fiber && f) {
                    value1 = 3;
                    return std::move( f);
                }};
            f1 = std::move( f1).resume();
            value3 = 3.14;
            return std::move( f);
        }};
    f = std::move( f).resume();
    BOOST_CHECK_EQUAL( 3, value1);
    BOOST_CHECK_EQUAL( 3.14, value3);
    BOOST_CHECK( ! f);
}

void test_prealloc() {
    value1 = 0;
    ctx::default_stack alloc;
    ctx::stack_context sctx( alloc.allocate() );
    void * sp = static_cast< char * >( sctx.sp) - 10;
    std::size_t size = sctx.size - 10;
    int i = 7;
    ctx::fiber f{
        std::allocator_arg, ctx::preallocated( sp, size, sctx), alloc,
        [&i]( ctx::fiber && f) {
            value1 = i;
            return std::move( f);
        }};
    f = std::move( f).resume();
    BOOST_CHECK_EQUAL( 7, value1);
    BOOST_CHECK( ! f);
}

void test_ontop() {
    {
        int i = 3;
        ctx::fiber f{ [&i](ctx::fiber && f) {
                    for (;;) {
                        i *= 10;
                        f = std::move( f).resume();
                    }
                    return std::move( f);
                }};
        f = std::move( f).resume();
        // Pass fn by reference to see if the types are properly decayed.
        auto fn = [&i](ctx::fiber && f){
                   i -= 10;
                   return std::move( f);
        };
        f = std::move( f).resume_with(fn);
        BOOST_CHECK( f);
        BOOST_CHECK_EQUAL( i, 200);
    }
    {
        ctx::fiber f1;
        ctx::fiber f{ [&f1](ctx::fiber && f) {
                    f = std::move( f).resume();
                    BOOST_CHECK( ! f);
                    return std::move( f1);
                }};
        f = std::move( f).resume();
        f = std::move( f).resume_with(
               [&f1](ctx::fiber && f){
                   f1 = std::move( f);
                   return std::move( f);
               });
    }
}

void test_ontop_exception() {
    value1 = 0;
    value2 = "";
    ctx::fiber f{ [](ctx::fiber && f){
            for (;;) {
                value1 = 3;
                try {
                    f = std::move( f).resume();
                } catch ( my_exception & ex) {
                    value2 = ex.what();
                    return std::move( ex.f);
                }
            }
            return std::move( f);
    }};
    f = std::move( f).resume();
    BOOST_CHECK_EQUAL( 3, value1);
    const char * what = "hello world";
    f = std::move( f).resume_with(
       [what](ctx::fiber && f){
            throw my_exception( std::move( f), what);
            return std::move( f);
       });
    BOOST_CHECK_EQUAL( 3, value1);
    BOOST_CHECK_EQUAL( std::string( what), value2);
}

void test_termination1() {
    {
        value1 = 0;
        ctx::fiber f{
            [](ctx::fiber && f){
                Y y;
                f = std::move( f).resume();
                return std::move(f);
            }};
        f = std::move( f).resume();
        BOOST_CHECK_EQUAL( 3, value1);
    }
    BOOST_CHECK_EQUAL( 7, value1);
    {
        value1 = 0;
        BOOST_CHECK_EQUAL( 0, value1);
        ctx::fiber f{
            [](ctx::fiber && f) {
                value1 = 3;
                return std::move( f);
            }};
        f = std::move( f).resume();
        BOOST_CHECK_EQUAL( 3, value1);
        BOOST_CHECK( ! f);
    }
    {
        value1 = 0;
        BOOST_CHECK_EQUAL( 0, value1);
        int i = 3;
        ctx::fiber f{
            [&i](ctx::fiber && f){
                value1 = i;
                f = std::move( f).resume();
                value1 = i;
                return std::move( f);
            }};
        f = std::move( f).resume();
        BOOST_CHECK( f);
        BOOST_CHECK_EQUAL( i, value1);
        BOOST_CHECK( f);
        i = 7;
        f = std::move( f).resume();
        BOOST_CHECK( ! f);
        BOOST_CHECK_EQUAL( i, value1);
    }
}

void test_termination2() {
    {
        value1 = 0;
        value3 = 0.0;
        ctx::fiber f{
            [](ctx::fiber && f){
                Y y;
                value1 = 3;
                value3 = 4.;
                f = std::move( f).resume();
                value1 = 7;
                value3 = 8.;
                f = std::move( f).resume();
                return std::move( f);
            }};
        BOOST_CHECK_EQUAL( 0, value1);
        BOOST_CHECK_EQUAL( 0., value3);
        f = std::move( f).resume();
        BOOST_CHECK_EQUAL( 3, value1);
        BOOST_CHECK_EQUAL( 4., value3);
        f = std::move( f).resume();
    }
    BOOST_CHECK_EQUAL( 7, value1);
    BOOST_CHECK_EQUAL( 8., value3);
}

void test_sscanf() {
    ctx::fiber{
		[]( ctx::fiber && f) {
			{
				double n1 = 0;
				double n2 = 0;
				sscanf("3.14 7.13", "%lf %lf", & n1, & n2);
				BOOST_CHECK( n1 == 3.14);
				BOOST_CHECK( n2 == 7.13);
			}
			{
				int n1=0;
				int n2=0;
				sscanf("1 23", "%d %d", & n1, & n2);
				BOOST_CHECK( n1 == 1);
				BOOST_CHECK( n2 == 23);
			}
			{
				int n1=0;
				int n2=0;
				sscanf("1 jjj 23", "%d %*[j] %d", & n1, & n2);
				BOOST_CHECK( n1 == 1);
				BOOST_CHECK( n2 == 23);
			}
			return std::move( f);
	}}.resume();
}

void test_snprintf() {
    ctx::fiber{
		[]( ctx::fiber && f) {
            {
                const char *fmt = "sqrt(2) = %f";
                char buf[19];
                snprintf( buf, sizeof( buf), fmt, std::sqrt( 2) );
                BOOST_CHECK( 0 < sizeof( buf) );
                BOOST_ASSERT( std::string("sqrt(2) = 1.41") == std::string( buf, 14) );
            }
            {
                std::uint64_t n = 0xbcdef1234567890;
                const char *fmt = "0x%016llX";
                char buf[100];
                snprintf( buf, sizeof( buf), fmt, n);
                BOOST_ASSERT( std::string("0x0BCDEF1234567890") == std::string( buf, 18) );
            }
			return std::move( f);
	}}.resume();
}

#ifdef BOOST_WINDOWS
void test_bug12215() {
        ctx::fiber{
            [](ctx::fiber && f) {
                char buffer[MAX_PATH];
                GetModuleFileName( nullptr, buffer, MAX_PATH);
                return std::move( f);
            }}.resume();
}
#endif

void test_goodcatch() {
    value1 = 0;
    value3 = 0.0;
    {
        ctx::fiber f{
            []( ctx::fiber && f) {
                Y y;
                value3 = 2.;
                f = std::move( f).resume();
                try {
                    value3 = 3.;
                    f = std::move( f).resume();
                } catch ( boost::context::detail::forced_unwind const&) {
                    value3 = 4.;
                    throw;
                } catch (...) {
                    value3 = 5.;
                }
                value3 = 6.;
                return std::move( f);
            }};
        BOOST_CHECK_EQUAL( 0, value1);
        BOOST_CHECK_EQUAL( 0., value3);
        f = std::move( f).resume();
        BOOST_CHECK_EQUAL( 3, value1);
        BOOST_CHECK_EQUAL( 2., value3);
        f = std::move( f).resume();
        BOOST_CHECK_EQUAL( 3, value1);
        BOOST_CHECK_EQUAL( 3., value3);
    }
    BOOST_CHECK_EQUAL( 7, value1);
    BOOST_CHECK_EQUAL( 4., value3);
}

void test_badcatch() {
#if 0
    value1 = 0;
    value3 = 0.;
    {
        ctx::fiber f{
            []( ctx::fiber && f) {
                Y y;
                try {
                    value3 = 3.;
                    f = std::move( f).resume();
                } catch (...) {
                    value3 = 5.;
                }
                return std::move( f);
            }};
        BOOST_CHECK_EQUAL( 0, value1);
        BOOST_CHECK_EQUAL( 0., value3);
        f = std::move( f).resume();
        BOOST_CHECK_EQUAL( 3, value1);
        BOOST_CHECK_EQUAL( 3., value3);
        // the destruction of ctx here will cause a forced_unwind to be thrown that is not caught
        // in fn19.  That will trigger the "not caught" assertion in ~forced_unwind.  Getting that
        // assertion to propogate bak here cleanly is non-trivial, and there seems to not be a good
        // way to hook directly into the assertion when it happens on an alternate stack.
        std::move( f);
    }
    BOOST_CHECK_EQUAL( 7, value1);
    BOOST_CHECK_EQUAL( 4., value3);
#endif
}

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

    test->add( BOOST_TEST_CASE( & test_move) );
    test->add( BOOST_TEST_CASE( & test_bind) );
    test->add( BOOST_TEST_CASE( & test_exception) );
    test->add( BOOST_TEST_CASE( & test_fp) );
    test->add( BOOST_TEST_CASE( & test_stacked) );
    test->add( BOOST_TEST_CASE( & test_prealloc) );
    test->add( BOOST_TEST_CASE( & test_ontop) );
    test->add( BOOST_TEST_CASE( & test_ontop_exception) );
    test->add( BOOST_TEST_CASE( & test_termination1) );
    test->add( BOOST_TEST_CASE( & test_termination2) );
    test->add( BOOST_TEST_CASE( & test_sscanf) );
    test->add( BOOST_TEST_CASE( & test_snprintf) );
#ifdef BOOST_WINDOWS
    test->add( BOOST_TEST_CASE( & test_bug12215) );
#endif
    test->add( BOOST_TEST_CASE( & test_goodcatch) );
    test->add( BOOST_TEST_CASE( & test_badcatch) );

    return test;
}

#if defined(BOOST_MSVC)
# pragma warning(pop)
#endif
