/*
 * 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);

/* -------------------------------------------------------------------- */
/* Standard mapper */

typedef struct {
    PyObject_HEAD
    char* base;
    int   size;
    int   offset;
#ifdef _WIN32
    HANDLE hFile;
    HANDLE hMap;
#endif
} ImagingMapperObject;

static PyTypeObject ImagingMapperType;

ImagingMapperObject*
PyImaging_MapperNew(const char* filename, int readonly)
{
    ImagingMapperObject *mapper;

    if (PyType_Ready(&ImagingMapperType) < 0) {
        return NULL;
    }

    mapper = PyObject_New(ImagingMapperObject, &ImagingMapperType);
    if (mapper == NULL) {
        return NULL;
    }

    mapper->base = NULL;
    mapper->size = mapper->offset = 0;

#ifdef _WIN32
    mapper->hFile = (HANDLE)-1;
    mapper->hMap  = (HANDLE)-1;

    /* FIXME: currently supports readonly mappings only */
    mapper->hFile = CreateFile(
        filename,
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);
    if (mapper->hFile == (HANDLE)-1) {
        PyErr_SetString(PyExc_OSError, "cannot open file");
        Py_DECREF(mapper);
        return NULL;
    }

    mapper->hMap = CreateFileMapping(
        mapper->hFile, NULL,
        PAGE_READONLY,
        0, 0, NULL);
    if (mapper->hMap == (HANDLE)-1) {
        CloseHandle(mapper->hFile);
        PyErr_SetString(PyExc_OSError, "cannot map file");
        Py_DECREF(mapper);
        return NULL;
    }

    mapper->base = (char*) MapViewOfFile(
        mapper->hMap,
        FILE_MAP_READ,
        0, 0, 0);

    mapper->size = GetFileSize(mapper->hFile, 0);
#endif

    return mapper;
}

static void
mapping_dealloc(ImagingMapperObject* mapper)
{
#ifdef _WIN32
    if (mapper->base != 0) {
        UnmapViewOfFile(mapper->base);
    }
    if (mapper->hMap != (HANDLE)-1) {
        CloseHandle(mapper->hMap);
    }
    if (mapper->hFile != (HANDLE)-1) {
        CloseHandle(mapper->hFile);
    }
    mapper->base = 0;
    mapper->hMap = mapper->hFile = (HANDLE)-1;
#endif
    PyObject_Del(mapper);
}

/* -------------------------------------------------------------------- */
/* standard file operations */

static PyObject*
mapping_read(ImagingMapperObject* mapper, PyObject* args)
{
    PyObject* buf;

    int size = -1;
    if (!PyArg_ParseTuple(args, "|i", &size)) {
        return NULL;
    }

    /* check size */
    if (size < 0 || mapper->offset + size > mapper->size) {
        size = mapper->size - mapper->offset;
    }
    if (size < 0) {
        size = 0;
    }

    buf = PyBytes_FromStringAndSize(NULL, size);
    if (!buf) {
        return NULL;
    }

    if (size > 0) {
        memcpy(PyBytes_AsString(buf), mapper->base + mapper->offset, size);
        mapper->offset += size;
    }

    return buf;
}

static PyObject*
mapping_seek(ImagingMapperObject* mapper, PyObject* args)
{
    int offset;
    int whence = 0;
    if (!PyArg_ParseTuple(args, "i|i", &offset, &whence)) {
        return NULL;
    }

    switch (whence) {
        case 0: /* SEEK_SET */
            mapper->offset = offset;
            break;
        case 1: /* SEEK_CUR */
            mapper->offset += offset;
            break;
        case 2: /* SEEK_END */
            mapper->offset = mapper->size + offset;
            break;
        default:
            /* FIXME: raise ValueError? */
            break;
    }

    Py_INCREF(Py_None);
    return Py_None;
}

/* -------------------------------------------------------------------- */
/* map entire image */

extern PyObject*PyImagingNew(Imaging im);

