#define NPY_NO_DEPRECATED_API NPY_API_VERSION
#define _MULTIARRAYMODULE

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <structmember.h>

#include "numpy/arrayobject.h"
#include "numpy/arrayscalars.h"
#include "numpy/npy_math.h"

#include "npy_config.h"
#include "arraywrap.h"
#include "ctors.h"
#include "shape.h"
#include "npy_static_data.h" /* for interned strings */
#include "templ_common.h" /* for npy_mul_sizes_with_overflow */
#include "common.h" /* for convert_shape_to_string */
#include "alloc.h"
#include "refcount.h"

static int
_fix_unknown_dimension(PyArray_Dims *newshape, PyArrayObject *arr);

static int
_attempt_nocopy_reshape(PyArrayObject *self, int newnd, const npy_intp *newdims,
                        npy_intp *newstrides, int is_f_order);

/*NUMPY_API
 * Resize (reallocate data).  Only works if nothing else is referencing this
 * array and it is contiguous.  If refcheck is 0, then the reference count is
 * not checked and assumed to be 1.  You still must own this data and have no
 * weak-references and no base object.
 */
NPY_NO_EXPORT PyObject *
PyArray_Resize(PyArrayObject *self, PyArray_Dims *newshape, int refcheck,
               NPY_ORDER NPY_UNUSED(order))
{
    npy_intp oldnbytes, newnbytes;
    npy_intp oldsize, newsize;
    int new_nd=newshape->len, k, elsize;
    int refcnt;
    npy_intp* new_dimensions=newshape->ptr;
    npy_intp new_strides[NPY_MAXDIMS];
    npy_intp *dimptr;
    char *new_data;

    if (!PyArray_ISONESEGMENT(self)) {
        PyErr_SetString(PyExc_ValueError,
                "resize only works on single-segment arrays");
        return NULL;
    }

    /* Compute total size of old and new arrays. The new size might overflow */
    oldsize = PyArray_SIZE(self);
    newsize = 1;
    for(k = 0; k < new_nd; k++) {
        if (new_dimensions[k] == 0) {
            newsize = 0;
            break;
        }
        if (new_dimensions[k] < 0) {
            PyErr_SetString(PyExc_ValueError,
                    "negative dimensions not allowed");
            return NULL;
        }
        if (npy_mul_sizes_with_overflow(&newsize, newsize, new_dimensions[k])) {
            return PyErr_NoMemory();
        }
    }

    /* Convert to number of bytes. The new count might overflow */
    elsize = PyArray_ITEMSIZE(self);
    oldnbytes = oldsize * elsize;
    if (npy_mul_sizes_with_overflow(&newnbytes, newsize, elsize)) {
        return PyErr_NoMemory();
    }

    if (oldnbytes != newnbytes) {
        if (!(PyArray_FLAGS(self) & NPY_ARRAY_OWNDATA)) {
            PyErr_SetString(PyExc_ValueError,
                    "cannot resize this array: it does not own its data");
            return NULL;
        }

        if (PyArray_BASE(self) != NULL
              || (((PyArrayObject_fields *)self)->weakreflist != NULL)) {
            PyErr_SetString(PyExc_ValueError,
                    "cannot resize an array that "
                    "references or is referenced\n"
                    "by another array in this way. Use the np.resize function.");
            return NULL;
        }
        if (refcheck) {
#ifdef PYPY_VERSION
            PyErr_SetString(PyExc_ValueError,
                    "cannot resize an array with refcheck=True on PyPy.\n"
                    "Use the np.resize function or refcheck=False");
            return NULL;
#else
            refcnt = Py_REFCNT(self);
#endif /* PYPY_VERSION */
        }
        else {
            refcnt = 1;
        }
        if (refcnt > 2) {
            PyErr_SetString(PyExc_ValueError,
                    "cannot resize an array that "
                    "references or is referenced\n"
                    "by another array in this way.\n"
                    "Use the np.resize function or refcheck=False");
            return NULL;
        }

        /* Reallocate space if needed - allocating 0 is forbidden */
        PyObject *handler = PyArray_HANDLER(self);
        if (handler == NULL) {
            /* This can happen if someone arbitrarily sets NPY_ARRAY_OWNDATA */
            PyErr_SetString(PyExc_RuntimeError,
                            "no memory handler found but OWNDATA flag set");
            return NULL;
        }
        new_data = PyDataMem_UserRENEW(PyArray_DATA(self),
                                       newnbytes == 0 ? elsize : newnbytes,
                                       handler);
        if (new_data == NULL) {
            PyErr_SetString(PyExc_MemoryError,
                    "cannot allocate memory for array");
            return NULL;
        }
        ((PyArrayObject_fields *)self)->data = new_data;
    }

    if (newnbytes > oldnbytes && PyArray_ISWRITEABLE(self)) {
        /* Fill new memory with zeros (PyLong zero for object arrays) */
        npy_intp stride = elsize;
        npy_intp size = newsize - oldsize;
        char *data = PyArray_BYTES(self) + oldnbytes;
        int aligned = PyArray_ISALIGNED(self);
        if (PyArray_ZeroContiguousBuffer(PyArray_DESCR(self), data,
                                         stride, size, aligned) < 0) {
            return NULL;
        }
    }

    if (new_nd > 0) {
        if (PyArray_NDIM(self) != new_nd) {
            /* Different number of dimensions. */
            ((PyArrayObject_fields *)self)->nd = new_nd;
            /* Need new dimensions and strides arrays */
            dimptr = PyDimMem_RENEW(PyArray_DIMS(self), 3*new_nd);
            if (dimptr == NULL) {
                PyErr_SetString(PyExc_MemoryError,
                                "cannot allocate memory for array");
                return NULL;
            }
            ((PyArrayObject_fields *)self)->dimensions = dimptr;
            ((PyArrayObject_fields *)self)->strides = dimptr + new_nd;
        }
        /* make new_strides variable */
        _array_fill_strides(new_strides, new_dimensions, new_nd,
                            PyArray_ITEMSIZE(self), PyArray_FLAGS(self),
                            &(((PyArrayObject_fields *)self)->flags));
        memmove(PyArray_DIMS(self), new_dimensions, new_nd*sizeof(npy_intp));
        memmove(PyArray_STRIDES(self), new_strides, new_nd*sizeof(npy_intp));
    }
    else {
        PyDimMem_FREE(((PyArrayObject_fields *)self)->dimensions);
        ((PyArrayObject_fields *)self)->nd = 0;
        ((PyArrayObject_fields *)self)->dimensions = NULL;
        ((PyArrayObject_fields *)self)->strides = NULL;
    }
    Py_RETURN_NONE;
}

