#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#include <Python.h>
#include "numpy/arrayobject.h"

#include "npy_pycompat.h"

/*
 * TODO:
 *  - Handle mode
 */

/**begin repeat
 * #name = double, int#
 * #type = npy_double, npy_int#
 * #typenum = NPY_DOUBLE, NPY_INT#
 */
static int copy_@name@(PyArrayIterObject *itx, PyArrayNeighborhoodIterObject *niterx,
        npy_intp *bounds,
        PyObject **out)
{
    npy_intp i, j;
    @type@ *ptr;
    npy_intp odims[NPY_MAXDIMS];
    PyArrayObject *aout;

    /*
     * For each point in itx, copy the current neighborhood into an array which
     * is appended at the output list
     */
    for (i = 0; i < itx->size; ++i) {
        PyArrayNeighborhoodIter_Reset(niterx);

        for (j = 0; j < PyArray_NDIM(itx->ao); ++j) {
            odims[j] = bounds[2 * j + 1] - bounds[2 * j] + 1;
        }
        aout = (PyArrayObject*)PyArray_SimpleNew(
                                PyArray_NDIM(itx->ao), odims, @typenum@);
        if (aout == NULL) {
            return -1;
        }

        ptr = (@type@*)PyArray_DATA(aout);

        for (j = 0; j < niterx->size; ++j) {
            *ptr = *((@type@*)niterx->dataptr);
            PyArrayNeighborhoodIter_Next(niterx);
            ptr += 1;
        }

        PyList_Append(*out, (PyObject*)aout);
        Py_DECREF(aout);
        PyArray_ITER_NEXT(itx);
    }

    return 0;
}
/**end repeat**/

static int copy_object(PyArrayIterObject *itx, PyArrayNeighborhoodIterObject *niterx,
        npy_intp *bounds,
        PyObject **out)
{
    npy_intp i, j;
    npy_intp odims[NPY_MAXDIMS];
    PyArrayObject *aout;
    PyArray_CopySwapFunc *copyswap = PyArray_DESCR(itx->ao)->f->copyswap;
    npy_int itemsize = PyArray_ITEMSIZE(itx->ao);

    /*
     * For each point in itx, copy the current neighborhood into an array which
     * is appended at the output list
     */
    for (i = 0; i < itx->size; ++i) {
        PyArrayNeighborhoodIter_Reset(niterx);

        for (j = 0; j < PyArray_NDIM(itx->ao); ++j) {
            odims[j] = bounds[2 * j + 1] - bounds[2 * j] + 1;
        }
        aout = (PyArrayObject*)PyArray_SimpleNew(PyArray_NDIM(itx->ao), odims, NPY_OBJECT);
        if (aout == NULL) {
            return -1;
        }

        for (j = 0; j < niterx->size; ++j) {
            copyswap(PyArray_BYTES(aout) + j * itemsize, niterx->dataptr, 0, NULL);
            PyArrayNeighborhoodIter_Next(niterx);
        }

        PyList_Append(*out, (PyObject*)aout);
        Py_DECREF(aout);
        PyArray_ITER_NEXT(itx);
    }

    return 0;
}

