//
// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
//
// 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)
//
// Official repository: https://github.com/boostorg/json
//

#ifndef BOOST_JSON_STORAGE_PTR_HPP
#define BOOST_JSON_STORAGE_PTR_HPP

#include <boost/container/pmr/polymorphic_allocator.hpp>
#include <boost/json/detail/config.hpp>
#include <boost/json/detail/shared_resource.hpp>
#include <boost/json/detail/default_resource.hpp>
#include <boost/json/is_deallocate_trivial.hpp>
#include <new>
#include <type_traits>
#include <utility>

namespace boost {
namespace json {

/** A smart pointer to a memory resource.

    This container is used to hold a pointer to a memory resource. The
    pointed-to resource is always valid. Depending on the means of
    construction, the ownership will be either:

    @li Non-owning, when constructing from a raw pointer to
    `boost::container::pmr::memory_resource` or from a
    `boost::container::pmr::polymorphic_allocator`. In this case the caller is
    responsible for ensuring that the lifetime of the memory resource extends
    until there are no more calls to allocate or deallocate.

    @li Owning, when constructing using the function
    @ref make_shared_resource. In this case
    ownership is shared; the lifetime of the memory
    resource extends until the last copy of the
    @ref storage_ptr is destroyed.

    @par Examples

    These statements create a memory resource on the
    stack and construct a pointer from it without
    taking ownership:
    @code
    monotonic_resource mr;                  // Create our memory resource on the stack
    storage_ptr sp( &mr );                  // Construct a non-owning pointer to the resource
    @endcode

    This function creates a pointer to a memory
    resource using shared ownership and returns it.
    The lifetime of the memory resource extends until
    the last copy of the pointer is destroyed:
    @code
    // Create a counted memory resource and return it
    storage_ptr make_storage()
    {
        return make_shared_resource< monotonic_resource >();
    }
    @endcode

    @par Thread Safety

    Instances of this type provide the default level of
    thread safety for all C++ objects. Specifically, it
    conforms to
    <a href="http://eel.is/c++draft/res.on.data.races">
        16.4.6.10 Data race avoidance</a>.

    @see
        @ref make_shared_resource,
        @ref boost::container::pmr::polymorphic_allocator,
        @ref boost::container::pmr::memory_resource.

*/
class storage_ptr
{
#ifndef BOOST_JSON_DOCS
    // VFALCO doc toolchain shows this when it shouldn't
    friend struct detail::shared_resource;
#endif
    using shared_resource =
        detail::shared_resource;

    using default_resource =
        detail::default_resource;

    std::uintptr_t i_;

    shared_resource*
    get_shared() const noexcept
    {
        return static_cast<shared_resource*>(
            reinterpret_cast<container::pmr::memory_resource*>(
                i_ & ~3));
    }

    void
    addref() const noexcept
    {
        if(is_shared())
            get_shared()->refs.fetch_add(
                1, std::memory_order_relaxed);
    }

    void
    release() const noexcept
    {
        if(is_shared())
        {
            auto const p = get_shared();
            if(p->refs.fetch_sub(1,
                    std::memory_order_acq_rel) == 1)
                delete p;
        }
    }

    template<class T>
    storage_ptr(
        detail::shared_resource_impl<T>* p) noexcept
        : i_(reinterpret_cast<std::uintptr_t>(
                static_cast<container::pmr::memory_resource*>(p)) + 1 +
            (json::is_deallocate_trivial<T>::value ? 2 : 0))
    {
        BOOST_ASSERT(p);
    }

public:
    /** Destructor

        If the pointer has shared ownership of the
        resource, the shared ownership is released.
        If this is the last owned copy, the memory
        resource is destroyed.

        @par Complexity
        Constant.

        @par Exception Safety
        No-throw guarantee.
    */
    ~storage_ptr() noexcept
    {
        release();
    }

    /** Constructor

        This constructs a non-owning pointer that refers
        to the [default memory resource].

        @par Complexity
        Constant.

        @par Exception Safety
        No-throw guarantee.

        [default memory resource]: json/allocators/storage_ptr.html#json.allocators.storage_ptr.default_memory_resource
    */
    storage_ptr() noexcept
        : i_(0)
    {
    }