/*
 * Returns a new array
 * with the new shape from the data
 * in the old array --- order-perspective depends on order argument.
 * copy-only-if-necessary
 */

/*NUMPY_API
 * New shape for an array
 */
NPY_NO_EXPORT PyObject *
PyArray_Newshape(PyArrayObject *self, PyArray_Dims *newdims,
                 NPY_ORDER order)
{
    return _reshape_with_copy_arg(self, newdims, order, NPY_COPY_IF_NEEDED);
}


NPY_NO_EXPORT PyObject *
_reshape_with_copy_arg(PyArrayObject *array, PyArray_Dims *newdims,
                       NPY_ORDER order, NPY_COPYMODE copy)
{
    npy_intp i;
    npy_intp *dimensions = newdims->ptr;
    PyArrayObject *ret;
    int ndim = newdims->len;
    npy_bool same;
    npy_intp *strides = NULL;
    npy_intp newstrides[NPY_MAXDIMS];
    int flags;

    if (order == NPY_ANYORDER) {
        order = PyArray_ISFORTRAN(array) ? NPY_FORTRANORDER : NPY_CORDER;
    }
    else if (order == NPY_KEEPORDER) {
        PyErr_SetString(PyExc_ValueError,
                "order 'K' is not permitted for reshaping");
        return NULL;
    }
    /*  Quick check to make sure anything actually needs to be done */
    if (ndim == PyArray_NDIM(array) && copy != NPY_COPY_ALWAYS) {
        same = NPY_TRUE;
        i = 0;
        while (same && i < ndim) {
            if (PyArray_DIM(array, i) != dimensions[i]) {
                same=NPY_FALSE;
            }
            i++;
        }
        if (same) {
            return PyArray_View(array, NULL, NULL);
        }
    }

    /*
     * fix any -1 dimensions and check new-dimensions against old size
     */
    if (_fix_unknown_dimension(newdims, array) < 0) {
        return NULL;
    }
    /*
     * Memory order doesn't depend on a copy/no-copy context.
     * 'order' argument is always honored.
     */
    if (copy == NPY_COPY_ALWAYS) {
        PyObject *newcopy = PyArray_NewCopy(array, order);
        if (newcopy == NULL) {
            return NULL;
        }
        array = (PyArrayObject *)newcopy;
    }
    else {
        /*
         * sometimes we have to create a new copy of the array
         * in order to get the right orientation and
         * because we can't just reuse the buffer with the
         * data in the order it is in.
         */
        Py_INCREF(array);
        if (((order == NPY_CORDER && !PyArray_IS_C_CONTIGUOUS(array)) ||
                (order == NPY_FORTRANORDER && !PyArray_IS_F_CONTIGUOUS(array)))) {
            int success = 0;
            success = _attempt_nocopy_reshape(array, ndim, dimensions,
                                              newstrides, order);
            if (success) {
                /* no need to copy the array after all */
                strides = newstrides;
            }
            else if (copy == NPY_COPY_NEVER) {
                PyErr_SetString(PyExc_ValueError,
                                "Unable to avoid creating a copy while reshaping.");
                Py_DECREF(array);
                return NULL;
            }
            else {
                PyObject *newcopy = PyArray_NewCopy(array, order);
                Py_DECREF(array);
                if (newcopy == NULL) {
                    return NULL;
                }
                array = (PyArrayObject *)newcopy;
            }
        }
    }
    /* We always have to interpret the contiguous buffer correctly */

    /* Make sure the flags argument is set. */
    flags = PyArray_FLAGS(array);
    if (ndim > 1) {
        if (order == NPY_FORTRANORDER) {
            flags &= ~NPY_ARRAY_C_CONTIGUOUS;
            flags |= NPY_ARRAY_F_CONTIGUOUS;
        }
        else {
            flags &= ~NPY_ARRAY_F_CONTIGUOUS;
            flags |= NPY_ARRAY_C_CONTIGUOUS;
        }
    }

    Py_INCREF(PyArray_DESCR(array));
    ret = (PyArrayObject *)PyArray_NewFromDescr_int(
            Py_TYPE(array), PyArray_DESCR(array),
            ndim, dimensions, strides, PyArray_DATA(array),
            flags, (PyObject *)array, (PyObject *)array,
            _NPY_ARRAY_ENSURE_DTYPE_IDENTITY);
    Py_DECREF(array);
    return (PyObject *)ret;
}


