#ifndef BOOST_LEAF_CAPTURE_HPP_INCLUDED
#define BOOST_LEAF_CAPTURE_HPP_INCLUDED

// 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)

#ifndef BOOST_LEAF_ENABLE_WARNINGS
#   if defined(__clang__)
#       pragma clang system_header
#   elif (__GNUC__*100+__GNUC_MINOR__>301)
#       pragma GCC system_header
#   elif defined(_MSC_VER)
#       pragma warning(push,1)
#   endif
#endif

#include <boost/leaf/exception.hpp>
#include <boost/leaf/on_error.hpp>

namespace boost { namespace leaf {

    namespace leaf_detail
    {
        template <class R, bool IsResult = is_result_type<R>::value>
        struct is_result_tag;

        template <class R>
        struct is_result_tag<R, false>
        {
        };

        template <class R>
        struct is_result_tag<R, true>
        {
        };
    }

#ifdef BOOST_LEAF_NO_EXCEPTIONS

    namespace leaf_detail
    {
        template <class R, class F, class... A>
        inline
        decltype(std::declval<F>()(std::forward<A>(std::declval<A>())...))
        capture_impl(is_result_tag<R, false>, context_ptr && ctx, F && f, A... a) noexcept
        {
            auto active_context = activate_context(*ctx);
            return std::forward<F>(f)(std::forward<A>(a)...);
        }

        template <class R, class F, class... A>
        inline
        decltype(std::declval<F>()(std::forward<A>(std::declval<A>())...))
        capture_impl(is_result_tag<R, true>, context_ptr && ctx, F && f, A... a) noexcept
        {
            auto active_context = activate_context(*ctx);
            if( auto r = std::forward<F>(f)(std::forward<A>(a)...) )
                return r;
            else
            {
                ctx->captured_id_ = r.error();
                return std::move(ctx);
            }
        }

        template <class R, class Future>
        inline
        decltype(std::declval<Future>().get())
        future_get_impl(is_result_tag<R, false>, Future & fut) noexcept
        {
            return fut.get();
        }

        template <class R, class Future>
        inline
        decltype(std::declval<Future>().get())
        future_get_impl(is_result_tag<R, true>, Future & fut) noexcept
        {
            if( auto r = fut.get() )
                return r;
            else
                return error_id(r.error()); // unloads
        }
    }

#else

    namespace leaf_detail
    {
        class capturing_exception:
            public std::exception
        {
            std::exception_ptr ex_;
            context_ptr ctx_;

        public:

            capturing_exception(std::exception_ptr && ex, context_ptr && ctx) noexcept:
                ex_(std::move(ex)),
                ctx_(std::move(ctx))
            {
                BOOST_LEAF_ASSERT(ex_);
                BOOST_LEAF_ASSERT(ctx_);
                BOOST_LEAF_ASSERT(ctx_->captured_id_);
            }

            [[noreturn]] void unload_and_rethrow_original_exception() const
            {
                BOOST_LEAF_ASSERT(ctx_->captured_id_);
                auto active_context = activate_context(*ctx_);
                id_factory<>::current_id = ctx_->captured_id_.value();
                std::rethrow_exception(ex_);
            }

            template <class CharT, class Traits>
            void print( std::basic_ostream<CharT, Traits> & os ) const
            {
                ctx_->print(os);
            }
        };

        template <class R, class F, class... A>
        inline
        decltype(std::declval<F>()(std::forward<A>(std::declval<A>())...))
        capture_impl(is_result_tag<R, false>, context_ptr && ctx, F && f, A... a)
        {
            auto active_context = activate_context(*ctx);
            error_monitor cur_err;
            try
            {
                return std::forward<F>(f)(std::forward<A>(a)...);
            }
            catch( capturing_exception const & )
            {
                throw;
            }
            catch( exception_base const & e )
            {
                ctx->captured_id_ = e.get_error_id();
                throw_exception( capturing_exception(std::current_exception(), std::move(ctx)) );
            }
            catch(...)
            {
                ctx->captured_id_ = cur_err.assigned_error_id();
                throw_exception( capturing_exception(std::current_exception(), std::move(ctx)) );
            }
        }