static PyObject*
test_neighborhood_iterator(PyObject* NPY_UNUSED(self), PyObject* args)
{
    PyObject *x, *fill, *out, *b;
    PyArrayObject *ax, *afill;
    PyArrayIterObject *itx;
    int i, typenum, mode, st;
    npy_intp bounds[NPY_MAXDIMS*2];
    PyArrayNeighborhoodIterObject *niterx;

    if (!PyArg_ParseTuple(args, "OOOi", &x, &b, &fill, &mode)) {
        return NULL;
    }

    if (!PySequence_Check(b)) {
        return NULL;
    }

    typenum = PyArray_ObjectType(x, 0);
    typenum = PyArray_ObjectType(fill, typenum);

    ax = (PyArrayObject*)PyArray_FromObject(x, typenum, 1, 10);
    if (ax == NULL) {
        return NULL;
    }
    if (PySequence_Size(b) != 2 * PyArray_NDIM(ax)) {
        PyErr_SetString(PyExc_ValueError,
                "bounds sequence size not compatible with x input");
        goto clean_ax;
    }

    out = PyList_New(0);
    if (out == NULL) {
        goto clean_ax;
    }

    itx = (PyArrayIterObject*)PyArray_IterNew(x);
    if (itx == NULL) {
        goto clean_out;
    }

    /* Compute boundaries for the neighborhood iterator */
    for (i = 0; i < 2 * PyArray_NDIM(ax); ++i) {
        PyObject* bound;
        bound = PySequence_GetItem(b, i);
        if (bounds == NULL) {
            goto clean_itx;
        }
        if (!PyInt_Check(bound)) {
            PyErr_SetString(PyExc_ValueError,
                    "bound not long");
            Py_DECREF(bound);
            goto clean_itx;
        }
        bounds[i] = PyInt_AsLong(bound);
        Py_DECREF(bound);
    }

    /* Create the neighborhood iterator */
    afill = NULL;
    if (mode == NPY_NEIGHBORHOOD_ITER_CONSTANT_PADDING) {
            afill = (PyArrayObject *)PyArray_FromObject(fill, typenum, 0, 0);
            if (afill == NULL) {
            goto clean_itx;
        }
    }

    niterx = (PyArrayNeighborhoodIterObject*)PyArray_NeighborhoodIterNew(
                    (PyArrayIterObject*)itx, bounds, mode, afill);
    if (niterx == NULL) {
        goto clean_afill;
    }

    switch (typenum) {
        case NPY_OBJECT:
            st = copy_object(itx, niterx, bounds, &out);
            break;
        case NPY_INT:
            st = copy_int(itx, niterx, bounds, &out);
            break;
        case NPY_DOUBLE:
            st = copy_double(itx, niterx, bounds, &out);
            break;
        default:
            PyErr_SetString(PyExc_ValueError,
                    "Type not supported");
            goto clean_niterx;
    }

    if (st) {
        goto clean_niterx;
    }

    Py_DECREF(niterx);
    Py_XDECREF(afill);
    Py_DECREF(itx);

    Py_DECREF(ax);

    return out;

clean_niterx:
    Py_DECREF(niterx);
clean_afill:
    Py_XDECREF(afill);
clean_itx:
    Py_DECREF(itx);
clean_out:
    Py_DECREF(out);
clean_ax:
    Py_DECREF(ax);
    return NULL;
}

static int
copy_double_double(PyArrayNeighborhoodIterObject *itx,
        PyArrayNeighborhoodIterObject *niterx,
        npy_intp *bounds,
        PyObject **out)
{
    npy_intp i, j;
    double *ptr;
    npy_intp odims[NPY_MAXDIMS];
    PyArrayObject *aout;

    /*
     * For each point in itx, copy the current neighborhood into an array which
     * is appended at the output list
     */
    PyArrayNeighborhoodIter_Reset(itx);
    for (i = 0; i < itx->size; ++i) {
        for (j = 0; j < PyArray_NDIM(itx->ao); ++j) {
            odims[j] = bounds[2 * j + 1] - bounds[2 * j] + 1;
        }
        aout = (PyArrayObject*)PyArray_SimpleNew(
                            PyArray_NDIM(itx->ao), odims, NPY_DOUBLE);
        if (aout == NULL) {
            return -1;
        }

        ptr = (double*)PyArray_DATA(aout);

        PyArrayNeighborhoodIter_Reset(niterx);
        for (j = 0; j < niterx->size; ++j) {
            *ptr = *((double*)niterx->dataptr);
            ptr += 1;
            PyArrayNeighborhoodIter_Next(niterx);
        }
        PyList_Append(*out, (PyObject*)aout);
        Py_DECREF(aout);
        PyArrayNeighborhoodIter_Next(itx);
    }
    return 0;
}