/* For backward compatibility -- Not recommended */

/*NUMPY_API
 * Reshape
 */
NPY_NO_EXPORT PyObject *
PyArray_Reshape(PyArrayObject *self, PyObject *shape)
{
    PyObject *ret;
    PyArray_Dims newdims;

    if (!PyArray_IntpConverter(shape, &newdims)) {
        return NULL;
    }
    ret = PyArray_Newshape(self, &newdims, NPY_CORDER);
    npy_free_cache_dim_obj(newdims);
    return ret;
}


/*
 * attempt to reshape an array without copying data
 *
 * The requested newdims are not checked, but must be compatible with
 * the size of self, which must be non-zero. Other than that this
 * function should correctly handle all reshapes, including axes of
 * length 1. Zero strides should work but are untested.
 *
 * If a copy is needed, returns 0
 * If no copy is needed, returns 1 and fills newstrides
 *     with appropriate strides
 *
 * The "is_f_order" argument describes how the array should be viewed
 * during the reshape, not how it is stored in memory (that
 * information is in PyArray_STRIDES(self)).
 *
 * If some output dimensions have length 1, the strides assigned to
 * them are arbitrary. In the current implementation, they are the
 * stride of the next-fastest index.
 */
static int
_attempt_nocopy_reshape(PyArrayObject *self, int newnd, const npy_intp *newdims,
                        npy_intp *newstrides, int is_f_order)
{
    int oldnd;
    npy_intp olddims[NPY_MAXDIMS];
    npy_intp oldstrides[NPY_MAXDIMS];
    npy_intp last_stride;
    int oi, oj, ok, ni, nj, nk;

    oldnd = 0;
    /*
     * Remove axes with dimension 1 from the old array. They have no effect
     * but would need special cases since their strides do not matter.
     */
    for (oi = 0; oi < PyArray_NDIM(self); oi++) {
        if (PyArray_DIMS(self)[oi]!= 1) {
            olddims[oldnd] = PyArray_DIMS(self)[oi];
            oldstrides[oldnd] = PyArray_STRIDES(self)[oi];
            oldnd++;
        }
    }

    /* oi to oj and ni to nj give the axis ranges currently worked with */
    oi = 0;
    oj = 1;
    ni = 0;
    nj = 1;
    while (ni < newnd && oi < oldnd) {
        npy_intp np = newdims[ni];
        npy_intp op = olddims[oi];

        while (np != op) {
            if (np < op) {
                /* Misses trailing 1s, these are handled later */
                np *= newdims[nj++];
            } else {
                op *= olddims[oj++];
            }
        }

        /* Check whether the original axes can be combined */
        for (ok = oi; ok < oj - 1; ok++) {
            if (is_f_order) {
                if (oldstrides[ok+1] != olddims[ok]*oldstrides[ok]) {
                     /* not contiguous enough */
                    return 0;
                }
            }
            else {
                /* C order */
                if (oldstrides[ok] != olddims[ok+1]*oldstrides[ok+1]) {
                    /* not contiguous enough */
                    return 0;
                }
            }
        }

        /* Calculate new strides for all axes currently worked with */
        if (is_f_order) {
            newstrides[ni] = oldstrides[oi];
            for (nk = ni + 1; nk < nj; nk++) {
                newstrides[nk] = newstrides[nk - 1]*newdims[nk - 1];
            }
        }
        else {
            /* C order */
            newstrides[nj - 1] = oldstrides[oj - 1];
            for (nk = nj - 1; nk > ni; nk--) {
                newstrides[nk - 1] = newstrides[nk]*newdims[nk];
            }
        }
        ni = nj++;
        oi = oj++;
    }

    /*
     * Set strides corresponding to trailing 1s of the new shape.
     */
    if (ni >= 1) {
        last_stride = newstrides[ni - 1];
    }
    else {
        last_stride = PyArray_ITEMSIZE(self);
    }
    if (is_f_order) {
        last_stride *= newdims[ni - 1];
    }
    for (nk = ni; nk < newnd; nk++) {
        newstrides[nk] = last_stride;
    }

    return 1;
}