    /** Constructor

        This constructs a non-owning pointer that points to the memory resource
        `r`. The caller is responsible for maintaining the lifetime of the
        pointed-to `boost::container::pmr::memory_resource`.

        @par Constraints
        @code
        std::is_convertible< T*, boost::container::pmr::memory_resource* >::value == true
        @endcode

        @par Preconditions
        @code
        r != nullptr
        @endcode

        @par Exception Safety
        No-throw guarantee.

        @param r A pointer to the memory resource to use.
        This may not be null.
    */
    template<class T
#ifndef BOOST_JSON_DOCS
        , class = typename std::enable_if<
            std::is_convertible<T*,
                container::pmr::memory_resource*>::value>::type
#endif
    >
    storage_ptr(T* r) noexcept
        : i_(reinterpret_cast<std::uintptr_t>(
                static_cast<container::pmr::memory_resource *>(r)) +
            (json::is_deallocate_trivial<T>::value ? 2 : 0))
    {
        BOOST_ASSERT(r);
    }

    /** Constructor

        This constructs a non-owning pointer that points to the same memory
        resource as `alloc`, obtained by calling `alloc.resource()`. The caller
        is responsible for maintaining the lifetime of the
        pointed-to `boost::container::pmr::memory_resource`.

        @par Constraints
        @code
        std::is_convertible< T*, boost::container::pmr::memory_resource* >::value == true
        @endcode

        @par Exception Safety
        No-throw guarantee.

        @param alloc A `boost::container::pmr::polymorphic_allocator` to
        construct from.
    */
    template<class T>
    storage_ptr(
        container::pmr::polymorphic_allocator<T> const& alloc) noexcept
        : i_(reinterpret_cast<std::uintptr_t>(
            alloc.resource()))
    {
    }

    /** Move constructor

        This function constructs a pointer that
        points to the same memory resource as `other`,
        with the same ownership:

        @li If `other` is non-owning, then `*this`
        will be be non-owning.

        @li If `other` has shared ownership, then
        ownership will be transferred to `*this`.

        After construction, `other` will point
        to the [default memory resource].

        @par Complexity
        Constant.

        @par Exception Safety
        No-throw guarantee.

        @param other The pointer to construct from.

        [default memory resource]: json/allocators/storage_ptr.html#json.allocators.storage_ptr.default_memory_resource
    */
    storage_ptr(
        storage_ptr&& other) noexcept
        : i_(detail::exchange(other.i_, 0))
    {
    }

    /** Copy constructor

        This function constructs a pointer that
        points to the same memory resource as `other`,
        with the same ownership:

        @li If `other` is non-owning, then `*this`
        will be be non-owning.

        @li If `other` has shared ownership, then
        `*this` will acquire shared ownership.

        @par Complexity
        Constant.

        @par Exception Safety
        No-throw guarantee.

        @param other The pointer to construct from.
    */
    storage_ptr(
        storage_ptr const& other) noexcept
        : i_(other.i_)
    {
        addref();
    }

    /** Move assignment

        This function assigns a pointer that
        points to the same memory resource as `other`,
        with the same ownership:

        @li If `other` is non-owning, then `*this`
        will be be non-owning.

        @li If `other` has shared ownership, then
        ownership will be transferred to `*this`.

        After assignment, `other` will point
        to the [default memory resource].
        If `*this` previously had shared ownership,
        it is released before the function returns.

        @par Complexity
        Constant.

        @par Exception Safety
        No-throw guarantee.

        @param other The storage pointer to move.

        [default memory resource]: json/allocators/storage_ptr.html#json.allocators.storage_ptr.default_memory_resource
    */
    storage_ptr&
    operator=(
        storage_ptr&& other) noexcept
    {
        release();
        i_ = detail::exchange(other.i_, 0);
        return *this;
    }

    /** Copy assignment.

        This function assigns a pointer that
        points to the same memory resource as `other`,
        with the same ownership:

        @li If `other` is non-owning, then `*this`
        will be be non-owning.

        @li If `other` has shared ownership, then
        `*this` will acquire shared ownership.

        If `*this` previously had shared ownership,
        it is released before the function returns.

        @par Complexity
        Constant.

        @par Exception Safety
        No-throw guarantee.

        @param other The storage pointer to copy.
    */
    storage_ptr&
    operator=(
        storage_ptr const& other) noexcept
    {
        other.addref();
        release();
        i_ = other.i_;
        return *this;
    }