static PyObject*
test_neighborhood_iterator_oob(PyObject* NPY_UNUSED(self), PyObject* args)
{
    PyObject *x, *out, *b1, *b2;
    PyArrayObject *ax;
    PyArrayIterObject *itx;
    int i, typenum, mode1, mode2, st;
    npy_intp bounds[NPY_MAXDIMS*2];
    PyArrayNeighborhoodIterObject *niterx1, *niterx2;

    if (!PyArg_ParseTuple(args, "OOiOi", &x, &b1, &mode1, &b2, &mode2)) {
        return NULL;
    }

    if (!PySequence_Check(b1) || !PySequence_Check(b2)) {
        return NULL;
    }

    typenum = PyArray_ObjectType(x, 0);

    ax = (PyArrayObject*)PyArray_FromObject(x, typenum, 1, 10);
    if (ax == NULL) {
        return NULL;
    }
    if (PySequence_Size(b1) != 2 * PyArray_NDIM(ax)) {
        PyErr_SetString(PyExc_ValueError,
                "bounds sequence 1 size not compatible with x input");
        goto clean_ax;
    }
    if (PySequence_Size(b2) != 2 * PyArray_NDIM(ax)) {
        PyErr_SetString(PyExc_ValueError,
                "bounds sequence 2 size not compatible with x input");
        goto clean_ax;
    }

    out = PyList_New(0);
    if (out == NULL) {
        goto clean_ax;
    }

    itx = (PyArrayIterObject*)PyArray_IterNew(x);
    if (itx == NULL) {
        goto clean_out;
    }

    /* Compute boundaries for the neighborhood iterator */
    for (i = 0; i < 2 * PyArray_NDIM(ax); ++i) {
        PyObject* bound;
        bound = PySequence_GetItem(b1, i);
        if (bounds == NULL) {
            goto clean_itx;
        }
        if (!PyInt_Check(bound)) {
            PyErr_SetString(PyExc_ValueError,
                    "bound not long");
            Py_DECREF(bound);
            goto clean_itx;
        }
        bounds[i] = PyInt_AsLong(bound);
        Py_DECREF(bound);
    }

    /* Create the neighborhood iterator */
    niterx1 = (PyArrayNeighborhoodIterObject*)PyArray_NeighborhoodIterNew(
                    (PyArrayIterObject*)itx, bounds,
                    mode1, NULL);
    if (niterx1 == NULL) {
        goto clean_out;
    }

    for (i = 0; i < 2 * PyArray_NDIM(ax); ++i) {
        PyObject* bound;
        bound = PySequence_GetItem(b2, i);
        if (bounds == NULL) {
            goto clean_itx;
        }
        if (!PyInt_Check(bound)) {
            PyErr_SetString(PyExc_ValueError,
                    "bound not long");
            Py_DECREF(bound);
            goto clean_itx;
        }
        bounds[i] = PyInt_AsLong(bound);
        Py_DECREF(bound);
    }

    niterx2 = (PyArrayNeighborhoodIterObject*)PyArray_NeighborhoodIterNew(
                    (PyArrayIterObject*)niterx1, bounds,
                    mode2, NULL);
    if (niterx1 == NULL) {
        goto clean_niterx1;
    }

    switch (typenum) {
        case NPY_DOUBLE:
            st = copy_double_double(niterx1, niterx2, bounds, &out);
            break;
        default:
            PyErr_SetString(PyExc_ValueError,
                    "Type not supported");
            goto clean_niterx2;
    }

    if (st) {
        goto clean_niterx2;
    }

    Py_DECREF(niterx2);
    Py_DECREF(niterx1);
    Py_DECREF(itx);
    Py_DECREF(ax);
    return out;

clean_niterx2:
    Py_DECREF(niterx2);
clean_niterx1:
    Py_DECREF(niterx1);
clean_itx:
    Py_DECREF(itx);
clean_out:
    Py_DECREF(out);
clean_ax:
    Py_DECREF(ax);
    return NULL;
}