static void
raise_reshape_size_mismatch(PyArray_Dims *newshape, PyArrayObject *arr)
{
    PyObject *tmp = convert_shape_to_string(newshape->len, newshape->ptr, "");
    if (tmp != NULL) {
        PyErr_Format(PyExc_ValueError,
                "cannot reshape array of size %zd into shape %S",
                PyArray_SIZE(arr), tmp);
        Py_DECREF(tmp);
    }
}

static int
_fix_unknown_dimension(PyArray_Dims *newshape, PyArrayObject *arr)
{
    npy_intp *dimensions;
    npy_intp s_original = PyArray_SIZE(arr);
    npy_intp i_unknown, s_known;
    int i, n;

    dimensions = newshape->ptr;
    n = newshape->len;
    s_known = 1;
    i_unknown = -1;

    for (i = 0; i < n; i++) {
        if (dimensions[i] < 0) {
            if (i_unknown == -1) {
                i_unknown = i;
            }
            else {
                PyErr_SetString(PyExc_ValueError,
                                "can only specify one unknown dimension");
                return -1;
            }
        }
        else if (npy_mul_sizes_with_overflow(&s_known, s_known,
                                            dimensions[i])) {
            raise_reshape_size_mismatch(newshape, arr);
            return -1;
        }
    }

    if (i_unknown >= 0) {
        if (s_known == 0 || s_original % s_known != 0) {
            raise_reshape_size_mismatch(newshape, arr);
            return -1;
        }
        dimensions[i_unknown] = s_original / s_known;
    }
    else {
        if (s_original != s_known) {
            raise_reshape_size_mismatch(newshape, arr);
            return -1;
        }
    }
    return 0;
}

