#pragma once

#include <util/generic/ptr.h>
#include <util/system/mutex.h>

namespace NPrivate {
    //
    // Heap object, which contains a mutex and a weak pointer
    //
    class THandleBase: public TAtomicRefCount<THandleBase> {
    public:
        using TDataPtr = TIntrusivePtr<THandleBase>;

    public:
        explicit THandleBase(void* object)
            : Owner_(object)
        {
        }

        virtual ~THandleBase() {
        }

        inline bool IsAlive() const noexcept {
            // may be called with or without lock
            return Owner_;
        }

        inline void Close() noexcept {
            TGuard<TMutex> g(Lock_);
            Owner_ = nullptr;
        }

        inline void Acquire() noexcept {
            Lock_.Acquire();
        }

        inline void Release() noexcept {
            Lock_.Release();
        }

        inline void* Ptr() const noexcept {
            return Owner_;
        }

    protected:
        mutable void* Owner_ = nullptr; // type erasure
        TMutex Lock_;
    };

    struct THandleBaseDisown {
        static inline void Destroy(THandleBase* handle) noexcept {
            if (handle) {
                TGuard<THandleBase> g(*handle);
                handle->Close(); // disowns
            }
        }
    };

    //
    // Strong unique ptr (owner's side)
    //
    class THandleOwnerBase {
    protected:
        THandleOwnerBase() noexcept = default;

        THandleOwnerBase(const THandleBase::TDataPtr& handle) noexcept
            : Handle(handle)
        {
            TryOwn();
        }

        void TryOwn() noexcept {
            if (!Handle)
                return;
            TGuard<THandleBase> g(*Handle);
            if (Handle->IsAlive())
                Holder = TStorage(Handle.Get());
            else
                Y_UNUSED(Handle.Release());
        }

        operator bool() const noexcept {
            return !!Handle;
        }

        inline void Reset() noexcept {
            Holder.Reset();
            Handle.Reset();
        }

    protected:
        using TStorage = THolder<THandleBase, NPrivate::THandleBaseDisown>;
        using TDataPtr = THandleBase::TDataPtr;

    protected:
        TDataPtr Handle;
        TStorage Holder;
    };

    template <class T>
    struct THandleUnlock {
        static inline void Destroy(T* handle) noexcept {
            if (handle) {
                handle->AsBase()->Release(); //unlocks
            }
        }
    };
}

//
// Handle (client side interface)
//
template <class T>
class THandle: public TPointerBase<THandle<T>, T> {
    using TBase = NPrivate::THandleBase;

public:
    using TLock = THolder<THandle<T>, NPrivate::THandleUnlock<THandle<T>>>;

public:
    inline THandle(const TBase::TDataPtr& data) noexcept
        : Handle(data)
    {
        Y_VERIFY(data);
    }

    inline THandle(T* obj = nullptr)
        : Handle(MakeIntrusive<TBase>(obj))
    {
    }

    inline T* Get() const noexcept {
        return reinterpret_cast<T*>(Handle->Ptr());
    }

    inline const TBase::TDataPtr& AsBase() const noexcept {
        return Handle;
    }

    inline TLock Lock() noexcept {
        Handle->Acquire();
        if (!Handle->IsAlive()) {
            Handle->Release();
            return TLock(nullptr);
        }
        return TLock(this);
    }

private:
    TBase::TDataPtr Handle;
};

//
// Handle (server side interface)
//
template <class T>
class THandleOwner final: public NPrivate::THandleOwnerBase {
    using TBase = NPrivate::THandleOwnerBase;

public:
    THandleOwner() noexcept = default;

    using TBase::operator bool;

    inline THandleOwner(const TBase::TDataPtr& data) noexcept
        : TBase(data)
    {
    }

    inline THandleOwner(const THandle<T>& handle) noexcept
        : TBase(handle.AsBase())
    {
    }

    inline THandle<T> AsHandle() const noexcept {
        return THandle<T>(Handle);
    }

    using TBase::Reset;
};

template <class T>
using THandleLock = typename THandle<T>::TLock;
