#ifndef Py_INTERNAL_FREELIST_H
#define Py_INTERNAL_FREELIST_H
#ifdef __cplusplus
extern "C" {
#endif

#ifndef Py_BUILD_CORE
#  error "this header requires Py_BUILD_CORE define"
#endif

#include "pycore_freelist_state.h"      // struct _Py_freelists
#include "pycore_object.h"              // _PyObject_IS_GC
#include "pycore_pystate.h"             // _PyThreadState_GET
#include "pycore_code.h"                // OBJECT_STAT_INC

static inline struct _Py_freelists *
_Py_freelists_GET(void)
{
    PyThreadState *tstate = _PyThreadState_GET();
#ifdef Py_DEBUG
    _Py_EnsureTstateNotNULL(tstate);
#endif

#ifdef Py_GIL_DISABLED
    return &((_PyThreadStateImpl*)tstate)->freelists;
#else
    return &tstate->interp->object_state.freelists;
#endif
}

// Pushes `op` to the freelist, calls `freefunc` if the freelist is full
#define _Py_FREELIST_FREE(NAME, op, freefunc) \
    _PyFreeList_Free(&_Py_freelists_GET()->NAME, _PyObject_CAST(op), \
                     Py_ ## NAME ## _MAXFREELIST, freefunc)
// Pushes `op` to the freelist, returns 1 if successful, 0 if the freelist is full
#define _Py_FREELIST_PUSH(NAME, op, limit) \
    _PyFreeList_Push(&_Py_freelists_GET()->NAME, _PyObject_CAST(op), limit)

// Pops a PyObject from the freelist, returns NULL if the freelist is empty.
#define _Py_FREELIST_POP(TYPE, NAME) \
    _Py_CAST(TYPE*, _PyFreeList_Pop(&_Py_freelists_GET()->NAME))

// Pops a non-PyObject data structure from the freelist, returns NULL if the
// freelist is empty.
#define _Py_FREELIST_POP_MEM(NAME) \
    _PyFreeList_PopMem(&_Py_freelists_GET()->NAME)

#define _Py_FREELIST_SIZE(NAME) (int)((_Py_freelists_GET()->NAME).size)

static inline int
_PyFreeList_Push(struct _Py_freelist *fl, void *obj, Py_ssize_t maxsize)
{
    if (fl->size < maxsize && fl->size >= 0) {
        FT_ATOMIC_STORE_PTR_RELAXED(*(void **)obj, fl->freelist);
        fl->freelist = obj;
        fl->size++;
        OBJECT_STAT_INC(to_freelist);
        return 1;
    }
    return 0;
}

static inline void
_PyFreeList_Free(struct _Py_freelist *fl, void *obj, Py_ssize_t maxsize,
                 freefunc dofree)
{
    if (!_PyFreeList_Push(fl, obj, maxsize)) {
        dofree(obj);
    }
}

static inline void *
_PyFreeList_PopNoStats(struct _Py_freelist *fl)
{
    void *obj = fl->freelist;
    if (obj != NULL) {
        assert(fl->size > 0);
        fl->freelist = *(void **)obj;
        fl->size--;
    }
    return obj;
}

static inline PyObject *
_PyFreeList_Pop(struct _Py_freelist *fl)
{
    PyObject *op = _PyFreeList_PopNoStats(fl);
    if (op != NULL) {
        OBJECT_STAT_INC(from_freelist);
        _Py_NewReference(op);
    }
    return op;
}

static inline void *
_PyFreeList_PopMem(struct _Py_freelist *fl)
{
    void *op = _PyFreeList_PopNoStats(fl);
    if (op != NULL) {
        OBJECT_STAT_INC(from_freelist);
    }
    return op;
}

extern void _PyObject_ClearFreeLists(struct _Py_freelists *freelists, int is_finalization);

#ifdef __cplusplus
}
#endif
#endif /* !Py_INTERNAL_FREELIST_H */