/*NUMPY_API
 *
 * return a new view of the array object with all of its unit-length
 * dimensions squeezed out if needed, otherwise
 * return the same array.
 */
NPY_NO_EXPORT PyObject *
PyArray_Squeeze(PyArrayObject *self)
{
    PyArrayObject *ret;
    npy_bool unit_dims[NPY_MAXDIMS];
    int idim, ndim, any_ones;
    npy_intp *shape;

    ndim = PyArray_NDIM(self);
    shape = PyArray_SHAPE(self);

    any_ones = 0;
    for (idim = 0; idim < ndim; ++idim) {
        if (shape[idim] == 1) {
            unit_dims[idim] = 1;
            any_ones = 1;
        }
        else {
            unit_dims[idim] = 0;
        }
    }

    /* If there were no ones to squeeze out, return the same array */
    if (!any_ones) {
        Py_INCREF(self);
        return (PyObject *)self;
    }

    ret = (PyArrayObject *)PyArray_View(self, NULL, &PyArray_Type);
    if (ret == NULL) {
        return NULL;
    }

    PyArray_RemoveAxesInPlace(ret, unit_dims);

    /*
     * If self isn't not a base class ndarray, call its
     * __array_wrap__ method
     */
    if (Py_TYPE(self) != &PyArray_Type) {
        PyObject *wrapped = npy_apply_wrap_simple(self, ret);
        Py_DECREF(ret);
        return wrapped;
    }

    return (PyObject *)ret;
}

/*
 * Just like PyArray_Squeeze, but allows the caller to select
 * a subset of the size-one dimensions to squeeze out.
 */
NPY_NO_EXPORT PyObject *
PyArray_SqueezeSelected(PyArrayObject *self, npy_bool *axis_flags)
{
    PyArrayObject *ret;
    int idim, ndim, any_ones;
    npy_intp *shape;

    ndim = PyArray_NDIM(self);
    shape = PyArray_SHAPE(self);

    /* Verify that the axes requested are all of size one */
    any_ones = 0;
    for (idim = 0; idim < ndim; ++idim) {
        if (axis_flags[idim] != 0) {
            if (shape[idim] == 1) {
                any_ones = 1;
            }
            else {
                PyErr_SetString(PyExc_ValueError,
                        "cannot select an axis to squeeze out "
                        "which has size not equal to one");
                return NULL;
            }
        }
    }

    /* If there were no axes to squeeze out, return the same array */
    if (!any_ones) {
        Py_INCREF(self);
        return (PyObject *)self;
    }

    ret = (PyArrayObject *)PyArray_View(self, NULL, &PyArray_Type);
    if (ret == NULL) {
        return NULL;
    }

    PyArray_RemoveAxesInPlace(ret, axis_flags);

    /*
     * If self isn't not a base class ndarray, call its
     * __array_wrap__ method
     */
    if (Py_TYPE(self) != &PyArray_Type) {
        PyObject *wrapped = npy_apply_wrap_simple(self, ret);
        Py_DECREF(ret);
        return wrapped;
    }

    return (PyObject *)ret;
}

/*NUMPY_API
 * SwapAxes
 */
NPY_NO_EXPORT PyObject *
PyArray_SwapAxes(PyArrayObject *ap, int a1, int a2)
{
    PyArray_Dims new_axes;
    npy_intp dims[NPY_MAXDIMS];
    int n = PyArray_NDIM(ap);
    int i;

    if (check_and_adjust_axis_msg(&a1, n, npy_interned_str.axis1) < 0) {
        return NULL;
    }
    if (check_and_adjust_axis_msg(&a2, n, npy_interned_str.axis2) < 0) {
        return NULL;
    }

    for (i = 0; i < n; ++i) {
        dims[i] = i;
    }
    dims[a1] = a2;
    dims[a2] = a1;

    new_axes.ptr = dims;
    new_axes.len = n;

    return PyArray_Transpose(ap, &new_axes);
}


/*NUMPY_API
 * Return Transpose.
 */
