#ifndef BOOST_LEAF_ERROR_HPP_INCLUDED
#define BOOST_LEAF_ERROR_HPP_INCLUDED

// Copyright 2018-2024 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/config.hpp>
#include <boost/leaf/detail/optional.hpp>
#include <boost/leaf/detail/function_traits.hpp>
#include <boost/leaf/detail/capture_list.hpp>

#if BOOST_LEAF_CFG_DIAGNOSTICS
#   include <ostream>
#endif

#if BOOST_LEAF_CFG_STD_SYSTEM_ERROR
#   include <system_error>
#endif

#define BOOST_LEAF_TOKEN_PASTE(x, y) x ## y
#define BOOST_LEAF_TOKEN_PASTE2(x, y) BOOST_LEAF_TOKEN_PASTE(x, y)
#define BOOST_LEAF_TMP BOOST_LEAF_TOKEN_PASTE2(boost_leaf_tmp_, __LINE__)

#define BOOST_LEAF_ASSIGN(v,r)\
    auto && BOOST_LEAF_TMP = r;\
    static_assert(::boost::leaf::is_result_type<typename std::decay<decltype(BOOST_LEAF_TMP)>::type>::value,\
        "BOOST_LEAF_ASSIGN/BOOST_LEAF_AUTO requires a result object as the second argument (see is_result_type)");\
    if( !BOOST_LEAF_TMP )\
        return BOOST_LEAF_TMP.error();\
    v = std::forward<decltype(BOOST_LEAF_TMP)>(BOOST_LEAF_TMP).value()

#define BOOST_LEAF_AUTO(v, r)\
    BOOST_LEAF_ASSIGN(auto v, r)

#if BOOST_LEAF_CFG_GNUC_STMTEXPR

#define BOOST_LEAF_CHECK(r)\
    ({\
        auto && BOOST_LEAF_TMP = (r);\
        static_assert(::boost::leaf::is_result_type<typename std::decay<decltype(BOOST_LEAF_TMP)>::type>::value,\
            "BOOST_LEAF_CHECK requires a result object (see is_result_type)");\
        if( !BOOST_LEAF_TMP )\
            return BOOST_LEAF_TMP.error();\
        std::move(BOOST_LEAF_TMP);\
    }).value()

#else

#define BOOST_LEAF_CHECK(r)\
    {\
        auto && BOOST_LEAF_TMP = (r);\
        static_assert(::boost::leaf::is_result_type<typename std::decay<decltype(BOOST_LEAF_TMP)>::type>::value,\
            "BOOST_LEAF_CHECK requires a result object (see is_result_type)");\
        if( !BOOST_LEAF_TMP )\
            return BOOST_LEAF_TMP.error();\
    }

#endif

#define BOOST_LEAF_NEW_ERROR ::boost::leaf::detail::inject_loc{__FILE__,__LINE__,__FUNCTION__}+::boost::leaf::new_error

namespace boost { namespace leaf {

struct BOOST_LEAF_SYMBOL_VISIBLE e_source_location
{
    char const * file;
    int line;
    char const * function;

