"""Python version-independent methods for C/Python buffers.

This module was copied and dapted from mpi4py:

Jul 23, 2010 18:00 PST (r539)
http://code.google.com/p/mpi4py/source/browse/trunk/src/MPI/asbuffer.pxi
Copyright (c) 2009, Lisandro Dalcin.
All rights reserved.
BSD License: http://www.opensource.org/licenses/bsd-license.php

Code not from mpi4py:

Authors
-------
* MinRK
"""

#-----------------------------------------------------------------------------
# Python includes.
#-----------------------------------------------------------------------------

# get version-independent aliases:
cdef extern from "pyversion_compat.h":
    pass

# Python 3 buffer interface (PEP 3118)
cdef extern from "Python.h":
    int PY_MAJOR_VERSION
    int PY_MINOR_VERSION
    ctypedef int Py_ssize_t
    ctypedef struct PyMemoryViewObject:
        pass
    ctypedef struct Py_buffer:
        void *buf
        Py_ssize_t len
        Py_ssize_t itemsize
        char *format
        Py_ssize_t *shape
    cdef enum:
        PyBUF_SIMPLE
        PyBUF_WRITABLE
        PyBUF_FORMAT
        PyBUF_ANY_CONTIGUOUS
    int  PyObject_CheckBuffer(object)
    int  PyObject_GetBuffer(object, Py_buffer *, int) except -1
    void PyBuffer_Release(Py_buffer *)
    
    int PyBuffer_FillInfo(Py_buffer *view, object obj, void *buf,
                Py_ssize_t len, int readonly, int infoflags) except -1
    object PyMemoryView_FromBuffer(Py_buffer *info)
    
    object PyMemoryView_FromObject(object)

# Python 2 buffer interface (legacy)
cdef extern from "Python.h":
    ctypedef void const_void "const void"
    Py_ssize_t Py_END_OF_BUFFER
    int PyObject_CheckReadBuffer(object)
    int PyObject_AsReadBuffer (object, const_void **, Py_ssize_t *) except -1
    int PyObject_AsWriteBuffer(object, void **, Py_ssize_t *) except -1
    
    object PyBuffer_FromMemory(void *ptr, Py_ssize_t s)
    object PyBuffer_FromReadWriteMemory(void *ptr, Py_ssize_t s)

    object PyBuffer_FromObject(object, Py_ssize_t offset, Py_ssize_t size)
    object PyBuffer_FromReadWriteObject(object, Py_ssize_t offset, Py_ssize_t size)


#-----------------------------------------------------------------------------
# asbuffer: C buffer from python object
#-----------------------------------------------------------------------------


cdef inline int newstyle_available():
    return PY_MAJOR_VERSION >= 3 or (PY_MAJOR_VERSION >=2 and PY_MINOR_VERSION >= 6)

cdef inline int memoryview_available():
    return PY_MAJOR_VERSION >= 3 or (PY_MAJOR_VERSION >=2 and PY_MINOR_VERSION >= 7)

cdef inline int oldstyle_available():
    return PY_MAJOR_VERSION < 3

cdef inline int check_buffer(object ob):
    """Version independent check for whether an object is a buffer.
    
    Parameters
    ----------
    object : object
        Any Python object

    Returns
    -------
    int : 0 if no buffer interface, 3 if newstyle buffer interface, 2 if oldstyle.
    """
    if newstyle_available():
        if PyObject_CheckBuffer(ob):
            return 3
    if oldstyle_available():
        return PyObject_CheckReadBuffer(ob) and 2
    return 0


cdef inline object asbuffer(object ob, int writable, int format,
                            void **base, Py_ssize_t *size,
                            Py_ssize_t *itemsize):
    """Turn an object into a C buffer in a Python version-independent way.
    
    Parameters
    ----------
    ob : object
        The object to be turned into a buffer.
        Must provide a Python Buffer interface
    writable : int
        Whether the resulting buffer should be allowed to write
        to the object.
    format : int
        The format of the buffer.  See Python buffer docs.
    base : void **
        The pointer that will be used to store the resulting C buffer.
    size : Py_ssize_t *
        The size of the buffer(s).
    itemsize : Py_ssize_t *
        The size of an item, if the buffer is non-contiguous.
    
    Returns
    -------
    An object describing the buffer format. Generally a str, such as 'B'.
    """

    cdef void *bptr = NULL
    cdef Py_ssize_t blen = 0, bitemlen = 0
    cdef str bfmt = None
    cdef Py_buffer view
    cdef int flags = PyBUF_SIMPLE
    cdef int mode = 0

    mode = check_buffer(ob)
    if mode == 0:
        raise TypeError("%r does not provide a buffer interface."%ob)

    if mode == 3:
        flags = PyBUF_ANY_CONTIGUOUS
        if writable:
            flags |= PyBUF_WRITABLE
        if format:
            flags |= PyBUF_FORMAT
        PyObject_GetBuffer(ob, &view, flags)
        bptr = view.buf
        blen = view.len
        if format:
            if view.format != NULL:
                bfmt = view.format
                bitemlen = view.itemsize
        PyBuffer_Release(&view)
    else: # oldstyle
        if writable:
            PyObject_AsWriteBuffer(ob, &bptr, &blen)
        else:
            PyObject_AsReadBuffer(ob, <const_void **>&bptr, &blen)
        if format:
            try: # numpy.ndarray
                dtype = ob.dtype
                bfmt = dtype.char
                bitemlen = dtype.itemsize
            except AttributeError:
                try: # array.array
                    bfmt = ob.typecode
                    bitemlen = ob.itemsize
                except AttributeError:
                    if isinstance(ob, bytes):
                        bfmt = "B"
                        bitemlen = 1
                    else:
                        # nothing found
                        bfmt = None
                        bitemlen = 0
    if base: base[0] = <void *>bptr
    if size: size[0] = <Py_ssize_t>blen
    if itemsize: itemsize[0] = <Py_ssize_t>bitemlen
    return bfmt


