#pragma once
#include <util/generic/ptr.h>
#include <util/system/mutex.h>
#include <util/system/guard.h>
#include <util/generic/vector.h>
#include <library/cpp/logger/global/global.h>

template <class TObject>
class TObjectsPool: TNonCopyable {
public:
    class TObjectFromPool;

private:
    TVector<TAtomicSharedPtr<TObject>> Objects;
    TMutex Mutex;

private:
    using TPtr = TAtomicSharedPtr<TObject>;

    struct TRestoreObjectInPool {
        static inline void Destroy(TObjectFromPool* t) noexcept;
        static inline void Destroy(void* t) noexcept;
    };

    class TObjectContainer {
    private:
        TPtr Object;
        TObjectsPool<TObject>* Parent;
    public:
        TObjectContainer(TPtr object, TObjectsPool<TObject>* parent)
            : Object(object)
            , Parent(parent)
        {
        }

        ~TObjectContainer() {
            if (Object && Parent) {
                TGuard<TMutex> g(Parent->Mutex);
                for (ui32 i = 0; i < Parent->Objects.size(); ++i) {
                    CHECK_WITH_LOG(Parent->Objects[i].Get() != Object.Get());
                }
                Parent->Objects.push_back(Object);
            }
        }

        TObject* GetObject() {
            return Object.Get();
        }

        const TObject* GetObject() const {
            return Object.Get();
        }

        TPtr GetPtr() {
            return Object;
        }

        const TPtr GetPtr() const {
            return Object;
        }

        void Destroy() {
            Object = nullptr;
        }
    };

public:
    class TObjectGuard: public TAtomicSharedPtr<TObjectContainer> {
    private:
        using TBase = TAtomicSharedPtr<TObjectContainer>;

    public:
        TObjectGuard(TPtr object, TObjectsPool<TObject>* parent)
            : TBase(new TObjectContainer(object, parent))
        {
        }

        TPtr operator*() {
            return TBase::Get()->GetPtr();
        }

        const TPtr operator*() const {
            return TBase::Get()->GetPtr();
        }

        bool operator!() const {
            return !TBase::Get()->GetPtr();
        }

        TObject* operator->() {
            return TBase::Get()->GetObject();
        }

        const TObject* operator->() const {
            return Get()->GetObject();
        }

        void Destroy() {
            TBase::Get()->Destroy();
        }
    };

    template <class T, class... TArgs>
    TObjectGuard Get(TArgs&... args) {
        TObjectGuard result = Get();
        if (!result) {
            result = Add(new T(args...));
        };
        return result;
    }

    template <class T, class... TArgs>
    TObjectGuard Get(TArgs*... args) {
        TObjectGuard result = Get();
        if (!result) {
            result = Add(new T(args...));
        };
        return result;
    }

    TObjectGuard Get() {
        TGuard<TMutex> g(Mutex);
        if (!Objects.size()) {
            return TObjectGuard(nullptr, this);
        }
        auto result = Objects.back();
        Objects.pop_back();
        return TObjectGuard(result, this);
    }

    TObjectGuard Add(TPtr object) {
        return TObjectGuard(object, this);
    }

    void Clear() {
        TGuard<TMutex> g(Mutex);
        Objects.clear();
    }
};