    template <class CharT, class Traits>
    friend std::ostream & operator<<(std::basic_ostream<CharT, Traits> & os, e_source_location const & x)
    {
        return os << x.file << '(' << x.line << ") in function " << x.function;
    }
};

template <>
struct show_in_diagnostics<e_source_location>: std::false_type
{
};

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

class BOOST_LEAF_SYMBOL_VISIBLE error_id;

namespace detail
{
    class BOOST_LEAF_SYMBOL_VISIBLE exception_base
    {
    public:
        virtual error_id get_error_id() const noexcept = 0;
#if BOOST_LEAF_CFG_DIAGNOSTICS && !defined(BOOST_LEAF_NO_EXCEPTIONS)
        virtual void print_type_name(std::ostream &) const = 0;
#endif
    protected:
        exception_base() noexcept { }
        ~exception_base() noexcept { }
    };
}

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

namespace detail
{
    template <class E>
    class BOOST_LEAF_SYMBOL_VISIBLE slot:
        optional<E>
    {
        slot( slot const & ) = delete;
        slot & operator=( slot const & ) = delete;

        using impl = optional<E>;
        slot<E> * prev_;

    public:

        BOOST_LEAF_CONSTEXPR slot() noexcept:
            prev_(nullptr)
        {
        }

        BOOST_LEAF_CONSTEXPR slot( slot && x ) noexcept:
            optional<E>(std::move(x)),
            prev_(nullptr)
        {
            BOOST_LEAF_ASSERT(x.prev_ == nullptr);
        }

        ~slot() noexcept
        {
            BOOST_LEAF_ASSERT(tls::read_ptr<slot<E>>() != this);
        }

        void activate() noexcept
        {
            prev_ = tls::read_ptr<slot<E>>();
            tls::write_ptr<slot<E>>(this);
        }

        void deactivate() const noexcept
        {
            tls::write_ptr<slot<E>>(prev_);
        }

        void unload( int err_id ) noexcept(!BOOST_LEAF_CFG_CAPTURE);

        template <class CharT, class Traits, class ErrorID>
        void print(std::basic_ostream<CharT, Traits> & os, ErrorID to_print, char const * & prefix) const
        {
            if( int k = this->key() )
            {
                if( to_print && to_print.value() != k )
                    return;
                if( diagnostic<E>::print(os, prefix, BOOST_LEAF_CFG_DIAGNOSTICS_DELIMITER, value(k)) && !to_print )
                    os << '(' << k/4 << ')';
            }
        }

        using impl::load;
        using impl::has_value;
        using impl::has_value_any_key;
        using impl::value;
        using impl::value_or_default;
    };
}

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

#if BOOST_LEAF_CFG_CAPTURE

namespace detail
{
    class BOOST_LEAF_SYMBOL_VISIBLE dynamic_allocator:
        capture_list
    {
        dynamic_allocator( dynamic_allocator const & ) = delete;
        dynamic_allocator & operator=( dynamic_allocator const & ) = delete;

        class capturing_node:
            public capture_list::node
        {
        protected:
            BOOST_LEAF_CONSTEXPR explicit capturing_node( capture_list::node * * & last ) noexcept:
                node(last)
            {
                BOOST_LEAF_ASSERT(last == &next_);
                BOOST_LEAF_ASSERT(next_ == nullptr);
            }
        public:
            virtual void deactivate() const noexcept = 0;
        };

        template <class E>
        class capturing_slot_node:
            public capturing_node,
            public slot<E>
        {
            using impl = slot<E>;
            capturing_slot_node( capturing_slot_node const & ) = delete;
            capturing_slot_node & operator=( capturing_slot_node const & ) = delete;
            void deactivate() const noexcept final override
            {
                impl::deactivate();
            }
            void unload( int err_id ) final override
            {
                impl::unload(err_id);
            }
#if BOOST_LEAF_CFG_DIAGNOSTICS
            void print(std::ostream & os, error_id const & to_print, char const * & prefix) const final override
            {
                impl::print(os, to_print, prefix);
            }
#endif
        public:
            template <class T>
            BOOST_LEAF_CONSTEXPR capturing_slot_node( capture_list::node * * & last, int err_id, T && e ):
                capturing_node(last)
            {
                BOOST_LEAF_ASSERT(last == &next_);
                BOOST_LEAF_ASSERT(next_ == nullptr);
                impl::load(err_id, std::forward<T>(e));
            }
        };

#ifndef BOOST_LEAF_NO_EXCEPTIONS
        class capturing_exception_node:
            public capturing_node
        {
            capturing_exception_node( capturing_exception_node const & ) = delete;
            capturing_exception_node & operator=( capturing_exception_node const & ) = delete;
            void deactivate() const noexcept final override
            {
                BOOST_LEAF_ASSERT(0);
            }
            void unload( int ) final override
            {
                std::rethrow_exception(ex_);
            }
#if BOOST_LEAF_CFG_DIAGNOSTICS
            void print(std::ostream &, error_id const &, char const * &) const final override
            {
            }
#endif
            std::exception_ptr const ex_;
        public:
            capturing_exception_node( capture_list::node * * & last, std::exception_ptr && ex ) noexcept:
                capturing_node(last),
                ex_(std::move(ex))
            {
                BOOST_LEAF_ASSERT(last == &next_);
                BOOST_LEAF_ASSERT(ex_);
            }
        };
#endif

        node * * last_;

    public:

        dynamic_allocator() noexcept:
            capture_list(nullptr),
            last_(&first_)
        {
            BOOST_LEAF_ASSERT(first_ == nullptr);
        }

        dynamic_allocator( dynamic_allocator && other ) noexcept:
            capture_list(std::move(other)),
            last_(other.last_ == &other.first_? &first_ : other.last_)
        {
            BOOST_LEAF_ASSERT(last_ != nullptr);
            BOOST_LEAF_ASSERT(*last_ == nullptr);
            BOOST_LEAF_ASSERT(other.first_ == nullptr);
            other.last_ = &other.first_;
        }

        template <class E>
        typename std::decay<E>::type & dynamic_load(int err_id, E && e)
        {
            using T = typename std::decay<E>::type;
            BOOST_LEAF_ASSERT(last_ != nullptr);
            BOOST_LEAF_ASSERT(*last_ == nullptr);
            BOOST_LEAF_ASSERT(tls::read_ptr<slot<T>>() == nullptr);
            capturing_slot_node<T> * csn = new capturing_slot_node<T>(last_, err_id, std::forward<E>(e));
            csn->activate();
            return csn->value(err_id);
        }

        void deactivate() const noexcept
        {
            for_each(
                []( capture_list::node const & n )
                {
                    static_cast<capturing_node const &>(n).deactivate();
                } );
        }

        template <class LeafResult>
        LeafResult extract_capture_list(int err_id)
        {
#ifndef BOOST_LEAF_NO_EXCEPTIONS
            if( std::exception_ptr ex = std::current_exception() )
                (void) new capturing_exception_node(last_, std::move(ex));
#endif
            detail::capture_list::node * const f = first_;
            first_ = nullptr;
            last_ = &first_;
            return { err_id, capture_list(f) };
        }

        using capture_list::unload;
        using capture_list::print;
    };

