/*
 * The Python Imaging Library.
 *
 * standard memory mapping interface for the Imaging library
 *
 * history:
 * 1998-03-05 fl   added Win32 read mapping
 * 1999-02-06 fl   added "I;16" support
 * 2003-04-21 fl   added PyImaging_MapBuffer primitive
 *
 * Copyright (c) 1998-2003 by Secret Labs AB.
 * Copyright (c) 2003 by Fredrik Lundh.
 *
 * See the README file for information on usage and redistribution.
 */

/*
 * FIXME: should move the memory mapping primitives into libImaging!
 */

#include "Python.h"

#include "libImaging/Imaging.h"

/* compatibility wrappers (defined in _imaging.c) */
extern int
PyImaging_CheckBuffer(PyObject *buffer);
extern int
PyImaging_GetBuffer(PyObject *buffer, Py_buffer *view);

extern PyObject *
PyImagingNew(Imaging im);

/* -------------------------------------------------------------------- */
/* Buffer mapper */

typedef struct ImagingBufferInstance {
    struct ImagingMemoryInstance im;
    PyObject *target;
    Py_buffer view;
} ImagingBufferInstance;

static void
mapping_destroy_buffer(Imaging im) {
    ImagingBufferInstance *buffer = (ImagingBufferInstance *)im;

    PyBuffer_Release(&buffer->view);
    Py_XDECREF(buffer->target);
}

PyObject *
PyImaging_MapBuffer(PyObject *self, PyObject *args) {
    Py_ssize_t y, size;
    Imaging im;

    PyObject *target;
    Py_buffer view;
    char *mode_name;
    char *codec;
    Py_ssize_t offset;
    int xsize, ysize;
    int stride;
    int ystep;

    if (!PyArg_ParseTuple(
            args,
            "O(ii)sn(sii)",
            &target,
            &xsize,
            &ysize,
            &codec,
            &offset,
            &mode_name,
            &stride,
            &ystep
        )) {
        return NULL;
    }

    if (!PyImaging_CheckBuffer(target)) {
        PyErr_SetString(PyExc_TypeError, "expected string or buffer");
        return NULL;
    }

    const ModeID mode = findModeID(mode_name);

    int pixelsize;
    if (mode == IMAGING_MODE_L || mode == IMAGING_MODE_P) {
        pixelsize = 1;
    } else if (isModeI16(mode)) {
        pixelsize = 2;
    } else {
        pixelsize = 4;
    }
    if (stride <= xsize * pixelsize) {
        stride = xsize * pixelsize;
    }

    if (stride > 0 && ysize > PY_SSIZE_T_MAX / stride) {
        PyErr_SetString(PyExc_MemoryError, "Integer overflow in ysize");
        return NULL;
    }

    size = (Py_ssize_t)ysize * stride;

    if (offset > PY_SSIZE_T_MAX - size) {
        PyErr_SetString(PyExc_MemoryError, "Integer overflow in offset");
        return NULL;
    }

    /* check buffer size */
    if (PyImaging_GetBuffer(target, &view) < 0) {
        return NULL;
    }

    if (view.len < 0) {
        PyErr_SetString(PyExc_ValueError, "buffer has negative size");
        PyBuffer_Release(&view);
        return NULL;
    }
    if (offset + size > view.len) {
        PyErr_SetString(PyExc_ValueError, "buffer is not large enough");
        PyBuffer_Release(&view);
        return NULL;
    }

    im = ImagingNewPrologueSubtype(mode, xsize, ysize, sizeof(ImagingBufferInstance));
    if (!im) {
        PyBuffer_Release(&view);
        return NULL;
    }

    /* setup file pointers */
    if (ystep > 0) {
        for (y = 0; y < ysize; y++) {
            im->image[y] = (char *)view.buf + offset + y * stride;
        }
    } else {
        for (y = 0; y < ysize; y++) {
            im->image[ysize - y - 1] = (char *)view.buf + offset + y * stride;
        }
    }

    im->read_only = view.readonly;
    im->destroy = mapping_destroy_buffer;

    Py_INCREF(target);
    ((ImagingBufferInstance *)im)->target = target;
    ((ImagingBufferInstance *)im)->view = view;

    return PyImagingNew(im);
}
