#ifndef _NPY_PRIVATE__REDUCTION_H_
#define _NPY_PRIVATE__REDUCTION_H_

/************************************************************
 * Typedefs used by PyArray_ReduceWrapper, new in 1.7.
 ************************************************************/

/*
 * This is a function for assigning a reduction identity to the result,
 * before doing the reduction computation. The
 * value in 'data' is passed through from PyArray_ReduceWrapper.
 *
 * This function could, for example, simply be a call like
 *      return PyArray_AssignZero(result, NULL, NULL);
 *
 * It should return -1 on failure, or 0 on success.
 */
typedef int (PyArray_AssignReduceIdentityFunc)(PyArrayObject *result,
                                               void *data);

/*
 * This is a function for the reduce loop.
 *
 * The needs_api parameter indicates whether it's ok to release the GIL during
 * the loop, such as when the iternext() function never calls
 * a function which could raise a Python exception.
 *
 * Ths skip_first_count parameter indicates how many elements need to be
 * skipped based on NpyIter_IsFirstVisit checks. This can only be positive
 * when the 'assign_identity' parameter was NULL when calling
 * PyArray_ReduceWrapper.
 *
 * The loop gets two data pointers and two strides, and should
 * look roughly like this:
 *  {
 *      NPY_BEGIN_THREADS_DEF;
 *      if (!needs_api) {
 *          NPY_BEGIN_THREADS;
 *      }
 *      // This first-visit loop can be skipped if 'assign_identity' was non-NULL
 *      if (skip_first_count > 0) {
 *          do {
 *              char *data0 = dataptr[0], *data1 = dataptr[1];
 *              npy_intp stride0 = strideptr[0], stride1 = strideptr[1];
 *              npy_intp count = *countptr;
 *
 *              // Skip any first-visit elements
 *              if (NpyIter_IsFirstVisit(iter, 0)) {
 *                  if (stride0 == 0) {
 *                      --count;
 *                      --skip_first_count;
 *                      data1 += stride1;
 *                  }
 *                  else {
 *                      skip_first_count -= count;
 *                      count = 0;
 *                  }
 *              }
 *
 *              while (count--) {
 *                  *(result_t *)data0 = my_reduce_op(*(result_t *)data0,
 *                                                    *(operand_t *)data1);
 *                  data0 += stride0;
 *                  data1 += stride1;
 *              }
 *
 *              // Jump to the faster loop when skipping is done
 *              if (skip_first_count == 0) {
 *                  if (iternext(iter)) {
 *                      break;
 *                  }
 *                  else {
 *                      goto finish_loop;
 *                  }
 *              }
 *          } while (iternext(iter));
 *      }
 *      do {
 *          char *data0 = dataptr[0], *data1 = dataptr[1];
 *          npy_intp stride0 = strideptr[0], stride1 = strideptr[1];
 *          npy_intp count = *countptr;
 *
 *          while (count--) {
 *              *(result_t *)data0 = my_reduce_op(*(result_t *)data0,
 *                                                *(operand_t *)data1);
 *              data0 += stride0;
 *              data1 += stride1;
 *          }
 *      } while (iternext(iter));
 *  finish_loop:
 *      if (!needs_api) {
 *          NPY_END_THREADS;
 *      }
 *      return (needs_api && PyErr_Occurred()) ? -1 : 0;
 *  }
 *
 * If needs_api is True, this function should call PyErr_Occurred()
 * to check if an error occurred during processing, and return -1 for
 * error, 0 for success.
 */
typedef int (PyArray_ReduceLoopFunc)(NpyIter *iter,
                                            char **dataptr,
                                            npy_intp *strideptr,
                                            npy_intp *countptr,
                                            NpyIter_IterNextFunc *iternext,
                                            int needs_api,
                                            npy_intp skip_first_count,
                                            void *data);

/*
 * This function executes all the standard NumPy reduction function
 * boilerplate code, just calling assign_identity and the appropriate
 * inner loop function where necessary.
 *
 * operand     : The array to be reduced.
 * out         : NULL, or the array into which to place the result.
 * wheremask   : NOT YET SUPPORTED, but this parameter is placed here
 *               so that support can be added in the future without breaking
 *               API compatibility. Pass in NULL.
 * operand_dtype : The dtype the inner loop expects for the operand.
 * result_dtype : The dtype the inner loop expects for the result.
 * casting     : The casting rule to apply to the operands.
 * axis_flags  : Flags indicating the reduction axes of 'operand'.
 * reorderable : If True, the reduction being done is reorderable, which
 *               means specifying multiple axes of reduction at once is ok,
 *               and the reduction code may calculate the reduction in an
 *               arbitrary order. The calculation may be reordered because
 *               of cache behavior or multithreading requirements.
 * keepdims    : If true, leaves the reduction dimensions in the result
 *               with size one.
 * subok       : If true, the result uses the subclass of operand, otherwise
 *               it is always a base class ndarray.
 * assign_identity : If NULL, PyArray_InitializeReduceResult is used, otherwise
 *               this function is called to initialize the result to
 *               the reduction's unit.
 * loop        : The loop which does the reduction.
 * data        : Data which is passed to assign_identity and the inner loop.
 * buffersize  : Buffer size for the iterator. For the default, pass in 0.
 * funcname    : The name of the reduction function, for error messages.
 */
NPY_NO_EXPORT PyArrayObject *
PyUFunc_ReduceWrapper(PyArrayObject *operand, PyArrayObject *out,
                      PyArrayObject *wheremask,
                      PyArray_Descr *operand_dtype,
                      PyArray_Descr *result_dtype,
                      NPY_CASTING casting,
                      npy_bool *axis_flags, int reorderable,
                      int keepdims,
                      int subok,
                      PyArray_AssignReduceIdentityFunc *assign_identity,
                      PyArray_ReduceLoopFunc *loop,
                      void *data, npy_intp buffersize, const char *funcname);

#endif