NPY_NO_EXPORT PyObject *
PyArray_Transpose(PyArrayObject *ap, PyArray_Dims *permute)
{
    npy_intp *axes;
    int i, n;
    int permutation[NPY_MAXDIMS], reverse_permutation[NPY_MAXDIMS];
    PyArrayObject *ret = NULL;
    int flags;

    if (permute == NULL) {
        n = PyArray_NDIM(ap);
        for (i = 0; i < n; i++) {
            permutation[i] = n-1-i;
        }
    }
    else {
        n = permute->len;
        axes = permute->ptr;
        if (n != PyArray_NDIM(ap)) {
            PyErr_SetString(PyExc_ValueError,
                            "axes don't match array");
            return NULL;
        }
        for (i = 0; i < n; i++) {
            reverse_permutation[i] = -1;
        }
        for (i = 0; i < n; i++) {
            int axis = axes[i];
            if (check_and_adjust_axis(&axis, PyArray_NDIM(ap)) < 0) {
                return NULL;
            }
            if (reverse_permutation[axis] != -1) {
                PyErr_SetString(PyExc_ValueError,
                                "repeated axis in transpose");
                return NULL;
            }
            reverse_permutation[axis] = i;
            permutation[i] = axis;
        }
    }

    flags = PyArray_FLAGS(ap);

    /*
     * this allocates memory for dimensions and strides (but fills them
     * incorrectly), sets up descr, and points data at PyArray_DATA(ap).
     */
    Py_INCREF(PyArray_DESCR(ap));
    ret = (PyArrayObject *) PyArray_NewFromDescrAndBase(
            Py_TYPE(ap), PyArray_DESCR(ap),
            n, PyArray_DIMS(ap), NULL, PyArray_DATA(ap),
            flags, (PyObject *)ap, (PyObject *)ap);
    if (ret == NULL) {
        return NULL;
    }

    /* fix the dimensions and strides of the return-array */
    for (i = 0; i < n; i++) {
        PyArray_DIMS(ret)[i] = PyArray_DIMS(ap)[permutation[i]];
        PyArray_STRIDES(ret)[i] = PyArray_STRIDES(ap)[permutation[i]];
    }
    PyArray_UpdateFlags(ret, NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_F_CONTIGUOUS |
                        NPY_ARRAY_ALIGNED);
    return (PyObject *)ret;
}

/*
 * Return matrix transpose (swap last two dimensions).
 */
NPY_NO_EXPORT PyObject *
PyArray_MatrixTranspose(PyArrayObject *ap)
{
    int ndim = PyArray_NDIM(ap);

    if (ndim < 2) {
        PyErr_SetString(PyExc_ValueError,
                        "matrix transpose with ndim < 2 is undefined");
        return NULL;
    }
    return PyArray_SwapAxes(ap, ndim - 2, ndim - 1);
}

/*
 * Sorts items so stride is descending, because C-order
 * is the default in the face of ambiguity.
 */
static int _npy_stride_sort_item_comparator(const void *a, const void *b)
{
    npy_intp astride = ((const npy_stride_sort_item *)a)->stride,
            bstride = ((const npy_stride_sort_item *)b)->stride;

    /* Sort the absolute value of the strides */
    if (astride < 0) {
        astride = -astride;
    }
    if (bstride < 0) {
        bstride = -bstride;
    }

    if (astride == bstride) {
        /*
         * Make the qsort stable by next comparing the perm order.
         * (Note that two perm entries will never be equal)
         */
        npy_intp aperm = ((const npy_stride_sort_item *)a)->perm,
                bperm = ((const npy_stride_sort_item *)b)->perm;
        return (aperm < bperm) ? -1 : 1;
    }
    if (astride > bstride) {
        return -1;
    }
    return 1;
}

/*NUMPY_API
 *
 * This function populates the first ndim elements
 * of strideperm with sorted descending by their absolute values.
 * For example, the stride array (4, -2, 12) becomes
 * [(2, 12), (0, 4), (1, -2)].
 */
