/*------------------------------------------------------------------------
 *  Copyright 2009-2010 (c) Jeff Brown <spadix@users.sourceforge.net>
 *
 *  This file is part of the ZBar Bar Code Reader.
 *
 *  The ZBar Bar Code Reader is free software; you can redistribute it
 *  and/or modify it under the terms of the GNU Lesser Public License as
 *  published by the Free Software Foundation; either version 2.1 of
 *  the License, or (at your option) any later version.
 *
 *  The ZBar Bar Code Reader is distributed in the hope that it will be
 *  useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 *  of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser Public License
 *  along with the ZBar Bar Code Reader; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 *  Boston, MA  02110-1301  USA
 *
 *  http://sourceforge.net/projects/zbar
 *------------------------------------------------------------------------*/

#include "zbarmodule.h"
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#endif

static char image_doc[] = PyDoc_STR(
    "image object.\n"
    "\n"
    "stores image data samples along with associated format and size metadata.");

static zbarImage *image_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    zbarImage *self = (zbarImage *)type->tp_alloc(type, 0);
    if (!self)
	return (NULL);

    self->zimg = zbar_image_create();
    if (!self->zimg) {
	Py_DECREF(self);
	return (NULL);
    }
    zbar_image_set_userdata(self->zimg, self);
    return (self);
}

static int image_traverse(zbarImage *self, visitproc visit, void *arg)
{
    Py_VISIT(self->data);
    return (0);
}

static int image_clear(zbarImage *self)
{
    zbar_image_t *zimg = self->zimg;
    self->zimg	       = NULL;
    if (zimg) {
	assert(zbar_image_get_userdata(zimg) == self);
	if (self->data) {
	    /* attach data directly to zbar image */
	    zbar_image_set_userdata(zimg, self->data);
	    self->data = NULL;
	} else
	    zbar_image_set_userdata(zimg, NULL);
	zbar_image_destroy(zimg);
    }
    return (0);
}

static void image_dealloc(zbarImage *self)
{
    image_clear(self);
    ((PyObject *)self)->ob_type->tp_free((PyObject *)self);
}

static zbarSymbolSet *image_get_symbols(zbarImage *self, void *closure)
{
    const zbar_symbol_set_t *zsyms = zbar_image_get_symbols(self->zimg);
    return (zbarSymbolSet_FromSymbolSet(zsyms));
}

static int image_set_symbols(zbarImage *self, PyObject *value, void *closure)
{
    const zbar_symbol_set_t *zsyms;
    if (!value || value == Py_None)
	zsyms = NULL;
    else if (zbarSymbolSet_Check(value))
	zsyms = ((zbarSymbolSet *)value)->zsyms;
    else {
	PyErr_Format(PyExc_TypeError,
		     "must set image symbols to a zbar.SymbolSet, not '%.50s'",
		     value->ob_type->tp_name);
	return (-1);
    }

    zbar_image_set_symbols(self->zimg, zsyms);
    return (0);
}

static zbarSymbolIter *image_iter(zbarImage *self)
{
    zbarSymbolSet *syms = image_get_symbols(self, NULL);
    if (!syms)
	return (NULL);
    return (zbarSymbolIter_FromSymbolSet(syms));
}

static PyObject *image_get_format(zbarImage *self, void *closure)
{
    unsigned long format = zbar_image_get_format(self->zimg);
#if PY_MAJOR_VERSION >= 3
    return (PyBytes_FromStringAndSize((char *)&format, 4));
#else
    return (PyString_FromStringAndSize((char *)&format, 4));
#endif
}