    template <>
    inline void slot<dynamic_allocator>::deactivate() const noexcept
    {
        if( dynamic_allocator const * c = this->has_value_any_key() )
            c->deactivate();
        tls::write_ptr<slot<dynamic_allocator>>(prev_);
    }

    template <>
    inline void slot<dynamic_allocator>::unload( int err_id ) noexcept(false)
    {
        BOOST_LEAF_ASSERT(err_id);
        if( dynamic_allocator * da1 = this->has_value_any_key() )
            da1->unload(err_id);
    }

    template <class E>
    inline void dynamic_load_( int err_id, E && e )
    {
        if( slot<dynamic_allocator> * sl = tls::read_ptr<slot<dynamic_allocator>>() )
        {
            if( dynamic_allocator * c = sl->has_value_any_key() )
                c->dynamic_load(err_id, std::forward<E>(e));
            else
                sl->load(err_id).dynamic_load(err_id, std::forward<E>(e));
        }
    }

    template <class E, class F>
    inline void dynamic_accumulate_( int err_id, F && f )
    {
        if( slot<dynamic_allocator> * sl = tls::read_ptr<slot<dynamic_allocator>>() )
        {
            if( dynamic_allocator * c = sl->has_value(err_id) )
                (void) std::forward<F>(f)(c->dynamic_load(err_id, E{}));
            else
                (void) std::forward<F>(f)(sl->load(err_id).dynamic_load(err_id, E{}));
        }
    }

    template <bool OnError, class E>
    inline void dynamic_load( int err_id, E && e  ) noexcept(OnError)
    {
        if( OnError )
        {
#ifndef BOOST_LEAF_NO_EXCEPTIONS
            try
            {
#endif
                dynamic_load_(err_id, std::forward<E>(e));
#ifndef BOOST_LEAF_NO_EXCEPTIONS
            }
            catch(...)
            {
            }
#endif
        }
        else
            dynamic_load_(err_id, std::forward<E>(e));
    }