NPY_NO_EXPORT void
PyArray_CreateSortedStridePerm(int ndim, npy_intp const *strides,
                        npy_stride_sort_item *out_strideperm)
{
    int i;

    /* Set up the strideperm values */
    for (i = 0; i < ndim; ++i) {
        out_strideperm[i].perm = i;
        out_strideperm[i].stride = strides[i];
    }

    /* Sort them */
    qsort(out_strideperm, ndim, sizeof(npy_stride_sort_item),
                                    &_npy_stride_sort_item_comparator);
}

static inline npy_intp
s_intp_abs(npy_intp x)
{
    return (x < 0) ? -x : x;
}



/*
 * Creates a sorted stride perm matching the KEEPORDER behavior
 * of the NpyIter object. Because this operates based on multiple
 * input strides, the 'stride' member of the npy_stride_sort_item
 * would be useless and we simply argsort a list of indices instead.
 *
 * The caller should have already validated that 'ndim' matches for
 * every array in the arrays list.
 */
NPY_NO_EXPORT void
PyArray_CreateMultiSortedStridePerm(int narrays, PyArrayObject **arrays,
                        int ndim, int *out_strideperm)
{
    int i0, i1, ipos, ax_j0, ax_j1, iarrays;

    /* Initialize the strideperm values to the identity. */
    for (i0 = 0; i0 < ndim; ++i0) {
        out_strideperm[i0] = i0;
    }

    /*
     * This is the same as the custom stable insertion sort in
     * the NpyIter object, but sorting in the reverse order as
     * in the iterator. The iterator sorts from smallest stride
     * to biggest stride (Fortran order), whereas here we sort
     * from biggest stride to smallest stride (C order).
     */
    for (i0 = 1; i0 < ndim; ++i0) {

        ipos = i0;
        ax_j0 = out_strideperm[i0];

        for (i1 = i0 - 1; i1 >= 0; --i1) {
            int ambig = 1, shouldswap = 0;

            ax_j1 = out_strideperm[i1];

            for (iarrays = 0; iarrays < narrays; ++iarrays) {
                if (PyArray_SHAPE(arrays[iarrays])[ax_j0] != 1 &&
                            PyArray_SHAPE(arrays[iarrays])[ax_j1] != 1) {
                    if (s_intp_abs(PyArray_STRIDES(arrays[iarrays])[ax_j0]) <=
                            s_intp_abs(PyArray_STRIDES(arrays[iarrays])[ax_j1])) {
                        /*
                         * Set swap even if it's not ambiguous already,
                         * because in the case of conflicts between
                         * different operands, C-order wins.
                         */
                        shouldswap = 0;
                    }
                    else {
                        /* Only set swap if it's still ambiguous */
                        if (ambig) {
                            shouldswap = 1;
                        }
                    }

                    /*
                     * A comparison has been done, so it's
                     * no longer ambiguous
                     */
                    ambig = 0;
                }
            }
            /*
             * If the comparison was unambiguous, either shift
             * 'ipos' to 'i1' or stop looking for an insertion point
             */
            if (!ambig) {
                if (shouldswap) {
                    ipos = i1;
                }
                else {
                    break;
                }
            }
        }

        /* Insert out_strideperm[i0] into the right place */
        if (ipos != i0) {
            for (i1 = i0; i1 > ipos; --i1) {
                out_strideperm[i1] = out_strideperm[i1-1];
            }
            out_strideperm[ipos] = ax_j0;
        }
    }
}

/*NUMPY_API
 * Ravel
 * Returns a contiguous array
 */