static int image_set_format(zbarImage *self, PyObject *value, void *closure)
{
    if (!value) {
	PyErr_SetString(PyExc_TypeError, "cannot delete format attribute");
	return (-1);
    }
    char *format = NULL;
    Py_ssize_t len;
#if PY_MAJOR_VERSION >= 3
    PyObject *bytes;

    if (PyUnicode_Check(value))
	bytes = PyUnicode_AsEncodedString(value, "utf-8", "surrogateescape");
    else
	bytes = value;
    if (PyBytes_AsStringAndSize(bytes, &format, &len) < 0 || !format ||
	len != 4) {
#else
    if (PyString_AsStringAndSize(value, &format, &len) || !format || len != 4) {
#endif
	if (!format)
	    format = "(nil)";
	PyErr_Format(PyExc_ValueError,
		     "format '%.50s' is not a valid four character code",
		     format);
	return (-1);
    }
    zbar_image_set_format(self->zimg, zbar_fourcc_parse(format));
    return (0);
}

static PyObject *image_get_size(zbarImage *self, void *closure)
{
    unsigned int w, h;
    zbar_image_get_size(self->zimg, &w, &h);
#if PY_MAJOR_VERSION >= 3
    return (PyTuple_Pack(2, PyLong_FromLong(w), PyLong_FromLong(h)));
#else
    return (PyTuple_Pack(2, PyInt_FromLong(w), PyInt_FromLong(h)));
#endif
}

static int image_set_size(zbarImage *self, PyObject *value, void *closure)
{
    if (!value) {
	PyErr_SetString(PyExc_TypeError, "cannot delete size attribute");
	return (-1);
    }

    int dims[2];
    if (parse_dimensions(value, dims, 2) || dims[0] < 0 || dims[1] < 0) {
	PyErr_SetString(PyExc_ValueError,
			"size must be a sequence of two positive ints");
	return (-1);
    }

    zbar_image_set_size(self->zimg, dims[0], dims[1]);
    return (0);
}

static PyObject *image_get_crop(zbarImage *self, void *closure)
{
    unsigned int x, y, w, h;
    zbar_image_get_crop(self->zimg, &x, &y, &w, &h);
#if PY_MAJOR_VERSION >= 3
    return (PyTuple_Pack(4, PyLong_FromLong(x), PyLong_FromLong(y),
			 PyLong_FromLong(w), PyLong_FromLong(h)));
#else
    return (PyTuple_Pack(4, PyInt_FromLong(x), PyInt_FromLong(y),
			 PyInt_FromLong(w), PyInt_FromLong(h)));
#endif
}

static int image_set_crop(zbarImage *self, PyObject *value, void *closure)
{
    unsigned w, h;
    zbar_image_get_size(self->zimg, &w, &h);
    if (!value) {
	zbar_image_set_crop(self->zimg, 0, 0, w, h);
	return (0);
    }

    int dims[4];
    if (parse_dimensions(value, dims, 4) || dims[2] < 0 || dims[3] < 0) {
	PyErr_SetString(PyExc_ValueError,
			"crop must be a sequence of four positive ints");
	return (-1);
    }

    if (dims[0] < 0) {
	dims[2] += dims[0];
	dims[0] = 0;
    }
    if (dims[1] < 0) {
	dims[3] += dims[1];
	dims[1] = 0;
    }

    zbar_image_set_crop(self->zimg, dims[0], dims[1], dims[2], dims[3]);
    return (0);
}

static PyObject *image_get_int(zbarImage *self, void *closure)
{
    unsigned int val = -1;
    switch ((intptr_t)closure) {
    case 0:
	val = zbar_image_get_width(self->zimg);
	break;
    case 1:
	val = zbar_image_get_height(self->zimg);
	break;
    case 2:
	val = zbar_image_get_sequence(self->zimg);
	break;
    default:
	assert(0);
    }
#if PY_MAJOR_VERSION >= 3
    return (PyLong_FromLong(val));
#else
    return (PyInt_FromLong(val));
#endif
}

static int image_set_int(zbarImage *self, PyObject *value, void *closure)
{
    unsigned int tmp;
#if PY_MAJOR_VERSION >= 3
    long val = PyLong_AsLong(value);
#else
    unsigned int val = PyInt_AsSsize_t(value);
#endif
    if (val == -1 && PyErr_Occurred()) {
	PyErr_SetString(PyExc_TypeError, "expecting an integer");
	return (-1);
    }
    switch ((intptr_t)closure) {
    case 0:
	tmp = zbar_image_get_height(self->zimg);
	zbar_image_set_size(self->zimg, val, tmp);
	break;
    case 1:
	tmp = zbar_image_get_width(self->zimg);
	zbar_image_set_size(self->zimg, tmp, val);
	break;
    case 2:
	zbar_image_set_sequence(self->zimg, val);
    default:
	assert(0);
    }
    return (0);
}

static PyObject *image_get_data(zbarImage *self, void *closure)
{
    assert(zbar_image_get_userdata(self->zimg) == self);
    if (self->data) {
	Py_INCREF(self->data);
	return (self->data);
    }

    const char *data	  = zbar_image_get_data(self->zimg);
    unsigned long datalen = zbar_image_get_data_length(self->zimg);
    if (!data || !datalen) {
	Py_INCREF(Py_None);
	return (Py_None);
    }

#if PY_MAJOR_VERSION >= 3
    self->data = PyMemoryView_FromMemory((void *)data, datalen, PyBUF_READ);
#else
    self->data = PyBuffer_FromMemory((void *)data, datalen);
#endif
    Py_INCREF(self->data);
    return (self->data);
}

void image_cleanup(zbar_image_t *zimg)
{
    PyObject *data = zbar_image_get_userdata(zimg);
    zbar_image_set_userdata(zimg, NULL);
    if (!data)
	return; /* FIXME internal error */
    if (PyObject_TypeCheck(data, &zbarImage_Type)) {
	zbarImage *self = (zbarImage *)data;
	assert(self->zimg == zimg);
	Py_CLEAR(self->data);
    } else
	Py_DECREF(data);
}

static int image_set_data(zbarImage *self, PyObject *value, void *closure)
{
    if (!value) {
	zbar_image_free_data(self->zimg);
	return (0);
    }
    char *data;
    Py_ssize_t datalen;
#if PY_MAJOR_VERSION >= 3
    PyObject *bytes;

    if (PyUnicode_Check(value))
	bytes = PyUnicode_AsEncodedString(value, "utf-8", "surrogateescape");
    else
	bytes = value;
    if (PyBytes_AsStringAndSize(bytes, &data, &datalen))
	return (-1);
#else
    if (PyString_AsStringAndSize(value, &data, &datalen))
	return (-1);
#endif

    Py_INCREF(value);
    zbar_image_set_data(self->zimg, data, datalen, image_cleanup);
    assert(!self->data);
    self->data = value;
    zbar_image_set_userdata(self->zimg, self);
    return (0);
}

static PyGetSetDef image_getset[] = {
    {
	"format",
	(getter)image_get_format,
	(setter)image_set_format,
    },
    {
	"size",
	(getter)image_get_size,
	(setter)image_set_size,
    },
    {
	"crop",
	(getter)image_get_crop,
	(setter)image_set_crop,
    },
    { "width", (getter)image_get_int, (setter)image_set_int, NULL, (void *)0 },
    { "height", (getter)image_get_int, (setter)image_set_int, NULL, (void *)1 },
    { "sequence", (getter)image_get_int, (setter)image_set_int, NULL,
      (void *)2 },
    {
	"data",
	(getter)image_get_data,
	(setter)image_set_data,
    },
    {
	"symbols",
	(getter)image_get_symbols,
	(setter)image_set_symbols,
    },
    {
	NULL,
    },
};

static int image_init(zbarImage *self, PyObject *args, PyObject *kwds)
{
    int width = -1, height = -1;
    PyObject *format = NULL, *data = NULL;
    static char *kwlist[] = { "width", "height", "format", "data", NULL };
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiOO", kwlist, &width,
				     &height, &format, &data))
	return (-1);

    if (width > 0 && height > 0)
	zbar_image_set_size(self->zimg, width, height);
    if (format && image_set_format(self, format, NULL))
	return (-1);
    if (data && image_set_data(self, data, NULL))
	return (-1);
    return (0);
}