    template <bool OnError, class E, class F>
    inline void dynamic_load_accumulate( int err_id, F && f  ) noexcept(OnError)
    {
        if( OnError )
        {
#ifndef BOOST_LEAF_NO_EXCEPTIONS
            try
            {
#endif
                dynamic_accumulate_<E>(err_id, std::forward<F>(f));
#ifndef BOOST_LEAF_NO_EXCEPTIONS
            }
            catch(...)
            {
            }
#endif
        }
        else
            dynamic_accumulate_<E>(err_id, std::forward<F>(f));
    }
}

template <>
struct show_in_diagnostics<detail::dynamic_allocator>: std::false_type
{
};

#endif

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

namespace detail
{
    template <class E>
    inline void slot<E>::unload( int err_id ) noexcept(!BOOST_LEAF_CFG_CAPTURE)
    {
        BOOST_LEAF_ASSERT(err_id);
        if( this->key() != err_id )
            return;
        if( impl * p = tls::read_ptr<slot<E>>() )
        {
            if( !p->has_value(err_id) )
                *p = std::move(*this);
        }
#if BOOST_LEAF_CFG_CAPTURE
        else
            dynamic_load<false>(err_id, std::move(*this).value(err_id));
#endif
    }

    template <bool OnError, class E>
    BOOST_LEAF_CONSTEXPR inline int load_slot( int err_id, E && e ) noexcept(OnError)
    {
        using T = typename std::decay<E>::type;
        static_assert(!std::is_pointer<T>::value, "Error objects of pointer types are not allowed");
        static_assert(!std::is_same<T, error_id>::value, "Error objects of type error_id are not allowed");
        BOOST_LEAF_ASSERT((err_id&3) == 1);
        if( slot<T> * p = tls::read_ptr<slot<T>>() )
        {
            if( !OnError || !p->has_value(err_id) )
                (void) p->load(err_id, std::forward<E>(e));
        }
#if BOOST_LEAF_CFG_CAPTURE
        else
            dynamic_load<OnError>(err_id, std::forward<E>(e));
#endif        
        return 0;
    }

    template <bool OnError, class F>
    BOOST_LEAF_CONSTEXPR inline int load_slot_deferred( int err_id, F && f ) noexcept(OnError)
    {
        using E = typename function_traits<F>::return_type;
        using T = typename std::decay<E>::type;
        static_assert(!std::is_pointer<T>::value, "Error objects of pointer types are not allowed");
        static_assert(!std::is_same<T, error_id>::value, "Error objects of type error_id are not allowed");
        BOOST_LEAF_ASSERT((err_id&3) == 1);
        if( slot<T> * p = tls::read_ptr<slot<T>>() )
        {
            if( !OnError || !p->has_value(err_id) )
                (void) p->load(err_id, std::forward<F>(f)());
        }
#if BOOST_LEAF_CFG_CAPTURE
        else
            dynamic_load<OnError>(err_id, std::forward<F>(f)());
#endif        
        return 0;
    }

    template <bool OnError, class F>
    BOOST_LEAF_CONSTEXPR inline int load_slot_accumulate( int err_id, F && f ) noexcept(OnError)
    {
        static_assert(function_traits<F>::arity == 1, "Lambdas passed to accumulate must take a single e-type argument by reference");
        using E = typename std::decay<fn_arg_type<F,0>>::type;
        using T = typename std::decay<E>::type;
        static_assert(!std::is_pointer<T>::value, "Error objects of pointer types are not allowed");
        BOOST_LEAF_ASSERT((err_id&3) == 1);
        if( auto sl = tls::read_ptr<slot<E>>() )
        {
            if( auto v = sl->has_value(err_id) )
                (void) std::forward<F>(f)(*v);
            else
                (void) std::forward<F>(f)(sl->load(err_id,E()));
        }
#if BOOST_LEAF_CFG_CAPTURE
        else
            dynamic_load_accumulate<OnError, E>(err_id, std::forward<F>(f));
#endif
        return 0;
    }
}

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

namespace detail
{
    template <class T, int Arity = function_traits<T>::arity>
    struct load_item
    {
        static_assert(Arity == 0 || Arity == 1, "If a functions is passed to new_error or load, it must take zero or one argument");
    };