NPY_NO_EXPORT PyObject *
PyArray_Ravel(PyArrayObject *arr, NPY_ORDER order)
{
    PyArray_Dims newdim = {NULL,1};
    npy_intp val[1] = {-1};

    newdim.ptr = val;

    if (order == NPY_KEEPORDER) {
        /* This handles some corner cases, such as 0-d arrays as well */
        if (PyArray_IS_C_CONTIGUOUS(arr)) {
            order = NPY_CORDER;
        }
        else if (PyArray_IS_F_CONTIGUOUS(arr)) {
            order = NPY_FORTRANORDER;
        }
    }
    else if (order == NPY_ANYORDER) {
        order = PyArray_ISFORTRAN(arr) ? NPY_FORTRANORDER : NPY_CORDER;
    }

    if (order == NPY_CORDER && PyArray_IS_C_CONTIGUOUS(arr)) {
        return PyArray_Newshape(arr, &newdim, NPY_CORDER);
    }
    else if (order == NPY_FORTRANORDER && PyArray_IS_F_CONTIGUOUS(arr)) {
        return PyArray_Newshape(arr, &newdim, NPY_FORTRANORDER);
    }
    /* For KEEPORDER, check if we can make a flattened view */
    else if (order == NPY_KEEPORDER) {
        npy_stride_sort_item strideperm[NPY_MAXDIMS];
        npy_intp stride;
        int i, ndim = PyArray_NDIM(arr);

        PyArray_CreateSortedStridePerm(PyArray_NDIM(arr),
                                PyArray_STRIDES(arr), strideperm);

        /* The output array must be contiguous, so the first stride is fixed */
        stride = PyArray_ITEMSIZE(arr);

        for (i = ndim-1; i >= 0; --i) {
            if (PyArray_DIM(arr, strideperm[i].perm) == 1) {
                /* A size one dimension does not matter */
                continue;
            }
            if (strideperm[i].stride != stride) {
                break;
            }
            stride *= PyArray_DIM(arr, strideperm[i].perm);
        }

        /* If all the strides matched a contiguous layout, return a view */
        if (i < 0) {
            stride = PyArray_ITEMSIZE(arr);
            val[0] = PyArray_SIZE(arr);

            Py_INCREF(PyArray_DESCR(arr));
            return PyArray_NewFromDescrAndBase(
                    Py_TYPE(arr), PyArray_DESCR(arr),
                    1, val, &stride, PyArray_BYTES(arr),
                    PyArray_FLAGS(arr), (PyObject *)arr, (PyObject *)arr);
        }
    }

    return PyArray_Flatten(arr, order);
}

/*NUMPY_API
 * Flatten
 */
NPY_NO_EXPORT PyObject *
PyArray_Flatten(PyArrayObject *a, NPY_ORDER order)
{
    PyArrayObject *ret;
    npy_intp size;

    if (order == NPY_ANYORDER) {
        order = PyArray_ISFORTRAN(a) ? NPY_FORTRANORDER : NPY_CORDER;
    }

    size = PyArray_SIZE(a);
    Py_INCREF(PyArray_DESCR(a));
    ret = (PyArrayObject *)PyArray_NewFromDescr(Py_TYPE(a),
                               PyArray_DESCR(a),
                               1, &size,
                               NULL,
                               NULL,
                               0, (PyObject *)a);
    if (ret == NULL) {
        return NULL;
    }

    if (PyArray_CopyAsFlat(ret, a, order) < 0) {
        Py_DECREF(ret);
        return NULL;
    }
    return (PyObject *)ret;
}


/*NUMPY_API
 *
 * Removes the axes flagged as True from the array,
 * modifying it in place. If an axis flagged for removal
 * has a shape entry bigger than one, this effectively selects
 * index zero for that axis.
 *
 * WARNING: If an axis flagged for removal has a shape equal to zero,
 *          the array will point to invalid memory. The caller must
 *          validate this!
 *          If an axis flagged for removal has a shape larger than one,
 *          the aligned flag (and in the future the contiguous flags),
 *          may need explicit update.
 *
 * For example, this can be used to remove the reduction axes
 * from a reduction result once its computation is complete.
 */
NPY_NO_EXPORT void
PyArray_RemoveAxesInPlace(PyArrayObject *arr, const npy_bool *flags)
{
    PyArrayObject_fields *fa = (PyArrayObject_fields *)arr;
    npy_intp *shape = fa->dimensions, *strides = fa->strides;
    int idim, ndim = fa->nd, idim_out = 0;

    /* Compress the dimensions and strides */
    for (idim = 0; idim < ndim; ++idim) {
        if (!flags[idim]) {
            shape[idim_out] = shape[idim];
            strides[idim_out] = strides[idim];
            ++idim_out;
        }
    }

    /* The final number of dimensions */
    fa->nd = idim_out;

    /* NOTE: This is only necessary if a dimension with size != 1 was removed */
    PyArray_UpdateFlags(arr, NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_F_CONTIGUOUS);
}