    /** Return `true` if ownership of the memory resource is shared.

        This function returns true for memory resources
        created using @ref make_shared_resource.
    */
    bool
    is_shared() const noexcept
    {
        return (i_ & 1) != 0;
    }

    /** Return `true` if calling `deallocate` on the memory resource has no effect.

        This function is used to determine if the deallocate
        function of the pointed to memory resource is trivial.
        The value of @ref is_deallocate_trivial is evaluated
        and saved when the memory resource is constructed
        and the type is known, before the type is erased.
    */
    bool
    is_deallocate_trivial() const noexcept
    {
        return (i_ & 2) != 0;
    }

    /** Return `true` if ownership of the memory resource is not shared and deallocate is trivial.

        This function is used to determine if calls to deallocate
        can effectively be skipped.

        @par Effects
        Returns `! this->is_shared() && this->is_deallocate_trivial()`
    */
    bool
    is_not_shared_and_deallocate_is_trivial() const noexcept
    {
        return (i_ & 3) == 2;
    }

    /** Return a pointer to the memory resource.

        This function returns a pointer to the
        referenced `boost::container::pmr::memory_resource`.

        @par Complexity
        Constant.

        @par Exception Safety
        No-throw guarantee.
    */
    container::pmr::memory_resource*
    get() const noexcept
    {
        if(i_ != 0)
            return reinterpret_cast<
                container::pmr::memory_resource*>(i_ & ~3);
        return default_resource::get();
    }

    /** Return a pointer to the memory resource.

        This function returns a pointer to the
        referenced `boost::container::pmr::memory_resource`.

        @par Complexity
        Constant.

        @par Exception Safety
        No-throw guarantee.
    */
    container::pmr::memory_resource*
    operator->() const noexcept
    {
        return get();
    }

    /** Return a reference to the memory resource.

        This function returns a reference to the
        pointed-to `boost::container::pmr::memory_resource`.

        @par Complexity

        Constant.

        @par Exception Safety

        No-throw guarantee.
    */
    container::pmr::memory_resource&
    operator*() const noexcept
    {
        return *get();
    }

    template<class U, class... Args>
    friend
    storage_ptr
    make_shared_resource(Args&&... args);
};

#if defined(_MSC_VER)
# pragma warning( push )
# if !defined(__clang__) && _MSC_VER <= 1900
#  pragma warning( disable : 4702 )
# endif
#endif
/** Return shared ownership of a new, dynamically allocated memory resource.

    This function dynamically allocates a new memory resource
    as if by `operator new` that uses shared ownership. The
    lifetime of the memory resource will be extended until
    the last @ref storage_ptr which points to it is destroyed.

    @par Mandates
    @code
    std::is_base_of< boost::container::pmr::memory_resource, U >::value == true
    @endcode

    @par Complexity
    Same as `new U( std::forward<Args>(args)... )`.

    @par Exception Safety
    Strong guarantee.

    @tparam U The type of memory resource to create.

    @param args Parameters forwarded to the constructor of `U`.
*/
template<class U, class... Args>
storage_ptr
make_shared_resource(Args&&... args)
{
    // If this generates an error, it means that
    // `T` is not a memory resource.
    BOOST_STATIC_ASSERT(
        std::is_base_of<
            container::pmr::memory_resource, U>::value);
    return storage_ptr(new
        detail::shared_resource_impl<U>(
            std::forward<Args>(args)...));
}
#if defined(_MSC_VER)
# pragma warning( pop )
#endif

/** Return true if two storage pointers point to the same memory resource.

    This function returns `true` if the
    `boost::container::pmr::memory_resource` objects pointed to by `lhs` and
    `rhs` have the same address.
*/
inline
bool
operator==(
    storage_ptr const& lhs,
    storage_ptr const& rhs) noexcept
{
    return lhs.get() == rhs.get();
}

/** Return true if two storage pointers point to different memory resources.

    This function returns `true` if the
    `boost::container::pmr::memory_resource` objects pointed to by `lhs` and
    `rhs` have different addresses.
*/
inline
bool
operator!=(
    storage_ptr const& lhs,
    storage_ptr const& rhs) noexcept
{
    return lhs.get() != rhs.get();
}

} // namespace json
} // namespace boost

#endif