        template <class R, class F, class... A>
        inline
        decltype(std::declval<F>()(std::forward<A>(std::declval<A>())...))
        capture_impl(is_result_tag<R, true>, context_ptr && ctx, F && f, A... a)
        {
            auto active_context = activate_context(*ctx);
            error_monitor cur_err;
            try
            {
                if( auto && r = std::forward<F>(f)(std::forward<A>(a)...) )
                    return std::move(r);
                else
                {
                    ctx->captured_id_ = r.error();
                    return std::move(ctx);
                }
            }
            catch( capturing_exception const & )
            {
                throw;
            }
            catch( exception_base const & e )
            {
                ctx->captured_id_ = e.get_error_id();
                throw_exception( capturing_exception(std::current_exception(), std::move(ctx)) );
            }
            catch(...)
            {
                ctx->captured_id_ = cur_err.assigned_error_id();
                throw_exception( capturing_exception(std::current_exception(), std::move(ctx)) );
            }
        }

        template <class R, class Future>
        inline
        decltype(std::declval<Future>().get())
        future_get_impl(is_result_tag<R, false>, Future & fut )
        {
            try
            {
                return fut.get();
            }
            catch( capturing_exception const & cap )
            {
                cap.unload_and_rethrow_original_exception();
            }
        }

        template <class R, class Future>
        inline
        decltype(std::declval<Future>().get())
        future_get_impl(is_result_tag<R, true>, Future & fut )
        {
            try
            {
                if( auto r = fut.get() )
                    return r;
                else
                    return error_id(r.error()); // unloads
            }
            catch( capturing_exception const & cap )
            {
                cap.unload_and_rethrow_original_exception();
            }
        }
    }

#endif

    template <class F, class... A>
    inline
    decltype(std::declval<F>()(std::forward<A>(std::declval<A>())...))
    capture(context_ptr && ctx, F && f, A... a)
    {
        using namespace leaf_detail;
        return capture_impl(is_result_tag<decltype(std::declval<F>()(std::forward<A>(std::declval<A>())...))>(), std::move(ctx), std::forward<F>(f), std::forward<A>(a)...);
    }

    template <class Future>
    inline
    decltype(std::declval<Future>().get())
    future_get( Future & fut )
    {
        using namespace leaf_detail;
        return future_get_impl(is_result_tag<decltype(std::declval<Future>().get())>(), fut);
    }

    ////////////////////////////////////////

#ifndef BOOST_LEAF_NO_EXCEPTIONS

    template <class T>
    class result;

    namespace leaf_detail
    {
        inline error_id catch_exceptions_helper( std::exception const & ex, leaf_detail_mp11::mp_list<> )
        {
            return leaf::new_error(std::current_exception());
        }

        template <class Ex1, class... Ex>
        inline error_id catch_exceptions_helper( std::exception const & ex, leaf_detail_mp11::mp_list<Ex1,Ex...> )
        {
            if( Ex1 const * p = dynamic_cast<Ex1 const *>(&ex) )
                return catch_exceptions_helper(ex, leaf_detail_mp11::mp_list<Ex...>{ }).load(*p);
            else
                return catch_exceptions_helper(ex, leaf_detail_mp11::mp_list<Ex...>{ });
        }

        template <class T>
        struct deduce_exception_to_result_return_type_impl
        {
            using type = result<T>;
        };

        template <class T>
        struct deduce_exception_to_result_return_type_impl<result<T>>
        {
            using type = result<T>;
        };

        template <class T>
        using deduce_exception_to_result_return_type = typename deduce_exception_to_result_return_type_impl<T>::type;
    }

    template <class... Ex, class F>
    inline
    leaf_detail::deduce_exception_to_result_return_type<leaf_detail::fn_return_type<F>>
    exception_to_result( F && f ) noexcept
    {
        try
        {
            return std::forward<F>(f)();
        }
        catch( std::exception const & ex )
        {
            return leaf_detail::catch_exceptions_helper(ex, leaf_detail_mp11::mp_list<Ex...>());
        }
        catch(...)
        {
            return leaf::new_error(std::current_exception());
        }
    }

#endif

} }

#endif