static void
ImagingDestroyMap(Imaging im)
{
    return; /* nothing to do! */
}

static PyObject*
mapping_readimage(ImagingMapperObject* mapper, PyObject* args)
{
    int y, size;
    Imaging im;

    char* mode;
    int xsize;
    int ysize;
    int stride;
    int orientation;
    if (!PyArg_ParseTuple(args, "s(ii)ii", &mode, &xsize, &ysize,
                          &stride, &orientation)) {
        return NULL;
    }

    if (stride <= 0) {
        /* FIXME: maybe we should call ImagingNewPrologue instead */
        if (!strcmp(mode, "L") || !strcmp(mode, "P")) {
            stride = xsize;
        } else if (!strcmp(mode, "I;16") || !strcmp(mode, "I;16B")) {
            stride = xsize * 2;
        } else {
            stride = xsize * 4;
        }
    }

    size = ysize * stride;

    if (mapper->offset + size > mapper->size) {
        PyErr_SetString(PyExc_OSError, "image file truncated");
        return NULL;
    }

    im = ImagingNewPrologue(mode, xsize, ysize);
    if (!im) {
        return NULL;
    }

    /* setup file pointers */
    if (orientation > 0) {
        for (y = 0; y < ysize; y++) {
            im->image[y] = mapper->base + mapper->offset + y * stride;
        }
    } else {
        for (y = 0; y < ysize; y++) {
            im->image[ysize-y-1] = mapper->base + mapper->offset + y * stride;
        }
    }

    im->destroy = ImagingDestroyMap;

    mapper->offset += size;

    return PyImagingNew(im);
}

static struct PyMethodDef methods[] = {
    /* standard file interface */
    {"read", (PyCFunction)mapping_read, 1},
    {"seek", (PyCFunction)mapping_seek, 1},
    /* extensions */
    {"readimage", (PyCFunction)mapping_readimage, 1},
    {NULL, NULL} /* sentinel */
};

static PyTypeObject ImagingMapperType = {
        PyVarObject_HEAD_INIT(NULL, 0)
        "ImagingMapper",                /*tp_name*/
        sizeof(ImagingMapperObject),    /*tp_size*/
        0,                              /*tp_itemsize*/
        /* methods */
        (destructor)mapping_dealloc,    /*tp_dealloc*/
        0,                              /*tp_print*/
    0,                          /*tp_getattr*/
    0,                          /*tp_setattr*/
    0,                          /*tp_compare*/
    0,                          /*tp_repr*/
    0,                          /*tp_as_number */
    0,                          /*tp_as_sequence */
    0,                          /*tp_as_mapping */
    0,                          /*tp_hash*/
    0,                          /*tp_call*/
    0,                          /*tp_str*/
    0,                          /*tp_getattro*/
    0,                          /*tp_setattro*/
    0,                          /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,         /*tp_flags*/
    0,                          /*tp_doc*/
    0,                          /*tp_traverse*/
    0,                          /*tp_clear*/
    0,                          /*tp_richcompare*/
    0,                          /*tp_weaklistoffset*/
    0,                          /*tp_iter*/
    0,                          /*tp_iternext*/
    methods,                    /*tp_methods*/
    0,                          /*tp_members*/
    0,                          /*tp_getset*/
};

PyObject*
PyImaging_Mapper(PyObject* self, PyObject* args)
{
    char* filename;
    if (!PyArg_ParseTuple(args, "s", &filename)) {
        return NULL;
    }

    return (PyObject*) PyImaging_MapperNew(filename, 1);
}

/* -------------------------------------------------------------------- */
/* 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;
    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, &stride, &ystep)) {
        return NULL;
    }

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

    if (stride <= 0) {
        if (!strcmp(mode, "L") || !strcmp(mode, "P")) {
            stride = xsize;
        } else if (!strncmp(mode, "I;16", 4)) {
            stride = xsize * 2;
        } else {
            stride = xsize * 4;
        }
    }

    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->destroy = mapping_destroy_buffer;

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

    return PyImagingNew(im);
}