static zbarImage *image_convert(zbarImage *self, PyObject *args, PyObject *kwds)
{
    const char *format = NULL;
    int width = -1, height = -1;
    static char *kwlist[] = { "format", "width", "height", NULL };
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|ii", kwlist, &format,
				     &width, &height))
	return (NULL);
    assert(format);

    if (strlen(format) != 4) {
	PyErr_Format(PyExc_ValueError,
		     "format '%.50s' is not a valid four character code",
		     format);
	return (NULL);
    }
    unsigned long fourcc = zbar_fourcc_parse(format);

    zbarImage *img = PyObject_GC_New(zbarImage, &zbarImage_Type);
    if (!img)
	return (NULL);
    img->data = NULL;
    if (width > 0 && height > 0)
	img->zimg =
	    zbar_image_convert_resize(self->zimg, fourcc, width, height);
    else
	img->zimg = zbar_image_convert(self->zimg, fourcc);

    if (!img->zimg) {
	/* FIXME propagate exception */
	Py_DECREF(img);
	return (NULL);
    }
    zbar_image_set_userdata(img->zimg, img);

    return (img);
}

static PyMethodDef image_methods[] = {
    {
	"convert",
	(PyCFunction)image_convert,
	METH_VARARGS | METH_KEYWORDS,
    },
    {
	NULL,
    },
};

PyTypeObject zbarImage_Type = {
    PyVarObject_HEAD_INIT(NULL, 0).tp_name = "zbar.Image",

    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,

    .tp_doc	  = image_doc,
    .tp_basicsize = sizeof(zbarImage),
    .tp_new	  = (newfunc)image_new,
    .tp_init	  = (initproc)image_init,
    .tp_traverse  = (traverseproc)image_traverse,
    .tp_clear	  = (inquiry)image_clear,
    .tp_dealloc	  = (destructor)image_dealloc,
    .tp_getset	  = image_getset,
    .tp_methods	  = image_methods,
    .tp_iter	  = (getiterfunc)image_iter,
};

zbarImage *zbarImage_FromImage(zbar_image_t *zimg)
{
    zbarImage *self = PyObject_GC_New(zbarImage, &zbarImage_Type);
    if (!self)
	return (NULL);
    zbar_image_ref(zimg, 1);
    zbar_image_set_userdata(zimg, self);
    self->zimg = zimg;
    self->data = NULL;
    return (self);
}

int zbarImage_validate(zbarImage *img)
{
    if (!zbar_image_get_width(img->zimg) || !zbar_image_get_height(img->zimg) ||
	!zbar_image_get_data(img->zimg) ||
	!zbar_image_get_data_length(img->zimg)) {
	PyErr_Format(PyExc_ValueError, "image size and data must be defined");
	return (-1);
    }
    return (0);
}