cdef inline object asbuffer_r(object ob, void **base, Py_ssize_t *size):
    """Wrapper for standard calls to asbuffer with a readonly buffer."""
    asbuffer(ob, 0, 0, base, size, NULL)
    return ob


cdef inline object asbuffer_w(object ob, void **base, Py_ssize_t *size):
    """Wrapper for standard calls to asbuffer with a writable buffer."""
    asbuffer(ob, 1, 0, base, size, NULL)
    return ob

#------------------------------------------------------------------------------
# frombuffer: python buffer/view from C buffer
#------------------------------------------------------------------------------

cdef inline object frombuffer_3(void *ptr, Py_ssize_t s, int readonly):
    """Python 3 version of frombuffer.

    This is the Python 3 model, but will work on Python >= 2.6. Currently,
    we use it only on >= 3.0.
    """
    cdef Py_buffer pybuf
    cdef Py_ssize_t *shape = [s]
    cdef str astr=""
    if newstyle_available():
        PyBuffer_FillInfo(&pybuf, astr, ptr, s, readonly, PyBUF_SIMPLE)
        pybuf.format = "B"
        pybuf.shape = shape
        return PyMemoryView_FromBuffer(&pybuf)
    else:
        raise NotImplementedError("New style buffers not available.")


cdef inline object frombuffer_2(void *ptr, Py_ssize_t s, int readonly):
    """Python 2 version of frombuffer. 

    This must be used for Python <= 2.6, but we use it for all Python < 3.
    """
    
    if oldstyle_available():
        if readonly:
            return PyBuffer_FromMemory(ptr, s)
        else:
            return PyBuffer_FromReadWriteMemory(ptr, s)
    else:
        raise NotImplementedError("Old style buffers not available.")


cdef inline object frombuffer(void *ptr, Py_ssize_t s, int readonly):
    """Create a Python Buffer/View of a C array. 
    
    Parameters
    ----------
    ptr : void *
        Pointer to the array to be copied.
    s : size_t
        Length of the buffer.
    readonly : int
        whether the resulting object should be allowed to write to the buffer.
    
    Returns
    -------
    Python Buffer/View of the C buffer.
    """
    # oldstyle first priority for now
    if oldstyle_available():
        return frombuffer_2(ptr, s, readonly)
    else:
        return frombuffer_3(ptr, s, readonly)


cdef inline object frombuffer_r(void *ptr, Py_ssize_t s):
    """Wrapper for readonly view frombuffer."""
    return frombuffer(ptr, s, 1)


cdef inline object frombuffer_w(void *ptr, Py_ssize_t s):
    """Wrapper for writable view frombuffer."""
    return frombuffer(ptr, s, 0)

#------------------------------------------------------------------------------
# viewfromobject: python buffer/view from python object, refcounts intact
# frombuffer(asbuffer(obj)) would lose track of refs
#------------------------------------------------------------------------------

cdef inline object viewfromobject(object obj, int readonly):
    """Construct a Python Buffer/View object from another Python object.

    This work in a Python version independent manner.
    
    Parameters
    ----------
    obj : object
        The input object to be cast as a buffer
    readonly : int
        Whether the result should be prevented from overwriting the original.
    
    Returns
    -------
    Buffer/View of the original object.
    """
    if not memoryview_available():
        if readonly:
            return PyBuffer_FromObject(obj, 0, Py_END_OF_BUFFER)
        else:
            return PyBuffer_FromReadWriteObject(obj, 0, Py_END_OF_BUFFER)
    else:
        return PyMemoryView_FromObject(obj)


cdef inline object viewfromobject_r(object obj):
    """Wrapper for readonly viewfromobject."""
    return viewfromobject(obj, 1)


cdef inline object viewfromobject_w(object obj):
    """Wrapper for writable viewfromobject."""
    return viewfromobject(obj, 0)
