#ifndef NUMPY_CORE_SRC_MULTIARRAY_ALLOC_H_
#define NUMPY_CORE_SRC_MULTIARRAY_ALLOC_H_

#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#define _MULTIARRAYMODULE
#include "numpy/ndarraytypes.h"

#define NPY_TRACE_DOMAIN 389047
#define MEM_HANDLER_CAPSULE_NAME "mem_handler"

NPY_NO_EXPORT PyObject *
_get_madvise_hugepage(PyObject *NPY_UNUSED(self), PyObject *NPY_UNUSED(args));

NPY_NO_EXPORT PyObject *
_set_madvise_hugepage(PyObject *NPY_UNUSED(self), PyObject *enabled_obj);

NPY_NO_EXPORT void *
PyDataMem_UserNEW(npy_uintp sz, PyObject *mem_handler);

NPY_NO_EXPORT void *
PyDataMem_UserNEW_ZEROED(size_t nmemb, size_t size, PyObject *mem_handler);

NPY_NO_EXPORT void
PyDataMem_UserFREE(void * p, npy_uintp sd, PyObject *mem_handler);

NPY_NO_EXPORT void *
PyDataMem_UserRENEW(void *ptr, size_t size, PyObject *mem_handler);

NPY_NO_EXPORT void *
npy_alloc_cache_dim(npy_uintp sz);

NPY_NO_EXPORT void
npy_free_cache_dim(void * p, npy_uintp sd);

static inline void
npy_free_cache_dim_obj(PyArray_Dims dims)
{
    npy_free_cache_dim(dims.ptr, dims.len);
}

static inline void
npy_free_cache_dim_array(PyArrayObject * arr)
{
    npy_free_cache_dim(PyArray_DIMS(arr), PyArray_NDIM(arr));
}

extern PyDataMem_Handler default_handler;
extern PyObject *current_handler; /* PyContextVar/PyCapsule */

NPY_NO_EXPORT PyObject *
get_handler_name(PyObject *NPY_UNUSED(self), PyObject *obj);
NPY_NO_EXPORT PyObject *
get_handler_version(PyObject *NPY_UNUSED(self), PyObject *obj);

/* Helper to add an overflow check (and avoid inlininig this probably) */
NPY_NO_EXPORT void *
_Npy_MallocWithOverflowCheck(npy_intp size, npy_intp elsize);


static inline void
_npy_init_workspace(
    void **buf, void *static_buf, size_t static_buf_size, size_t elsize, size_t size)
{
    if (NPY_LIKELY(size <= static_buf_size)) {
        *buf = static_buf;
    }
    else {
        *buf = _Npy_MallocWithOverflowCheck(size, elsize);
        if (*buf == NULL) {
            PyErr_NoMemory();
        }
    }
}


/*
 * Helper definition macro for a small work/scratchspace.
 * The `NAME` is the C array to be defined of with the type `TYPE`.
 *
 * The usage pattern for this is:
 *
 *     NPY_ALLOC_WORKSPACE(arr, PyObject *, 14, n_objects);
 *     if (arr == NULL) {
 *         return -1;  // Memory error is set
 *     }
 *     ...
 *     npy_free_workspace(arr);
 *
 * Notes
 * -----
 * The reason is to avoid allocations in most cases, but gracefully
 * succeed for large sizes as well.
 * With some caches, it may be possible to malloc/calloc very quickly in which
 * case we should not hesitate to replace this pattern.
 */
#define NPY_DEFINE_WORKSPACE(NAME, TYPE, fixed_size)        \
    TYPE NAME##_static[fixed_size];                        \
    TYPE *NAME;
#define NPY_INIT_WORKSPACE(NAME, TYPE, fixed_size, size)   \
    _npy_init_workspace((void **)&NAME, NAME##_static, (fixed_size), sizeof(TYPE), (size))

#define NPY_ALLOC_WORKSPACE(NAME, TYPE, fixed_size, size)  \
    NPY_DEFINE_WORKSPACE(NAME, TYPE, fixed_size)            \
    NPY_INIT_WORKSPACE(NAME, TYPE, fixed_size, size)


static inline void
_npy_free_workspace(void *buf, void *static_buf)
{
    if (buf != static_buf) {
        PyMem_FREE(buf);
    }
}

/* Free a small workspace allocation (macro to fetch the _static name) */
#define npy_free_workspace(NAME)  \
    _npy_free_workspace(NAME, NAME##_static)

#endif  /* NUMPY_CORE_SRC_MULTIARRAY_ALLOC_H_ */
