#ifndef NUMPY_CORE_INCLUDE_NUMPY_UFUNCOBJECT_H_
#define NUMPY_CORE_INCLUDE_NUMPY_UFUNCOBJECT_H_

#include <numpy/npy_math.h>
#include <numpy/npy_common.h>
#include <numpy/utils.h>

#ifdef __cplusplus
extern "C" {
#endif

/*
 * The legacy generic inner loop for a standard element-wise or
 * generalized ufunc.
 */
typedef void (*PyUFuncGenericFunction)
            (char **args,
             npy_intp const *dimensions,
             npy_intp const *strides,
             void *innerloopdata);

/*
 * The most generic one-dimensional inner loop for
 * a masked standard element-wise ufunc. "Masked" here means that it skips
 * doing calculations on any items for which the maskptr array has a true
 * value.
 */
typedef void (PyUFunc_MaskedStridedInnerLoopFunc)(
                char **dataptrs, npy_intp *strides,
                char *maskptr, npy_intp mask_stride,
                npy_intp count,
                NpyAuxData *innerloopdata);

/* Forward declaration for the type resolver and loop selector typedefs */
struct _tagPyUFuncObject;

/*
 * Given the operands for calling a ufunc, should determine the
 * calculation input and output data types and return an inner loop function.
 * This function should validate that the casting rule is being followed,
 * and fail if it is not.
 *
 * For backwards compatibility, the regular type resolution function does not
 * support auxiliary data with object semantics. The type resolution call
 * which returns a masked generic function returns a standard NpyAuxData
 * object, for which the NPY_AUXDATA_FREE and NPY_AUXDATA_CLONE macros
 * work.
 *
 * ufunc:             The ufunc object.
 * casting:           The 'casting' parameter provided to the ufunc.
 * operands:          An array of length (ufunc->nin + ufunc->nout),
 *                    with the output parameters possibly NULL.
 * type_tup:          Either NULL, or the type_tup passed to the ufunc.
 * out_dtypes:        An array which should be populated with new
 *                    references to (ufunc->nin + ufunc->nout) new
 *                    dtypes, one for each input and output. These
 *                    dtypes should all be in native-endian format.
 *
 * Should return 0 on success, -1 on failure (with exception set),
 * or -2 if Py_NotImplemented should be returned.
 */
typedef int (PyUFunc_TypeResolutionFunc)(
                                struct _tagPyUFuncObject *ufunc,
                                NPY_CASTING casting,
                                PyArrayObject **operands,
                                PyObject *type_tup,
                                PyArray_Descr **out_dtypes);

/*
 * This is the signature for the functions that may be assigned to the
 * `process_core_dims_func` field of the PyUFuncObject structure.
 * Implementation of this function is optional.  This function is only used
 * by generalized ufuncs (i.e. those with the field `core_enabled` set to 1).
 * The function is called by the ufunc during the processing of the arguments
 * of a call of the ufunc. The function can check the core dimensions of the
 * input and output arrays and return -1 with an exception set if any
 * requirements are not satisfied. If the caller of the ufunc didn't provide
 * output arrays, the core dimensions associated with the output arrays (i.e.
 * those that are not also used in input arrays) will have the value -1 in
 * `core_dim_sizes`.  This function can replace any output core dimensions
 * that are -1 with a value that is appropriate for the ufunc.
 *
 * Parameter       Description
 * --------------- ------------------------------------------------------
 * ufunc           The ufunc object
 * core_dim_sizes  An array with length `ufunc->core_num_dim_ix`.
 *                 The core dimensions of the arrays passed to the ufunc
 *                 will have been set.  If the caller of the ufunc didn't
 *                 provide the output array(s), the output-only core
 *                 dimensions will have the value -1.
 *
 * The function must not change any element in `core_dim_sizes` that is
 * not -1 on input. Doing so will result in incorrect output from the
 * ufunc, and could result in a crash of the Python interpreter.
 *
 * The function must return 0 on success, -1 on failure (with an exception
 * set).
 */
typedef int (PyUFunc_ProcessCoreDimsFunc)(
                                struct _tagPyUFuncObject *ufunc,
                                npy_intp *core_dim_sizes);

typedef struct _tagPyUFuncObject {
#ifndef Py_TARGET_ABI3T
        PyObject_HEAD
#endif
        /*
         * nin: Number of inputs
         * nout: Number of outputs
         * nargs: Always nin + nout (Why is it stored?)
         */
        _NPY_OPAQUE_FIRST_FIELD int nin;
        int nout, nargs;

        /*
         * Identity for reduction, any of PyUFunc_One, PyUFunc_Zero
         * PyUFunc_MinusOne, PyUFunc_None, PyUFunc_ReorderableNone,
         * PyUFunc_IdentityValue.
         */
        int identity;

        /* Array of one-dimensional core loops */
        PyUFuncGenericFunction *functions;
        /* Array of funcdata that gets passed into the functions */
        void *const *data;
        /* The number of elements in 'functions' and 'data' */
        int ntypes;

        /* Flags for the ufunc (e.g. UFUNC_NO_FLOATINGPOINT_ERRORS) */
        int _ufunc_flags;

        /* The name of the ufunc */
        const char *name;

        /* Array of type numbers, of size ('nargs' * 'ntypes') */
        const char *types;

        /* Documentation string */
        const char *doc;

        void *ptr;
        PyObject *obj;
        PyObject *userloops;

        /* generalized ufunc parameters */

        /* 0 for scalar ufunc; 1 for generalized ufunc */
        int core_enabled;
        /* number of distinct dimension names in signature */
        int core_num_dim_ix;

        /*
         * dimension indices of input/output argument k are stored in
         * core_dim_ixs[core_offsets[k]..core_offsets[k]+core_num_dims[k]-1]
         */

        /* numbers of core dimensions of each argument */
        int *core_num_dims;
        /*
         * dimension indices in a flatted form; indices
         * are in the range of [0,core_num_dim_ix)
         */
        int *core_dim_ixs;
        /*
         * positions of 1st core dimensions of each
         * argument in core_dim_ixs, equivalent to cumsum(core_num_dims)
         */
        int *core_offsets;
        /* signature string for printing purpose */
        char *core_signature;

        /*
         * A function which resolves the types and fills an array
         * with the dtypes for the inputs and outputs.
         */
        PyUFunc_TypeResolutionFunc *type_resolver;

        /* A dictionary to monkeypatch ufuncs */
        PyObject *dict;

        /*
         * This was blocked off to be the "new" inner loop selector in 1.7,
         * but this was never implemented. (This is also why the above
         * selector is called the "legacy" selector.)
         */
        #if !defined(Py_LIMITED_API) || Py_LIMITED_API >= 0x030C0000
            vectorcallfunc vectorcall;
        #else
            void *vectorcall;
        #endif

        /* Was previously the `PyUFunc_MaskedInnerLoopSelectionFunc` */
        void *reserved3;

        /*
         * List of flags for each operand when ufunc is called by nditer object.
         * These flags will be used in addition to the default flags for each
         * operand set by nditer object.
         */
        npy_uint32 *op_flags;

        /*
         * List of global flags used when ufunc is called by nditer object.
         * These flags will be used in addition to the default global flags
         * set by nditer object.
         */
        npy_uint32 iter_flags;

        /* New in NPY_API_VERSION 0x0000000D and above */
    #if NPY_FEATURE_VERSION >= NPY_1_16_API_VERSION
        /*
         * for each core_num_dim_ix distinct dimension names,
         * the possible "frozen" size (-1 if not frozen).
         */
        npy_intp *core_dim_sizes;

        /*
         * for each distinct core dimension, a set of UFUNC_CORE_DIM* flags
         */
        npy_uint32 *core_dim_flags;

        /* Identity for reduction, when identity == PyUFunc_IdentityValue */
        PyObject *identity_value;
    #endif  /* NPY_FEATURE_VERSION >= NPY_1_16_API_VERSION */

        /* New in NPY_API_VERSION 0x0000000F and above */
    #if NPY_FEATURE_VERSION >= NPY_1_22_API_VERSION
        /* New private fields related to dispatching */
        void *_dispatch_cache;
        /* Ordered dict `tuple of DTypes -> (tuple of DTypes, ArrayMethod/Promoter)` */
        PyObject *_loops;
    #endif
    #if NPY_FEATURE_VERSION >= NPY_2_1_API_VERSION
        /*
         * Optional function to process core dimensions of a gufunc.
         */
        PyUFunc_ProcessCoreDimsFunc *process_core_dims_func;
    #endif
} PyUFuncObject_fields;


#include "arrayobject.h"
/* Generalized ufunc; 0x0001 reserved for possible use as CORE_ENABLED */
/* the core dimension's size will be determined by the operands. */
#define UFUNC_CORE_DIM_SIZE_INFERRED 0x0002
/* the core dimension may be absent */
#define UFUNC_CORE_DIM_CAN_IGNORE 0x0004
/* flags inferred during execution */
#define UFUNC_CORE_DIM_MISSING 0x00040000


#define UFUNC_OBJ_ISOBJECT      1
#define UFUNC_OBJ_NEEDS_API     2

#if defined(NPY_INTERNAL_BUILD) && NPY_INTERNAL_BUILD
/*
 * Flag stored in PyUFuncObject._ufunc_flags to indicate that non-object loops
 * of this ufunc never raise floating point errors.  Used to skip the
 * expensive npy_clear_floatstatus/npy_get_floatstatus calls.
 */
#define UFUNC_NO_FLOATINGPOINT_ERRORS  0x1
#endif  /* NPY_INTERNAL_BUILD */


#if NPY_ALLOW_THREADS
#define NPY_LOOP_BEGIN_THREADS do {if (!(loop->obj & UFUNC_OBJ_NEEDS_API)) _save = PyEval_SaveThread();} while (0);
#define NPY_LOOP_END_THREADS   do {if (!(loop->obj & UFUNC_OBJ_NEEDS_API)) PyEval_RestoreThread(_save);} while (0);
#else
#define NPY_LOOP_BEGIN_THREADS
#define NPY_LOOP_END_THREADS
#endif

/*
 * UFunc has unit of 0, and the order of operations can be reordered
 * This case allows reduction with multiple axes at once.
 */
#define PyUFunc_Zero 0
/*
 * UFunc has unit of 1, and the order of operations can be reordered
 * This case allows reduction with multiple axes at once.
 */
#define PyUFunc_One 1
/*
 * UFunc has unit of -1, and the order of operations can be reordered
 * This case allows reduction with multiple axes at once. Intended for
 * bitwise_and reduction.
 */
#define PyUFunc_MinusOne 2
/*
 * UFunc has no unit, and the order of operations cannot be reordered.
 * This case does not allow reduction with multiple axes at once.
 */
#define PyUFunc_None -1
/*
 * UFunc has no unit, and the order of operations can be reordered
 * This case allows reduction with multiple axes at once.
 */
#define PyUFunc_ReorderableNone -2
/*
 * UFunc unit is an identity_value, and the order of operations can be reordered
 * This case allows reduction with multiple axes at once.
 */
#define PyUFunc_IdentityValue -3


#define UFUNC_REDUCE 0
#define UFUNC_ACCUMULATE 1
#define UFUNC_REDUCEAT 2
#define UFUNC_OUTER 3


typedef struct {
        int nin;
        int nout;
        PyObject *callable;
} PyUFunc_PyFuncData;

/* A linked-list of function information for
   user-defined 1-d loops.
 */
typedef struct _loop1d_info {
        PyUFuncGenericFunction func;
        void *data;
        int *arg_types;
        struct _loop1d_info *next;
        int nargs;
        PyArray_Descr **arg_dtypes;
} PyUFunc_Loop1d;


#define UFUNC_PYVALS_NAME "UFUNC_PYVALS"

/* THESE MACROS ARE DEPRECATED.
 * Use npy_set_floatstatus_* in the npymath library.
 */
#define UFUNC_FPE_DIVIDEBYZERO  NPY_FPE_DIVIDEBYZERO
#define UFUNC_FPE_OVERFLOW      NPY_FPE_OVERFLOW
#define UFUNC_FPE_UNDERFLOW     NPY_FPE_UNDERFLOW
#define UFUNC_FPE_INVALID       NPY_FPE_INVALID

/* Make sure it gets defined if it isn't already */
#ifndef UFUNC_NOFPE
/* Clear the floating point exception default of Borland C++ */
#if defined(__BORLANDC__)
#define UFUNC_NOFPE _control87(MCW_EM, MCW_EM);
#else
#define UFUNC_NOFPE
#endif
#endif

#ifndef Py_TARGET_ABI3T
typedef struct _tagPyUFuncObject PyUFuncObject;
#else
typedef struct tagPyUFuncObject PyUFuncObject;
#endif

#include "__ufunc_api.h"

#ifndef Py_TARGET_ABI3T
#undef _PyUFuncObject_GET_ITEM_DATA
#define _PyUFuncObject_GET_ITEM_DATA(ufunc) ((PyUFuncObject_fields *)(ufunc))
#endif

#ifdef __cplusplus
}
#endif

#endif  /* NUMPY_CORE_INCLUDE_NUMPY_UFUNCOBJECT_H_ */