    template <class E>
    struct load_item<E, -1>
    {
        BOOST_LEAF_CONSTEXPR static int load_( int err_id, E && e ) noexcept
        {
            return load_slot<false>(err_id, std::forward<E>(e));
        }
    };

    template <class F>
    struct load_item<F, 0>
    {
        BOOST_LEAF_CONSTEXPR static int load_( int err_id, F && f ) noexcept
        {
            return load_slot_deferred<false>(err_id, std::forward<F>(f));
        }
    };

    template <class F>
    struct load_item<F, 1>
    {
        BOOST_LEAF_CONSTEXPR static int load_( int err_id, F && f ) noexcept
        {
            return load_slot_accumulate<false>(err_id, std::forward<F>(f));
        }
    };
}

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

namespace detail
{
    struct BOOST_LEAF_SYMBOL_VISIBLE tls_tag_id_factory_current_id;

    template <class=void>
    struct BOOST_LEAF_SYMBOL_VISIBLE id_factory
    {
        static atomic_unsigned_int counter;

        BOOST_LEAF_CONSTEXPR static unsigned generate_next_id() noexcept
        {
            auto id = (counter+=4);
            BOOST_LEAF_ASSERT((id&3) == 1);
            return id;
        }
    };

    template <class T>
    atomic_unsigned_int id_factory<T>::counter(1);

    inline int current_id() noexcept
    {
        unsigned id = tls::read_uint<tls_tag_id_factory_current_id>();
        BOOST_LEAF_ASSERT(id == 0 || (id&3) == 1);
        return int(id);
    }

    inline int new_id() noexcept
    {
        unsigned id = id_factory<>::generate_next_id();
        tls::write_uint<tls_tag_id_factory_current_id>(id);
        return int(id);
    }

    struct inject_loc
    {
        char const * file;
        int line;
        char const * fn;

        template <class T>
        friend T operator+( inject_loc loc, T && x ) noexcept
        {
            x.load_source_location_(loc.file, loc.line, loc.fn);
            return std::move(x);
        }
    };
}

#if BOOST_LEAF_CFG_STD_SYSTEM_ERROR

namespace detail
{
    class leaf_error_category final: public std::error_category
    {
        bool equivalent( int,  std::error_condition const & ) const noexcept final override { return false; }
        bool equivalent( std::error_code const &, int ) const noexcept final override { return false; }
        char const * name() const noexcept final override { return "LEAF error"; }
        std::string message( int ) const final override { return name(); }
    public:
        ~leaf_error_category() noexcept final override { }
    };

    template <class=void>
    struct get_leaf_error_category
    {
        static leaf_error_category cat;
    };

    template <class T>
    leaf_error_category get_leaf_error_category<T>::cat;

    inline int import_error_code( std::error_code const & ec ) noexcept
    {
        if( int err_id = ec.value() )
        {
            std::error_category const & cat = get_leaf_error_category<>::cat;
            if( &ec.category() == &cat )
            {
                BOOST_LEAF_ASSERT((err_id&3) == 1);
                return (err_id&~3)|1;
            }
            else
            {
                err_id = new_id();
                (void) load_slot<false>(err_id, ec);
                return (err_id&~3)|1;
            }
        }
        else
            return 0;
    }
}

inline bool is_error_id( std::error_code const & ec ) noexcept
{
    bool res = (&ec.category() == &detail::get_leaf_error_category<>::cat);
    BOOST_LEAF_ASSERT(!res || !ec.value() || ((ec.value()&3) == 1));
    return res;
}

#endif

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

namespace detail
{
    BOOST_LEAF_CONSTEXPR error_id make_error_id(int) noexcept;
}

class BOOST_LEAF_SYMBOL_VISIBLE error_id
{
    friend error_id BOOST_LEAF_CONSTEXPR detail::make_error_id(int) noexcept;

    int value_;

