#ifndef ATOMIC_INTRUSIVE_PTR_INL_H_
#error "Direct inclusion of this file is not allowed, include atomic_intrusive_ptr.h"
// For the sake of sane code completion.
#include "atomic_intrusive_ptr.h"
#endif
#undef ATOMIC_INTRUSIVE_PTR_INL_H_

#include <util/system/spinlock.h>

namespace NYT {

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

template <class T>
TAtomicIntrusivePtr<T>::TAtomicIntrusivePtr(std::nullptr_t)
{ }

template <class T>
TAtomicIntrusivePtr<T>::TAtomicIntrusivePtr(TIntrusivePtr<T> other)
    : Ptr_(AcquireObject(other.Release(), true))
{ }

template <class T>
TAtomicIntrusivePtr<T>::TAtomicIntrusivePtr(TAtomicIntrusivePtr&& other)
    : Ptr_(other.Ptr_.load(std::memory_order::relaxed))
{
    other.Ptr_.store(nullptr, std::memory_order::relaxed);
}

template <class T>
TAtomicIntrusivePtr<T>::~TAtomicIntrusivePtr()
{
    ReleaseObject(Ptr_.load());
}

template <class T>
TAtomicIntrusivePtr<T>& TAtomicIntrusivePtr<T>::operator=(TIntrusivePtr<T> other)
{
    Store(std::move(other));
    return *this;
}

template <class T>
TAtomicIntrusivePtr<T>& TAtomicIntrusivePtr<T>::operator=(std::nullptr_t)
{
    Reset();
    return *this;
}

template <class T>
TIntrusivePtr<T> TAtomicIntrusivePtr<T>::Acquire() const
{
    auto ptr = Ptr_.load();
    while (true) {
        auto [obj, localRefs] = TTaggedPtr<T>::Unpack(ptr);

        if (!obj) {
            return {};
        }

        YT_VERIFY(localRefs < ReservedRefCount);

        auto newLocalRefs = localRefs + 1;

        if (newLocalRefs == ReservedRefCount) {
            SpinLockPause();

            ptr = Ptr_.load();
            continue;
        }

        // Can not Ref(obj) here because it can be destroyed.
        if (Ptr_.compare_exchange_weak(ptr, TTaggedPtr(obj, newLocalRefs).Pack())) {
            if (Y_UNLIKELY(newLocalRefs > ReservedRefCount / 2)) {
                Ref(obj, ReservedRefCount / 2);

                // Decrease local ref count.
                while (true) {
                    auto [currentObj, localRefs] = TTaggedPtr<T>::Unpack(ptr);

                    if (currentObj != obj || localRefs <= ReservedRefCount / 2) {
                        Unref(obj, ReservedRefCount / 2);
                        break;
                    }

                    if (Ptr_.compare_exchange_weak(ptr, TTaggedPtr(obj, localRefs - ReservedRefCount / 2).Pack())) {
                        break;
                    }
                }
            }

            return TIntrusivePtr<T>(obj, false);
        }
    }
}

template <class T>
TIntrusivePtr<T> TAtomicIntrusivePtr<T>::Exchange(TIntrusivePtr<T> other)
{
    auto [obj, localRefs] = TTaggedPtr<T>::Unpack(Ptr_.exchange(AcquireObject(other.Release(), true)));
    DoRelease(obj, localRefs + 1);
    return TIntrusivePtr<T>(obj, false);
}

template <class T>
void TAtomicIntrusivePtr<T>::Store(TIntrusivePtr<T> other)
{
    ReleaseObject(Ptr_.exchange(AcquireObject(other.Release(), true)));
}

template <class T>
void TAtomicIntrusivePtr<T>::Reset()
{
    ReleaseObject(Ptr_.exchange(0));
}

template <class T>
bool TAtomicIntrusivePtr<T>::CompareAndSwap(TRawPtr& comparePtr, T* target)
{
    auto* targetPtr = AcquireObject(target, false);

    auto currentPtr = Ptr_.load();
    if (UnpackPointer<T>(currentPtr).Ptr == comparePtr && Ptr_.compare_exchange_strong(currentPtr, targetPtr)) {
        ReleaseObject(currentPtr);
        return true;
    }

    comparePtr = UnpackPointer<T>(currentPtr).Ptr;

    ReleaseObject(targetPtr);
    return false;
}

template <class T>
bool TAtomicIntrusivePtr<T>::CompareAndSwap(TRawPtr& comparePtr, TIntrusivePtr<T> target)
{
    // TODO(lukyan): Make helper for packed owning ptr?
    auto targetPtr = AcquireObject(target.Release(), true);

    auto currentPtr = Ptr_.load();
    if (TTaggedPtr<T>::Unpack(currentPtr).Ptr == comparePtr && Ptr_.compare_exchange_strong(currentPtr, targetPtr)) {
        ReleaseObject(currentPtr);
        return true;
    }

    comparePtr = TTaggedPtr<T>::Unpack(currentPtr).Ptr;

    ReleaseObject(targetPtr);
    return false;
}

template <class T>
typename TAtomicIntrusivePtr<T>::TRawPtr TAtomicIntrusivePtr<T>::Get() const
{
    return TTaggedPtr<void>::Unpack(Ptr_.load()).Ptr;
}

template <class T>
TAtomicIntrusivePtr<T>::operator bool() const
{
    return Get();
}

template <class T>
TPackedPtr TAtomicIntrusivePtr<T>::AcquireObject(T* obj, bool consumeRef)
{
    if (obj) {
        Ref(obj, static_cast<int>(ReservedRefCount - consumeRef));
    }

    return TTaggedPtr(obj).Pack();
}

template <class T>
void TAtomicIntrusivePtr<T>::ReleaseObject(TPackedPtr packedPtr)
{
    auto [obj, localRefs] = TTaggedPtr<T>::Unpack(packedPtr);
    DoRelease(obj, localRefs);
}

template <class T>
void TAtomicIntrusivePtr<T>::DoRelease(T* obj, int refs)
{
    if (obj) {
        Unref(obj, static_cast<int>(ReservedRefCount - refs));
    }
}

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

template <class T>
bool operator==(const TAtomicIntrusivePtr<T>& lhs, const TIntrusivePtr<T>& rhs)
{
    return lhs.Get() == rhs.Get();
}

template <class T>
bool operator==(const TIntrusivePtr<T>& lhs, const TAtomicIntrusivePtr<T>& rhs)
{
    return lhs.Get() == rhs.Get();
}

template <class T>
bool operator!=(const TAtomicIntrusivePtr<T>& lhs, const TIntrusivePtr<T>& rhs)
{
    return lhs.Get() != rhs.Get();
}

template <class T>
bool operator!=(const TIntrusivePtr<T>& lhs, const TAtomicIntrusivePtr<T>& rhs)
{
    return lhs.Get() != rhs.Get();
}

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

} // namespace NYT