/*
 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see .
 */
#ifndef TRINITYCORE_UNIQUE_TRACKABLE_PTR_H
#define TRINITYCORE_UNIQUE_TRACKABLE_PTR_H
#include 
namespace Trinity
{
template 
class unique_trackable_ptr;
template 
class unique_weak_ptr;
template 
class unique_strong_ref_ptr;
/**
 * \brief Specialized variant of std::shared_ptr that enforces unique ownership and/or std::unique_ptr with std::weak_ptr capabilities
 * Implementation has the same overhead as a std::shared_ptr, that is, a separate allocation for control block that holds use counters
 * \tparam T Type of held object
 */
template 
class unique_trackable_ptr
{
public:
    using element_type = T;
    using pointer = T*;
    unique_trackable_ptr() : _ptr() { }
    explicit unique_trackable_ptr(pointer ptr)
        : _ptr(ptr) { }
    template , std::is_invocable>, int> = 0>
    explicit unique_trackable_ptr(pointer ptr, Deleter deleter)
        : _ptr(ptr, std::move(deleter)) { }
    unique_trackable_ptr(unique_trackable_ptr const&) = delete;
    unique_trackable_ptr(unique_trackable_ptr&& other) noexcept
        : _ptr(std::move(other._ptr)) { }
    template , int> = 0>
    unique_trackable_ptr(unique_trackable_ptr&& other) noexcept
        : _ptr(std::move(other)._ptr) { }
    unique_trackable_ptr& operator=(unique_trackable_ptr const&) = delete;
    unique_trackable_ptr& operator=(unique_trackable_ptr&& other) noexcept
    {
        _ptr = std::move(other._ptr);
        return *this;
    }
    template , int> = 0>
    unique_trackable_ptr& operator=(unique_trackable_ptr&& other) noexcept
    {
        _ptr = std::move(other)._ptr;
        return *this;
    }
    ~unique_trackable_ptr() = default;
    unique_trackable_ptr& operator=(std::nullptr_t)
    {
        reset();
        return *this;
    }
    void swap(unique_trackable_ptr& other) noexcept
    {
        using std::swap;
        swap(_ptr, other._ptr);
    }
    element_type& operator*() const
    {
        return *_ptr;
    }
    pointer operator->() const
    {
        return _ptr.operator->();
    }
    pointer get() const
    {
        return _ptr.get();
    }
    explicit operator bool() const
    {
        return static_cast(_ptr);
    }
    void reset()
    {
        _ptr.reset();
    }
    void reset(pointer ptr)
    {
        _ptr.reset(ptr);
    }
    template , std::is_invocable>, int> = 0>
    void reset(pointer ptr, Deleter deleter)
    {
        _ptr.reset(ptr, std::move(deleter));
    }
private:
    template 
    friend class unique_trackable_ptr;
    template 
    friend class unique_weak_ptr;
    template 
    friend std::enable_if_t, unique_trackable_ptr> make_unique_trackable(Args&&... args);
    template 
    friend std::enable_if_t, unique_trackable_ptr> make_unique_trackable(std::size_t N);
    template 
    friend std::enable_if_t, unique_trackable_ptr> make_unique_trackable(std::size_t N, std::remove_extent_t const& val);
    template 
    friend std::enable_if_t, unique_trackable_ptr> make_unique_trackable();
    template 
    friend std::enable_if_t, unique_trackable_ptr> make_unique_trackable(std::remove_extent_t const& val);
    std::shared_ptr _ptr;
};
/**
 * \brief Trinity::unique_trackable_ptr companion class, replicating what std::weak_ptr is to std::shared_ptr
 * \tparam T Type of held object
 */
template 
class unique_weak_ptr
{
public:
    using element_type = T;
    using pointer = T*;
    unique_weak_ptr() = default;
    unique_weak_ptr(unique_trackable_ptr const& trackable)
        : _ptr(trackable._ptr) { }
    unique_weak_ptr(unique_weak_ptr const& other) = default;
    template , int> = 0>
    unique_weak_ptr(unique_weak_ptr const& other) noexcept
        : _ptr(other._ptr) { }
    unique_weak_ptr(unique_weak_ptr&& other) noexcept = default;
    template , int> = 0>
    unique_weak_ptr(unique_weak_ptr&& other) noexcept
        : _ptr(std::move(other)._ptr) { }
    unique_weak_ptr& operator=(unique_trackable_ptr const& trackable)
    {
        _ptr = trackable._ptr;
        return *this;
    }
    unique_weak_ptr& operator=(unique_weak_ptr const& other) = default;
    template , int> = 0>
    unique_weak_ptr& operator=(unique_weak_ptr&& other)
    {
        _ptr = std::move(other)._ptr;
        return *this;
    }
    unique_weak_ptr& operator=(unique_weak_ptr&& other) noexcept = default;
    ~unique_weak_ptr() = default;
    void swap(unique_weak_ptr& other) noexcept
    {
        using std::swap;
        swap(_ptr, other._ptr);
    }
    bool expired() const
    {
        return _ptr.expired();
    }
    unique_strong_ref_ptr lock() const
    {
        return unique_strong_ref_ptr(_ptr.lock());
    }
private:
    template 
    friend class unique_weak_ptr;
    template 
    friend class unique_strong_ref_ptr;
    template 
    friend unique_weak_ptr static_pointer_cast(unique_weak_ptr const& other);
    template 
    friend unique_weak_ptr const_pointer_cast(unique_weak_ptr const& other);
    template 
    friend unique_weak_ptr reinterpret_pointer_cast(unique_weak_ptr const& other);
    template 
    friend unique_weak_ptr dynamic_pointer_cast(unique_weak_ptr const& other);
    std::weak_ptr _ptr;
};
/**
 * \brief Result of unique_weak_ptr::lock() function, this class holds a temporary strong reference to held object
 * to prevent it from being deallocated by another thread while it is being actively accessed.
 * This class is non-movable and non-copypable and is intended only for short lived local variables
 * \tparam T Type of held object
 */
template 
class unique_strong_ref_ptr
{
public:
    using element_type = T;
    using pointer = T*;
    unique_strong_ref_ptr(unique_strong_ref_ptr const&) = delete;
    unique_strong_ref_ptr(unique_strong_ref_ptr&&) = delete;
    unique_strong_ref_ptr& operator=(unique_strong_ref_ptr const&) = delete;
    unique_strong_ref_ptr& operator=(unique_strong_ref_ptr&&) = delete;
    ~unique_strong_ref_ptr() = default;
    element_type& operator*() const
    {
        return *_ptr;
    }
    pointer operator->() const
    {
        return _ptr.operator->();
    }
    pointer get() const
    {
        return _ptr.get();
    }
    explicit operator bool() const
    {
        return static_cast(_ptr);
    }
    operator unique_weak_ptr() const
    {
        unique_weak_ptr weak;
        weak._ptr = _ptr;
        return weak;
    }
private:
    template 
    friend class unique_weak_ptr;
    template 
    friend unique_strong_ref_ptr static_pointer_cast(unique_strong_ref_ptr const& other);
    template 
    friend unique_strong_ref_ptr static_pointer_cast(unique_strong_ref_ptr&& other);
    template 
    friend unique_strong_ref_ptr const_pointer_cast(unique_strong_ref_ptr const& other);
    template 
    friend unique_strong_ref_ptr const_pointer_cast(unique_strong_ref_ptr&& other);
    template 
    friend unique_strong_ref_ptr reinterpret_pointer_cast(unique_strong_ref_ptr const& other);
    template 
    friend unique_strong_ref_ptr reinterpret_pointer_cast(unique_strong_ref_ptr&& other);
    template 
    friend unique_strong_ref_ptr dynamic_pointer_cast(unique_strong_ref_ptr const& other);
    template 
    friend unique_strong_ref_ptr dynamic_pointer_cast(unique_strong_ref_ptr&& other);
    unique_strong_ref_ptr(std::shared_ptr ptr) : _ptr(std::move(ptr)) { }
    std::shared_ptr _ptr;
};
// unique_trackable_ptr funcions
template 
bool operator==(unique_trackable_ptr const& left, unique_trackable_ptr const& right)
{
    return left.get() == right.get();
}
template 
std::strong_ordering operator<=>(unique_trackable_ptr const& left, unique_trackable_ptr const& right)
{
    return left.get() <=> right.get();
}
template 
bool operator==(unique_trackable_ptr const& left, std::nullptr_t)
{
    return left.get() == nullptr;
}
template 
std::strong_ordering operator<=>(unique_trackable_ptr const& left, std::nullptr_t)
{
    return left.get() <=> nullptr;
}
template 
std::enable_if_t, unique_trackable_ptr> make_unique_trackable(Args&&... args)
{
    unique_trackable_ptr ptr;
    ptr._ptr = std::make_shared(std::forward(args)...);
    return ptr;
}
template 
std::enable_if_t, unique_trackable_ptr> make_unique_trackable(std::size_t N)
{
    unique_trackable_ptr ptr;
    ptr._ptr = std::make_shared(N);
    return ptr;
}
template 
std::enable_if_t, unique_trackable_ptr> make_unique_trackable(std::size_t N, std::remove_extent_t const& val)
{
    unique_trackable_ptr ptr;
    ptr._ptr = std::make_shared(N, val);
    return ptr;
}
template 
std::enable_if_t, unique_trackable_ptr> make_unique_trackable()
{
    unique_trackable_ptr ptr;
    ptr._ptr = std::make_shared();
    return ptr;
}
template 
std::enable_if_t, unique_trackable_ptr> make_unique_trackable(std::remove_extent_t const& val)
{
    unique_trackable_ptr ptr;
    ptr._ptr = std::make_shared(val);
    return ptr;
}
// unique_weak_ptr funcions
template 
unique_weak_ptr static_pointer_cast(unique_weak_ptr const& other)
{
    unique_weak_ptr to;
    to._ptr = std::static_pointer_cast(other._ptr.lock());
    return to;
}
template 
unique_weak_ptr const_pointer_cast(unique_weak_ptr const& other)
{
    unique_weak_ptr to;
    to._ptr = std::const_pointer_cast(other._ptr.lock());
    return to;
}
template 
unique_weak_ptr reinterpret_pointer_cast(unique_weak_ptr const& other)
{
    unique_weak_ptr to;
    to._ptr = std::reinterpret_pointer_cast(other._ptr.lock());
    return to;
}
template 
unique_weak_ptr dynamic_pointer_cast(unique_weak_ptr const& other)
{
    unique_weak_ptr to;
    to._ptr = std::dynamic_pointer_cast(other._ptr.lock());
    return to;
}
// unique_strong_ref_ptr funcions
template 
bool operator==(unique_strong_ref_ptr const& left, unique_strong_ref_ptr const& right)
{
    return left.get() == right.get();
}
template 
std::strong_ordering operator<=>(unique_strong_ref_ptr const& left, unique_strong_ref_ptr const& right)
{
    return left.get() <=> right.get();
}
template 
bool operator==(unique_strong_ref_ptr const& left, std::nullptr_t)
{
    return left.get() == nullptr;
}
template 
std::strong_ordering operator<=>(unique_strong_ref_ptr const& left, std::nullptr_t)
{
    return left.get() <=> nullptr;
}
template 
unique_strong_ref_ptr static_pointer_cast(unique_strong_ref_ptr const& other)
{
    return unique_strong_ref_ptr(std::static_pointer_cast(other._ptr));
}
template 
unique_strong_ref_ptr static_pointer_cast(unique_strong_ref_ptr&& other)
{
    return unique_strong_ref_ptr(std::static_pointer_cast(std::move(other._ptr)));
}
template 
unique_strong_ref_ptr const_pointer_cast(unique_strong_ref_ptr const& other)
{
    return unique_strong_ref_ptr(std::const_pointer_cast(other._ptr));
}
template 
unique_strong_ref_ptr const_pointer_cast(unique_strong_ref_ptr&& other)
{
    return unique_strong_ref_ptr(std::const_pointer_cast(std::move(other._ptr)));
}
template 
unique_strong_ref_ptr reinterpret_pointer_cast(unique_strong_ref_ptr const& other)
{
    return unique_strong_ref_ptr(std::reinterpret_pointer_cast(other._ptr));
}
template 
unique_strong_ref_ptr reinterpret_pointer_cast(unique_strong_ref_ptr&& other)
{
    return unique_strong_ref_ptr(std::reinterpret_pointer_cast(std::move(other._ptr)));
}
template 
unique_strong_ref_ptr dynamic_pointer_cast(unique_strong_ref_ptr const& other)
{
    return unique_strong_ref_ptr(std::dynamic_pointer_cast(other._ptr));
}
template 
unique_strong_ref_ptr dynamic_pointer_cast(unique_strong_ref_ptr&& other)
{
    return unique_strong_ref_ptr(std::dynamic_pointer_cast(std::move(other._ptr)));
}
}
#endif // TRINITYCORE_UNIQUE_TRACKABLE_PTR_H