    BOOST_LEAF_CONSTEXPR explicit error_id( int value ) noexcept:
        value_(value)
    {
        BOOST_LEAF_ASSERT(value_ == 0 || ((value_&3) == 1));
    }

public:

    BOOST_LEAF_CONSTEXPR error_id() noexcept:
        value_(0)
    {
    }

#if BOOST_LEAF_CFG_STD_SYSTEM_ERROR
    explicit error_id( std::error_code const & ec ) noexcept:
        value_(detail::import_error_code(std::error_code(ec)))
    {
        BOOST_LEAF_ASSERT(!value_ || ((value_&3) == 1));
    }

    template <class Enum>
    error_id( Enum e, typename std::enable_if<std::is_error_code_enum<Enum>::value, int>::type = 0 ) noexcept:
        value_(detail::import_error_code(e))
    {
    }

    template <class T, typename std::enable_if<std::is_constructible<T, std::error_code>::value, int>::type = 0>
    operator T() const noexcept
    {
        return std::error_code(value_, detail::get_leaf_error_category<>::cat);
    }
#endif

    BOOST_LEAF_CONSTEXPR error_id load() const noexcept
    {
        return *this;
    }

    template <class Item>
    BOOST_LEAF_CONSTEXPR error_id load(Item && item) const noexcept
    {
        if (int err_id = value())
        {
            int const unused[] = { 42, detail::load_item<Item>::load_(err_id, std::forward<Item>(item)) };
            (void)unused;
        }
        return *this;
    }

    template <class... Item>
    BOOST_LEAF_CONSTEXPR error_id load( Item && ... item ) const noexcept
    {
        if( int err_id = value() )
        {
            int const unused[] = { 42, detail::load_item<Item>::load_(err_id, std::forward<Item>(item))... };
            (void) unused;
        }
        return *this;
    }

    BOOST_LEAF_CONSTEXPR int value() const noexcept
    {
        BOOST_LEAF_ASSERT(value_ == 0 || ((value_&3) == 1));
        return value_;
    }

    BOOST_LEAF_CONSTEXPR explicit operator bool() const noexcept
    {
        return value_ != 0;
    }

    BOOST_LEAF_CONSTEXPR friend bool operator==( error_id a, error_id b ) noexcept
    {
        return a.value_ == b.value_;
    }

    BOOST_LEAF_CONSTEXPR friend bool operator!=( error_id a, error_id b ) noexcept
    {
        return !(a == b);
    }

    BOOST_LEAF_CONSTEXPR friend bool operator<( error_id a, error_id b ) noexcept
    {
        return a.value_ < b.value_;
    }

    template <class CharT, class Traits>
    friend std::ostream & operator<<( std::basic_ostream<CharT, Traits> & os, error_id x )
    {
        return os << (x.value_ / 4);
    }

    BOOST_LEAF_CONSTEXPR void load_source_location_( char const * file, int line, char const * function ) const noexcept
    {
        BOOST_LEAF_ASSERT(file&&*file);
        BOOST_LEAF_ASSERT(line>0);
        BOOST_LEAF_ASSERT(function&&*function);
        BOOST_LEAF_ASSERT(value_);
        (void) load(e_source_location {file,line,function});
    }
};

namespace detail
{
    BOOST_LEAF_CONSTEXPR inline error_id make_error_id( int err_id ) noexcept
    {
        BOOST_LEAF_ASSERT(err_id == 0 || (err_id&3) == 1);
        return error_id((err_id&~3)|1);
    }
}

inline error_id new_error() noexcept
{
    return detail::make_error_id(detail::new_id());
}

template <class... Item>
inline error_id new_error( Item && ... item ) noexcept
{
    return detail::make_error_id(detail::new_id()).load(std::forward<Item>(item)...);
}

inline error_id current_error() noexcept
{
    return detail::make_error_id(detail::current_id());
}

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

template <class R>
struct is_result_type: std::false_type
{
};

template <class R>
struct is_result_type<R const>: is_result_type<R>
{
};

} }

#endif // BOOST_LEAF_ERROR_HPP_INCLUDED