/* PyDataMem_SetHook tests */
static int malloc_free_counts[2];
static PyDataMem_EventHookFunc *old_hook = NULL;
static void *old_data;

static void test_hook(void *old, void *new, size_t size, void *user_data)
{
    int* counters = (int *) user_data;
    if (old == NULL) {
        counters[0]++; /* malloc counter */
    }
    if (size == 0) {
        counters[1]++; /* free counter */
    }
}

static PyObject*
test_pydatamem_seteventhook_start(PyObject* NPY_UNUSED(self), PyObject* NPY_UNUSED(args))
{
    malloc_free_counts[0] = malloc_free_counts[1] = 0;
    old_hook = PyDataMem_SetEventHook(test_hook, (void *) malloc_free_counts, &old_data);
    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject*
test_pydatamem_seteventhook_end(PyObject* NPY_UNUSED(self), PyObject* NPY_UNUSED(args))
{
    PyDataMem_EventHookFunc *my_hook;
    void *my_data;

    my_hook = PyDataMem_SetEventHook(old_hook, old_data, &my_data);
    if ((my_hook != test_hook) || (my_data != (void *) malloc_free_counts)) {
        PyErr_SetString(PyExc_ValueError,
                        "hook/data was not the expected test hook");
        return NULL;
    }

    if (malloc_free_counts[0] == 0) {
        PyErr_SetString(PyExc_ValueError,
                        "malloc count is zero after test");
        return NULL;
    }
    if (malloc_free_counts[1] == 0) {
        PyErr_SetString(PyExc_ValueError,
                        "free count is zero after test");
        return NULL;
    }

    Py_INCREF(Py_None);
    return Py_None;
}


typedef void (*inplace_map_binop)(PyArrayMapIterObject *, PyArrayIterObject *);

static void npy_float64_inplace_add(PyArrayMapIterObject *mit, PyArrayIterObject *it)
{
    int index = mit->size;
    while (index--) {
        ((npy_float64*)mit->dataptr)[0] = ((npy_float64*)mit->dataptr)[0] + ((npy_float64*)it->dataptr)[0];

        PyArray_MapIterNext(mit);
        PyArray_ITER_NEXT(it);
    }
}

inplace_map_binop addition_funcs[] = {
npy_float64_inplace_add,
NULL};

int type_numbers[] = {
NPY_FLOAT64,
-1000};



static int
map_increment(PyArrayMapIterObject *mit, PyObject *op, inplace_map_binop add_inplace)
{
    PyArrayObject *arr = NULL;
    PyArrayIterObject *it;
    PyArray_Descr *descr;

    if (mit->ait == NULL) {
        return -1;
    }
    descr = PyArray_DESCR(mit->ait->ao);
    Py_INCREF(descr);
    arr = (PyArrayObject *)PyArray_FromAny(op, descr,
                                0, 0, NPY_ARRAY_FORCECAST, NULL);
    if (arr == NULL) {
        return -1;
    }

    if ((mit->subspace != NULL) && (mit->consec)) {
        PyArray_MapIterSwapAxes(mit, (PyArrayObject **)&arr, 0);
        if (arr == NULL) {
            return -1;
        }
    }

    if ((it = (PyArrayIterObject *)\
            PyArray_BroadcastToShape((PyObject *)arr, mit->dimensions,
                                     mit->nd)) == NULL) {
        Py_DECREF(arr);

        return -1;
    }

    (*add_inplace)(mit, it);

    Py_DECREF(arr);
    Py_DECREF(it);
    return 0;
}


static PyObject *
inplace_increment(PyObject *dummy, PyObject *args)
{
    PyObject *arg_a = NULL, *index=NULL, *inc=NULL;
    PyArrayObject *a;
    inplace_map_binop add_inplace = NULL;
    int type_number = -1;
    int i =0;
    PyArrayMapIterObject * mit;

    if (!PyArg_ParseTuple(args, "OOO", &arg_a, &index,
            &inc)) {
        return NULL;
    }
    if (!PyArray_Check(arg_a)) {
         PyErr_SetString(PyExc_ValueError, "needs an ndarray as first argument");
         return NULL;
    }
    a = (PyArrayObject *) arg_a;

    if (PyArray_FailUnlessWriteable(a, "input/output array") < 0) {
        return NULL;
    }

    if (PyArray_NDIM(a) == 0) {
        PyErr_SetString(PyExc_IndexError, "0-d arrays can't be indexed.");
        return NULL;
    }
    type_number = PyArray_TYPE(a);

    while (type_numbers[i] >= 0 && addition_funcs[i] != NULL){
        if (type_number == type_numbers[i]) {
            add_inplace = addition_funcs[i];
            break;
        }
        i++ ;
    }

    if (add_inplace == NULL) {
        PyErr_SetString(PyExc_TypeError, "unsupported type for a");
        return NULL;
    }

    mit = (PyArrayMapIterObject *) PyArray_MapIterArray(a, index);
    if (mit == NULL) {
        goto fail;
    }

    if (map_increment(mit, inc, add_inplace) != 0) {
        goto fail;
    }

    Py_DECREF(mit);

    Py_INCREF(Py_None);
    return Py_None;

fail:
    Py_XDECREF(mit);

    return NULL;
}


#if !defined(NPY_PY3K)
static PyObject *
int_subclass(PyObject *dummy, PyObject *args)
{

  PyObject *result = NULL;
  PyObject *scalar_object = NULL;

  if (!PyArg_UnpackTuple(args, "test_int_subclass", 1, 1, &scalar_object))
    return NULL;

  if (PyInt_Check(scalar_object))
    result = Py_True;
  else
    result = Py_False;

  Py_INCREF(result);

  return result;

}
#endif


/*
 * Create python string from a FLAG and or the corresponding PyBuf flag
 * for the use in get_buffer_info.
 */
#define GET_PYBUF_FLAG(FLAG)                                        \
    buf_flag = PyUnicode_FromString(#FLAG);                         \
    flag_matches = PyObject_RichCompareBool(buf_flag, tmp, Py_EQ);  \
    Py_DECREF(buf_flag);                                            \
    if (flag_matches == 1) {                                        \
        Py_DECREF(tmp);                                             \
        flags |= PyBUF_##FLAG;                                      \
        continue;                                                   \
    }                                                               \
    else if (flag_matches == -1) {                                  \
        Py_DECREF(tmp);                                             \
        return NULL;                                                \
    }


/*
 * Get information for a buffer through PyBuf_GetBuffer with the
 * corresponding flags or'ed. Note that the python caller has to
 * make sure that or'ing those flags actually makes sense.
 * More information should probably be returned for future tests.
 */
static PyObject *
get_buffer_info(PyObject *NPY_UNUSED(self), PyObject *args)
{
    PyObject *buffer_obj, *pyflags;
    PyObject *tmp, *buf_flag;
    Py_buffer buffer;
    PyObject *shape, *strides;
    Py_ssize_t i, n;
    int flag_matches;
    int flags = 0;

    if (!PyArg_ParseTuple(args, "OO", &buffer_obj, &pyflags)) {
        return NULL;
    }

    n = PySequence_Length(pyflags);
    if (n < 0) {
        return NULL;
    }

    for (i=0; i < n; i++) {
        tmp = PySequence_GetItem(pyflags, i);
        if (tmp == NULL) {
            return NULL;
        }

        GET_PYBUF_FLAG(SIMPLE);
        GET_PYBUF_FLAG(WRITABLE);
        GET_PYBUF_FLAG(STRIDES);
        GET_PYBUF_FLAG(ND);
        GET_PYBUF_FLAG(C_CONTIGUOUS);
        GET_PYBUF_FLAG(F_CONTIGUOUS);
        GET_PYBUF_FLAG(ANY_CONTIGUOUS);
        GET_PYBUF_FLAG(INDIRECT);
        GET_PYBUF_FLAG(FORMAT);
        GET_PYBUF_FLAG(STRIDED);
        GET_PYBUF_FLAG(STRIDED_RO);
        GET_PYBUF_FLAG(RECORDS);
        GET_PYBUF_FLAG(RECORDS_RO);
        GET_PYBUF_FLAG(FULL);
        GET_PYBUF_FLAG(FULL_RO);
        GET_PYBUF_FLAG(CONTIG);
        GET_PYBUF_FLAG(CONTIG_RO);

        Py_DECREF(tmp);

        /* One of the flags must match */
        PyErr_SetString(PyExc_ValueError, "invalid flag used.");
        return NULL;
    }

    if (PyObject_GetBuffer(buffer_obj, &buffer, flags) < 0) {
        return NULL;
    }

    if (buffer.shape == NULL) {
        Py_INCREF(Py_None);
        shape = Py_None;
    }
    else {
        shape = PyTuple_New(buffer.ndim);
        for (i=0; i < buffer.ndim; i++) {
            PyTuple_SET_ITEM(shape, i, PyLong_FromSsize_t(buffer.shape[i]));
        }
    }

    if (buffer.strides == NULL) {
        Py_INCREF(Py_None);
        strides = Py_None;
    }
    else {
        strides = PyTuple_New(buffer.ndim);
        for (i=0; i < buffer.ndim; i++) {
            PyTuple_SET_ITEM(strides, i, PyLong_FromSsize_t(buffer.strides[i]));
        }
    }

    PyBuffer_Release(&buffer);
    return Py_BuildValue("(NN)", shape, strides);
}

#undef GET_PYBUF_FLAG


/*
 * Test C-api level item getting.
 */
static PyObject *
array_indexing(PyObject *NPY_UNUSED(self), PyObject *args)
{
    int mode;
    Py_ssize_t i;
    PyObject *arr, *op = NULL;

    if (!PyArg_ParseTuple(args, "iOn|O", &mode, &arr, &i, &op)) {
        return NULL;
    }

    if (mode == 0) {
        return PySequence_GetItem(arr, i);
    }
    if (mode == 1) {
        if (PySequence_SetItem(arr, i, op) < 0) {
            return NULL;
        }        
        Py_RETURN_NONE;
    }

    PyErr_SetString(PyExc_ValueError,
                    "invalid mode. 0: item 1: assign");
    return NULL;
}


/*
 * Test nditer of too large arrays using remove axis, etc.
 */
static PyObject *
test_nditer_too_large(PyObject *NPY_UNUSED(self), PyObject *args) {
    NpyIter *iter;
    PyObject *array_tuple, *arr;
    PyArrayObject *arrays[NPY_MAXARGS];
    npy_uint32 op_flags[NPY_MAXARGS];
    Py_ssize_t nop;
    int i, axis, mode;

    npy_intp index[NPY_MAXARGS] = {0};
    char *msg;

    if (!PyArg_ParseTuple(args, "Oii", &array_tuple, &axis, &mode)) {
        return NULL;
    }

    if (!PyTuple_CheckExact(array_tuple)) {
        PyErr_SetString(PyExc_ValueError, "tuple required as first argument");
        return NULL;
    }
    nop = PyTuple_Size(array_tuple);
    if (nop > NPY_MAXARGS) {
        PyErr_SetString(PyExc_ValueError, "tuple must be smaller then maxargs");
        return NULL;
    }

    for (i=0; i < nop; i++) {
        arr = PyTuple_GET_ITEM(array_tuple, i);
        if (!PyArray_CheckExact(arr)) {
            PyErr_SetString(PyExc_ValueError, "require base class ndarray");
            return NULL;
        }
        arrays[i] = (PyArrayObject *)arr;
        op_flags[i] = NPY_ITER_READONLY;
    }

    iter = NpyIter_MultiNew(nop, arrays, NPY_ITER_MULTI_INDEX | NPY_ITER_RANGED,
                            NPY_KEEPORDER, NPY_NO_CASTING, op_flags, NULL);

    if (iter == NULL) {
        return NULL;
    }

    /* Remove an axis (negative, do not remove any) */
    if (axis >= 0) {
        if (!NpyIter_RemoveAxis(iter, axis)) {
            goto fail;
        }
    }

    switch (mode) {
        /* Test IterNext getting */
        case 0:
            if (NpyIter_GetIterNext(iter, NULL) == NULL) {
                goto fail;
            }
            break;
        case 1:
            if (NpyIter_GetIterNext(iter, &msg) == NULL) {
                PyErr_SetString(PyExc_ValueError, msg);
                goto fail;
            }
            break;
        /* Test Multi Index removal */
        case 2:
            if (!NpyIter_RemoveMultiIndex(iter)) {
                goto fail;
            }
            break;
        /* Test GotoMultiIndex (just 0 hardcoded) */
        case 3:
            if (!NpyIter_GotoMultiIndex(iter, index)) {
                goto fail;
            }
            break;
        /* Test setting iterrange (hardcoded range of 0, 1) */
        case 4:
            if (!NpyIter_ResetToIterIndexRange(iter, 0, 1, NULL)) {
                goto fail;
            }
            break;
        case 5:
            if (!NpyIter_ResetToIterIndexRange(iter, 0, 1, &msg)) {
                PyErr_SetString(PyExc_ValueError, msg);
                goto fail;
            }
            break;
        /* Do nothing */
        default:
            break;
    }

    NpyIter_Deallocate(iter);
    Py_RETURN_NONE;
  fail:
    NpyIter_Deallocate(iter);
    return NULL;
}


static PyMethodDef Multiarray_TestsMethods[] = {
    {"test_neighborhood_iterator",
        test_neighborhood_iterator,
        METH_VARARGS, NULL},
    {"test_neighborhood_iterator_oob",
        test_neighborhood_iterator_oob,
        METH_VARARGS, NULL},
    {"test_pydatamem_seteventhook_start",
        test_pydatamem_seteventhook_start,
        METH_NOARGS, NULL},
    {"test_pydatamem_seteventhook_end",
        test_pydatamem_seteventhook_end,
        METH_NOARGS, NULL},
    {"test_inplace_increment",
        inplace_increment,
        METH_VARARGS, NULL},
#if !defined(NPY_PY3K)
    {"test_int_subclass",
        int_subclass,
        METH_VARARGS, NULL},
#endif
    {"get_buffer_info",
        get_buffer_info,
        METH_VARARGS, NULL},
    {"array_indexing",
        array_indexing,
        METH_VARARGS, NULL},
    {"test_nditer_too_large",
        test_nditer_too_large,
        METH_VARARGS, NULL},
    {NULL, NULL, 0, NULL}        /* Sentinel */
};


#if defined(NPY_PY3K)
static struct PyModuleDef moduledef = {
        PyModuleDef_HEAD_INIT,
        "multiarray_tests",
        NULL,
        -1,
        Multiarray_TestsMethods,
        NULL,
        NULL,
        NULL,
        NULL
};
#endif

#if defined(NPY_PY3K)
#define RETVAL m
PyMODINIT_FUNC PyInit_multiarray_tests(void)
#else
#define RETVAL
PyMODINIT_FUNC
initmultiarray_tests(void)
#endif
{
    PyObject *m;

#if defined(NPY_PY3K)
    m = PyModule_Create(&moduledef);
#else
    m = Py_InitModule("multiarray_tests", Multiarray_TestsMethods);
#endif
    if (m == NULL) {
        return RETVAL;
    }
    import_array();
    if (PyErr_Occurred()) {
        PyErr_SetString(PyExc_RuntimeError,
                        "cannot load umath_tests module.");
    }
    return RETVAL;
}
