/* -*- Mode: C; c-basic-offset: 4 -*- 
    Gimp-Python - allows the writing of Gimp plugins in Python.
    Copyright (C) 1997-2002  James Henstridge <james@daa.com.au>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif
#include "pygimp.h"
#include <structmember.h>

#ifndef PG_DEBUG
# define PG_DEBUG 0
#endif

/* ----------------------------------------------------- */

/* Declarations for objects of type pdb */

typedef struct {
    PyObject_HEAD
} PyGimpPDB;


/* ---------------------------------------------------------------- */

/* Declarations for objects of type pdbFunc */

typedef struct {
    PyObject_HEAD
    char *name;
    PyObject *proc_name, *proc_blurb, *proc_help, *proc_author,
	*proc_copyright, *proc_date, *proc_type, *py_params,
	*py_return_vals;
    int nparams, nreturn_vals;
    GimpParamDef *params, *return_vals;
} PyGimpPDBFunction;

static PyObject *pygimp_pdb_function_new_from_proc_db(char *name);

/* ---------------------------------------------------------------- */

/* routines to convert between Python tuples and gimp GimpParam's */

#if PG_DEBUG > 0

static void
pygimp_param_print(int nparams, GimpParam *params)
{
    int i;

    for (i = 0; i < nparams; i++) {
	switch(params[i].type) {
	case GIMP_PDB_INT32:
	    fprintf(stderr, "%i. int %i\n", i,
		    params[i].data.d_int32);
	    break;
	case GIMP_PDB_INT16:
	    fprintf(stderr, "%i. int %i\n", i,
		    params[i].data.d_int16);
	    break;
	case GIMP_PDB_INT8:
	    fprintf(stderr, "%i. int %i\n", i,
		    params[i].data.d_int8);
	    break;
	case GIMP_PDB_FLOAT:
	    fprintf(stderr, "%i. float %f\n", i,
		    params[i].data.d_float);
	    break;
	case GIMP_PDB_STRING:
	    fprintf(stderr, "%i. string %s\n", i,
		    params[i].data.d_string);
	    break;
	case GIMP_PDB_INT32ARRAY:
	case GIMP_PDB_INT16ARRAY:
	case GIMP_PDB_INT8ARRAY:
	case GIMP_PDB_FLOATARRAY:
	case GIMP_PDB_STRINGARRAY:
	    fprintf(stderr, "%i. array of type %i %s\n", i,
		    params[i].type, params[i].data.d_int32array
		    == NULL ? "(null)":"");
	    break;
	case GIMP_PDB_STATUS:
	    fprintf(stderr, "%i. status %i\n", i,
		    params[i].data.d_status);
	    break;
	default:
	    fprintf(stderr, "%i. other %i\n", i,
		    params[i].data.d_int32);
	    break;
	}
    }
}

#endif

PyObject *
pygimp_param_to_tuple(int nparams, const GimpParam *params)
{
    PyObject *args, *tmp;
    int i, j, n;

    args = PyTuple_New(nparams);
    for (i = 0; i < nparams && params[i].type != GIMP_PDB_END; i++) {
	PyObject *value = NULL;

	switch(params[i].type) {
	case GIMP_PDB_INT32:
	    value = PyInt_FromLong(params[i].data.d_int32);
	    break;
	case GIMP_PDB_INT16:
	    value = PyInt_FromLong(params[i].data.d_int16);
	    break;
	case GIMP_PDB_INT8:
	    value = PyInt_FromLong(params[i].data.d_int8);
	    break;
	case GIMP_PDB_FLOAT:
	    value = PyFloat_FromDouble(params[i].data.d_float);
	    break;
	case GIMP_PDB_STRING:
	    if (params[i].data.d_string == NULL) {
		Py_INCREF(Py_None);
		value = Py_None;
	    } else
		value = PyString_FromString(params[i].data.d_string);
	    break;

	    /* For these to work, the previous argument must have
	     * been an integer
	     */
	case GIMP_PDB_INT32ARRAY:
	    if (params[i].data.d_int32array == NULL) {
		value = PyTuple_New(0);
		break;
	    }
	    if ((tmp=PyTuple_GetItem(args, i-1)) == NULL) {
		Py_DECREF(args);
		return NULL;
	    }
	    if (!PyInt_Check(tmp)) {
		PyErr_SetString(PyExc_TypeError,
				"count type must be integer");
		Py_DECREF(args);
		return NULL;
	    }
	    n = PyInt_AsLong(tmp);
	    value = PyTuple_New(n);
	    for (j = 0; j < n; j++)
		PyTuple_SetItem(value, j,
			PyInt_FromLong(params[i].data.d_int32array[j]));
	    break;
	case GIMP_PDB_INT16ARRAY:
	    if (params[i].data.d_int16array == NULL) {
		value = PyTuple_New(0);
		break;
	    }
	    if ((tmp=PyTuple_GetItem(args, i-1)) == NULL) {
		Py_DECREF(args);
		return NULL;
	    }
	    if (!PyInt_Check(tmp)) {
		PyErr_SetString(PyExc_TypeError,
				"count type must be integer");
		Py_DECREF(args);
		return NULL;
	    }
	    n = PyInt_AsLong(tmp);
	    value = PyTuple_New(n);
	    for (j = 0; j < n; j++)
		PyTuple_SetItem(value, j,
			PyInt_FromLong(params[i].data.d_int16array[j]));
	    break;
	case GIMP_PDB_INT8ARRAY:
	    if (params[i].data.d_int8array == NULL) {
		value = PyTuple_New(0);
		break;
	    }
	    if ((tmp=PyTuple_GetItem(args, i-1)) == NULL) {
		Py_DECREF(args);
		return NULL;
	    }
	    if (!PyInt_Check(tmp)) {
		PyErr_SetString(PyExc_TypeError,
				"count type must be integer");
		Py_DECREF(args);
		return NULL;
	    }
	    n = PyInt_AsLong(tmp);
	    value = PyTuple_New(n);
	    for (j = 0; j < n; j++)
		PyTuple_SetItem(value, j,
			PyInt_FromLong(params[i].data.d_int8array[j]));
	    break;
	case GIMP_PDB_FLOATARRAY:
	    if (params[i].data.d_floatarray == NULL) {
		value = PyTuple_New(0);
		break;
	    }
	    if ((tmp=PyTuple_GetItem(args, i-1)) == NULL) {
		Py_DECREF(args);
		return NULL;
	    }
	    if (!PyInt_Check(tmp)) {
		PyErr_SetString(PyExc_TypeError,
				"count type must be integer");
		Py_DECREF(args);
		return NULL;
	    }
	    n = PyInt_AsLong(tmp);
	    value = PyTuple_New(n);
	    for (j = 0; j < n; j++)
		PyTuple_SetItem(value, j,
			PyFloat_FromDouble(params[i].data.d_floatarray[j]));
	    break;
	case GIMP_PDB_STRINGARRAY:
	    if (params[i].data.d_stringarray == NULL) {
		value = PyTuple_New(0);
		break;
	    }
	    if ((tmp=PyTuple_GetItem(args, i-1)) == NULL) {
		Py_DECREF(args);
		return NULL;
	    }
	    if (!PyInt_Check(tmp)) {
		PyErr_SetString(PyExc_TypeError,
				"count type must be integer");
		Py_DECREF(args);
		return NULL;
	    }
	    n = PyInt_AsLong(tmp);
	    value = PyTuple_New(n);
	    for (j = 0; j < n; j++)
		PyTuple_SetItem(value, j,
			params[i].data.d_stringarray[j] ?
			PyString_FromString(params[i].data.d_stringarray[j]) :
			PyString_FromString(""));
	    break;
	case GIMP_PDB_COLOR:
	    {
		guchar r, g, b;
		gimp_rgb_get_uchar(&params[i].data.d_color, &r, &g, &b);
		value = Py_BuildValue("(iii)", (int)r, (int)g, (int)b);
	    }
	    break;
	case GIMP_PDB_REGION:
	    value = Py_BuildValue("(iiii)",
				  (int) params[i].data.d_region.x,
				  (int) params[i].data.d_region.y,
				  (int) params[i].data.d_region.width,
				  (int) params[i].data.d_region.height);
	    break;
	case GIMP_PDB_DISPLAY:
	    value = pygimp_display_new(params[i].data.d_display);
	    break;
	case GIMP_PDB_IMAGE:
	    value = pygimp_image_new(params[i].data.d_image);
	    break;
	case GIMP_PDB_LAYER:
	    value = pygimp_layer_new(params[i].data.d_layer);
	    break;
	case GIMP_PDB_CHANNEL:
	    value = pygimp_channel_new(params[i].data.d_channel);
	    break;
	case GIMP_PDB_DRAWABLE:
	    value = pygimp_drawable_new(NULL, params[i].data.d_drawable);
	    break;
	case GIMP_PDB_SELECTION:
	    value = pygimp_layer_new(params[i].data.d_selection);
	    break;
	case GIMP_PDB_BOUNDARY:
	    value = PyInt_FromLong(params[i].data.d_boundary);
	    break;
	case GIMP_PDB_PATH:
	    value = PyInt_FromLong(params[i].data.d_path);
	    break;
	case GIMP_PDB_PARASITE:
	    value = pygimp_parasite_new(gimp_parasite_copy(
					&(params[i].data.d_parasite)));
	    break;
	case GIMP_PDB_STATUS:
	    value = PyInt_FromLong(params[i].data.d_status);
	    break;
	case GIMP_PDB_END:
	    break;
	}
	PyTuple_SetItem(args, i, value);
    }
    return args;
}

GimpParam *
pygimp_param_from_tuple(PyObject *args, const GimpParamDef *ptype, int nparams)
{
    PyObject *tuple, *item, *r, *g, *b, *x, *y, *w, *h;
    GimpParam *ret;
    int i, j, len;
    gint32 *i32a; gint16 *i16a; gint8 *i8a; gdouble *fa; gchar **sa;

    if (nparams == 0)
	tuple = PyTuple_New(0);
    else if (!PyTuple_Check(args) && nparams == 1)
	tuple = Py_BuildValue("(O)", args);
    else {
	Py_INCREF(args);
	tuple = args;
    }
    if (!PyTuple_Check(tuple)) {
	PyErr_SetString(PyExc_TypeError, "wrong type of parameter");
	return NULL;
    }

    if (PyTuple_Size(tuple) != nparams) {
	PyErr_SetString(PyExc_TypeError, "wrong number of parameters");
	return NULL;
    }

    ret = g_new(GimpParam, nparams+1);
    for (i = 0; i <= nparams; i++)
	ret[i].type = GIMP_PDB_STATUS;
#define check(expr) if (expr) { \
	    PyErr_SetString(PyExc_TypeError, "wrong parameter type"); \
	    Py_DECREF(tuple); \
	    gimp_destroy_params(ret, nparams); \
	    return NULL; \
	}
#define arraycheck(expr, ar) if (expr) { \
	    PyErr_SetString(PyExc_TypeError, "subscript of wrong type"); \
	    Py_DECREF(tuple); \
	    gimp_destroy_params(ret, nparams); \
	    g_free(ar); \
	    return NULL; \
	}
    for (i = 1; i <= nparams; i++) {
	item = PyTuple_GetItem(tuple, i-1);
	switch (ptype[i-1].type) {
	case GIMP_PDB_INT32:
	    check((x = PyNumber_Int(item)) == NULL);
	    ret[i].data.d_int32 = PyInt_AsLong(x);
	    Py_DECREF(x);
	    break;
	case GIMP_PDB_INT16:
	    check((x = PyNumber_Int(item)) == NULL);
	    ret[i].data.d_int16 = PyInt_AsLong(x);
	    Py_DECREF(x);
	    break;
	case GIMP_PDB_INT8:
	    check((x = PyNumber_Int(item)) == NULL);
	    ret[i].data.d_int8 = PyInt_AsLong(x);
	    Py_DECREF(x);
	    break;
	case GIMP_PDB_FLOAT:
	    check((x = PyNumber_Float(item)) == NULL);
	    ret[i].data.d_float = PyFloat_AsDouble(x);
	    Py_DECREF(x);
	    break;
	case GIMP_PDB_STRING:
	    if (item == Py_None) {
		ret[i].data.d_string = NULL;
		break;
	    }
	    check((x = PyObject_Str(item)) == NULL);
	    ret[i].data.d_string = g_strdup(PyString_AsString(x));
	    Py_DECREF(x);
	    break;
	case GIMP_PDB_INT32ARRAY:
	    check(!PySequence_Check(item));
	    len = PySequence_Length(item);
	    i32a = g_new(gint32, len);
	    for (j = 0; j < len; j++) {
		x = PySequence_GetItem(item, j);
		arraycheck((y=PyNumber_Int(x))==NULL,
			   i32a);
		i32a[j] = PyInt_AsLong(y);
		Py_DECREF(y);
	    }
	    ret[i].data.d_int32array = i32a;
	    break;
	case GIMP_PDB_INT16ARRAY:
	    check(!PySequence_Check(item));
	    len = PySequence_Length(item);
	    i16a = g_new(gint16, len);
	    for (j = 0; j < len; j++) {
		x = PySequence_GetItem(item, j);
		arraycheck((y=PyNumber_Int(x))==NULL,
			   i16a);
		i16a[j] = PyInt_AsLong(y);
		Py_DECREF(y);
	    }
	    ret[i].data.d_int16array = i16a;
	    break;
	case GIMP_PDB_INT8ARRAY:
	    check(!PySequence_Check(item));
	    len = PySequence_Length(item);
	    i8a = g_new(gint8, len);
	    for (j = 0; j < len; j++) {
		x = PySequence_GetItem(item, j);
		arraycheck((y=PyNumber_Int(x))==NULL,
			   i8a);
		i8a[j] = PyInt_AsLong(y);
		Py_DECREF(y);
	    }
	    ret[i].data.d_int8array = i8a;
	    break;
	case GIMP_PDB_FLOATARRAY:
	    check(!PySequence_Check(item));
	    len = PySequence_Length(item);
	    fa = g_new(gdouble, len);
	    for (j = 0; j < len; j++) {
		x = PySequence_GetItem(item, j);
		arraycheck((y=PyNumber_Float(x))==NULL,
			   fa);
		fa[j] = PyFloat_AsDouble(y);
		Py_DECREF(y);
	    }
	    ret[i].data.d_floatarray = fa;
	    break;
	case GIMP_PDB_STRINGARRAY:
	    check(!PySequence_Check(item));
	    len = PySequence_Length(item);
	    sa = g_new(gchar *, len);
	    for (j = 0; j < len; j++) {
		x = PySequence_GetItem(item, j);
		if (x == Py_None) {
		    sa[j] = NULL;
		    continue;
		}
		arraycheck((y=PyObject_Str(x))==NULL,
			   sa);
		sa[j] = g_strdup(PyString_AsString(y));
		Py_DECREF(y);
	    }
	    ret[i].data.d_stringarray = sa;
	    break;
	case GIMP_PDB_COLOR:
	    check(!PySequence_Check(item) || PySequence_Length(item) < 3);
	    r = PySequence_GetItem(item, 0);
	    g = PySequence_GetItem(item, 1);
	    b = PySequence_GetItem(item, 2);
	    check(!PyInt_Check(r) || !PyInt_Check(g) ||
		  !PyInt_Check(b));
	    gimp_rgb_set_uchar(&ret[i].data.d_color, PyInt_AsLong(r),
			       PyInt_AsLong(g), PyInt_AsLong(b));
	    break;
	case GIMP_PDB_REGION:
	    check(!PySequence_Check(item) ||
		  PySequence_Length(item) < 4);
	    x = PySequence_GetItem(item, 0);
	    y = PySequence_GetItem(item, 1);
	    w = PySequence_GetItem(item, 2);
	    h = PySequence_GetItem(item, 3);
	    check(!PyInt_Check(x) || !PyInt_Check(y) ||
		  !PyInt_Check(w) || !PyInt_Check(h));
	    ret[i].data.d_region.x = PyInt_AsLong(x);
	    ret[i].data.d_region.y = PyInt_AsLong(y);
	    ret[i].data.d_region.width = PyInt_AsLong(w);
	    ret[i].data.d_region.height = PyInt_AsLong(h);
	    break;
	case GIMP_PDB_DISPLAY:
	    check(!pygimp_display_check(item));
	    ret[i].data.d_display=((PyGimpDisplay *)item)->ID;
	    break;
	case GIMP_PDB_IMAGE:
	    if (item == Py_None) {
		ret[i].data.d_image = -1;
		break;
	    }
	    check(!pygimp_image_check(item));
	    ret[i].data.d_image=((PyGimpImage *)item)->ID;
	    break;
	case GIMP_PDB_LAYER:
	    if (item == Py_None) {
		ret[i].data.d_layer = -1;
		break;
	    }
	    check(!pygimp_layer_check(item));
	    ret[i].data.d_layer=((PyGimpLayer *)item)->ID;
	    break;
	case GIMP_PDB_CHANNEL:
	    if (item == Py_None) {
		ret[i].data.d_channel = -1;
		break;
	    }
	    check(!pygimp_channel_check(item));
	    ret[i].data.d_channel=((PyGimpChannel *)item)->ID;
	    break;
	case GIMP_PDB_DRAWABLE:
	    if (item == Py_None) {
		ret[i].data.d_channel = -1;
		break;
	    }
	    check(!pygimp_drawable_check(item));
	    ret[i].data.d_channel=((PyGimpDrawable *)item)->ID;
	    break;
	case GIMP_PDB_SELECTION:
	    check(!pygimp_layer_check(item));
	    ret[i].data.d_selection=((PyGimpLayer *)item)->ID;
	    break;
	case GIMP_PDB_BOUNDARY:
	    check(!PyInt_Check(item));
	    ret[i].data.d_boundary = PyInt_AsLong(item);
	    break;
	case GIMP_PDB_PATH:
	    check(!PyInt_Check(item));
	    ret[i].data.d_path = PyInt_AsLong(item);
	    break;
	case GIMP_PDB_PARASITE:
	    /* can't do anything, since size of GimpParasite is not known */
	    break;
	case GIMP_PDB_STATUS:
	    check(!PyInt_Check(item));
	    ret[i].data.d_status = PyInt_AsLong(item);
	    break;
	case GIMP_PDB_END:
	    break;
	}
#undef check
#undef arraycheck
	ret[i].type = ptype[i-1].type;
    }

    Py_DECREF(tuple);
    return ret;
}

/* ---------------------------------------------------------------- */

static PyObject *
pdb_query(PyGimpPDB *self, PyObject *args)
{
    char *n=".*", *b=".*", *h=".*", *a=".*", *c=".*", *d=".*", *t=".*";
    int num, i;
    char **names;
    PyObject *ret;
    if (!PyArg_ParseTuple(args, "|zzzzzzz:gimp.pdb.query", &n, &b, &h, &a,
			  &c, &d, &t))
	return NULL;
    gimp_procedural_db_query(n, b, h, a, c, d, t, &num, &names);
    ret = PyList_New(num);
    for (i = 0; i < num; i++)
	PyList_SetItem(ret, i, PyString_FromString(names[i]));
    g_free(names);
    return ret;
}

static PyMethodDef pdb_methods[] = {
    {"query", (PyCFunction)pdb_query, METH_VARARGS},
    {NULL,		NULL}		/* sentinel */
};

/* ---------- */


PyObject *
pygimp_pdb_new(void)
{
    PyGimpPDB *self;
	
    self = PyObject_NEW(PyGimpPDB, &PyGimpPDB_Type);
    if (self == NULL)
	return NULL;
    return (PyObject *)self;
}


static void
pdb_dealloc(PyGimpPDB *self)
{
    PyObject_DEL(self);
}

static PyObject *
pdb_repr(PyGimpPDB *self)
{
    return PyString_FromString("<gimp procedural database>");
}

/* Code to access pdb objects as mappings */

static PyObject *
pdb_subscript(PyGimpPDB *self, PyObject *key)
{
    PyObject *r;

    if (!PyString_Check(key)) {
	PyErr_SetString(PyExc_TypeError, "Subscript must be a string");
	return NULL;
    }
    r = (PyObject *)pygimp_pdb_function_new_from_proc_db(PyString_AsString(key));
    if (r == NULL) {
	PyErr_Clear();
	PyErr_SetObject(PyExc_KeyError, key);
    }
    return r;
}

static PyMappingMethods pdb_as_mapping = {
    (inquiry)0,			/*mp_length*/
    (binaryfunc)pdb_subscript,	/*mp_subscript*/
    (objobjargproc)0,		/*mp_ass_subscript*/
};

/* -------------------------------------------------------- */

static PyObject *
pdb_getattro(PyGimpPDB *self, PyObject *attr)
{
    PyObject *ret;

    ret = PyObject_GenericGetAttr((PyObject *)self, attr);
    if (ret)
	return ret;
    PyErr_Clear();
    return pygimp_pdb_function_new_from_proc_db(PyString_AsString(attr));
}

PyTypeObject PyGimpPDB_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                  /* ob_size */
    "gimp.PDB",                         /* tp_name */
    sizeof(PyGimpPDB),                  /* tp_basicsize */
    0,                                  /* tp_itemsize */
    /* methods */
    (destructor)pdb_dealloc,           /* tp_dealloc */
    (printfunc)0,                       /* tp_print */
    (getattrfunc)0,                     /* tp_getattr */
    (setattrfunc)0,                     /* tp_setattr */
    (cmpfunc)0,                         /* tp_compare */
    (reprfunc)pdb_repr,                 /* tp_repr */
    0,                                  /* tp_as_number */
    0,                                  /* tp_as_sequence */
    &pdb_as_mapping,                     /* tp_as_mapping */
    (hashfunc)0,                        /* tp_hash */
    (ternaryfunc)0,                     /* tp_call */
    (reprfunc)0,                        /* tp_str */
    (getattrofunc)pdb_getattro,         /* tp_getattro */
    (setattrofunc)0,                    /* tp_setattro */
    0,					/* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,	                /* tp_flags */
    NULL, /* Documentation string */
    (traverseproc)0,			/* tp_traverse */
    (inquiry)0,				/* tp_clear */
    (richcmpfunc)0,			/* tp_richcompare */
    0,					/* tp_weaklistoffset */
    (getiterfunc)0,			/* tp_iter */
    (iternextfunc)0,			/* tp_iternext */
    pdb_methods,			/* tp_methods */
    0,					/* tp_members */
    0,					/* tp_getset */
    (PyTypeObject *)0,			/* tp_base */
    (PyObject *)0,			/* tp_dict */
    0,					/* tp_descr_get */
    0,					/* tp_descr_set */
    0,					/* tp_dictoffset */
    (initproc)0,	                /* tp_init */
    (allocfunc)0,			/* tp_alloc */
    (newfunc)0,				/* tp_new */
};

/* End of code for pdb objects */
/* -------------------------------------------------------- */


static PyObject *
pygimp_pdb_function_new_from_proc_db(char *name)
{
    PyObject *ret;
    char *b,*h,*a,*c,*d;
    int np, nr;
    GimpPDBProcType pt;
    GimpParamDef *p, *r;

    if (!gimp_procedural_db_proc_info (name, &b, &h, &a, &c, &d, &pt,
				       &np, &nr, &p, &r)) {
	PyErr_SetString(pygimp_error, "procedure not found");
	return NULL;
    }

    ret = pygimp_pdb_function_new(name, b, h, a, c, d, pt, np, nr, p, r);

    g_free(b); g_free(h); g_free(a); g_free(c); g_free(d);

    return ret;
}

static void
pf_dealloc(PyGimpPDBFunction *self)
{
    g_free(self->name);
    Py_DECREF(self->proc_name);
    Py_DECREF(self->proc_blurb);
    Py_DECREF(self->proc_help);
    Py_DECREF(self->proc_author);
    Py_DECREF(self->proc_copyright);
    Py_DECREF(self->proc_date);
    Py_DECREF(self->proc_type);
    Py_DECREF(self->py_params);
    Py_DECREF(self->py_return_vals);
    gimp_destroy_paramdefs(self->params, self->nparams);
    gimp_destroy_paramdefs(self->return_vals, self->nreturn_vals);
    PyObject_DEL(self);
}

#define OFF(x) offsetof(PyGimpPDBFunction, x)
static struct PyMemberDef pf_members[] = {
    {"proc_name",      T_OBJECT, OFF(proc_name),      RO},
    {"proc_blurb",     T_OBJECT, OFF(proc_blurb),     RO},
    {"proc_help",      T_OBJECT, OFF(proc_help),      RO},
    {"proc_author",    T_OBJECT, OFF(proc_author),    RO},
    {"proc_copyright", T_OBJECT, OFF(proc_copyright), RO},
    {"proc_date",      T_OBJECT, OFF(proc_date),      RO},
    {"proc_type",      T_OBJECT, OFF(proc_type),      RO},
    {"nparams",        T_INT,    OFF(nparams),        RO},
    {"nreturn_vals",   T_INT,    OFF(nreturn_vals),   RO},
    {"params",         T_OBJECT, OFF(py_params),      RO},
    {"return_vals",    T_OBJECT, OFF(py_return_vals), RO},
    {NULL}  /* Sentinel */
};
#undef OFF

static PyObject *
pf_repr(PyGimpPDBFunction *self)
{
    return PyString_FromFormat("<pdb function '%s'>",
			       PyString_AsString(self->proc_name));
}

static PyObject *
pf_call(PyGimpPDBFunction *self, PyObject *args, PyObject *kwargs)
{
    GimpParam *params, *ret;
    int nret;
    PyObject *t = NULL, *r;

#if PG_DEBUG > 0
    fprintf(stderr, "--- %s --- ", PyString_AsString(self->proc_name));
#endif
    if (self->nparams > 0 && !strcmp(self->params[0].name, "run_mode")) {
	params = pygimp_param_from_tuple(args, self->params+1, self->nparams-1);
	if (params == NULL)
	    return NULL;
	params[0].type = self->params[0].type;
	params[0].data.d_int32 = GIMP_RUN_NONINTERACTIVE;
#if PG_DEBUG > 1
	pygimp_param_print(self->nparams, params);
#endif
	ret = gimp_run_procedure2(self->name, &nret, self->nparams,
				  params);
    } else {
	params = pygimp_param_from_tuple(args, self->params, self->nparams);
	if (params == NULL)
	    return NULL;
#if PG_DEBUG > 1
	pygimp_param_print(self->nparams, params+1);
#endif
	ret = gimp_run_procedure2(self->name, &nret, self->nparams,
				  params+1);
    }
    gimp_destroy_params(params, self->nparams);
    if (!ret) {
	PyErr_SetString(pygimp_error, "no status returned");
#if PG_DEBUG >= 1
	fprintf(stderr, "ret == NULL\n");
#endif
	return NULL;
    }
    switch(ret[0].data.d_status) {
    case GIMP_PDB_EXECUTION_ERROR:
#if PG_DEBUG > 0
	fprintf(stderr, "execution error\n");
#endif
	gimp_destroy_params(ret, nret);
	PyErr_SetString(PyExc_RuntimeError, "execution error");
	return NULL;
	break;
    case GIMP_PDB_CALLING_ERROR:
#if PG_DEBUG > 0
	fprintf(stderr, "calling error\n");
#endif
	gimp_destroy_params(ret, nret);
	PyErr_SetString(PyExc_TypeError, "invalid arguments");
	return NULL;
	break;
    case GIMP_PDB_SUCCESS:
#if PG_DEBUG > 0
	fprintf(stderr, "success\n");
#endif
	t = pygimp_param_to_tuple(nret-1, ret+1);
	gimp_destroy_params(ret, nret);
	if (t == NULL) {
	    PyErr_SetString(pygimp_error,
			    "couldn't make return value");
	    return NULL;
	}
	break;
    default:
#if PG_DEBUG > 0
	fprintf(stderr, "unknown - %i (type %i)\n",
		ret[0].data.d_status, ret[0].type);
#endif
	PyErr_SetString(pygimp_error, "unknown return code");
	return NULL;
	break;
    }
    if (PyTuple_Size(t) == 1) {
	r = PyTuple_GetItem(t, 0);
	Py_INCREF(r);
	Py_DECREF(t);
	return r;
    }
    if (PyTuple_Size(t) == 0) {
	r = Py_None;
	Py_INCREF(r);
	Py_DECREF(t);
	return r;
    }
    return t;
}


PyTypeObject PyGimpPDBFunction_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                                  /* ob_size */
    "gimp.PDBFunction",                 /* tp_name */
    sizeof(PyGimpPDBFunction),          /* tp_basicsize */
    0,                                  /* tp_itemsize */
    /* methods */
    (destructor)pf_dealloc,             /* tp_dealloc */
    (printfunc)0,                       /* tp_print */
    (getattrfunc)0,                     /* tp_getattr */
    (setattrfunc)0,                     /* tp_setattr */
    (cmpfunc)0,                         /* tp_compare */
    (reprfunc)pf_repr,                  /* tp_repr */
    0,                                  /* tp_as_number */
    0,                                  /* tp_as_sequence */
    0,                                  /* tp_as_mapping */
    (hashfunc)0,                        /* tp_hash */
    (ternaryfunc)pf_call,               /* tp_call */
    (reprfunc)0,                        /* tp_str */
    (getattrofunc)0,                    /* tp_getattro */
    (setattrofunc)0,                    /* tp_setattro */
    0,					/* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,	                /* tp_flags */
    NULL, /* Documentation string */
    (traverseproc)0,			/* tp_traverse */
    (inquiry)0,				/* tp_clear */
    (richcmpfunc)0,			/* tp_richcompare */
    0,					/* tp_weaklistoffset */
    (getiterfunc)0,			/* tp_iter */
    (iternextfunc)0,			/* tp_iternext */
    0,					/* tp_methods */
    pf_members,				/* tp_members */
    0,					/* tp_getset */
    (PyTypeObject *)0,			/* tp_base */
    (PyObject *)0,			/* tp_dict */
    0,					/* tp_descr_get */
    0,					/* tp_descr_set */
    0,					/* tp_dictoffset */
    (initproc)0,	                /* tp_init */
    (allocfunc)0,			/* tp_alloc */
    (newfunc)0,				/* tp_new */
};

PyObject *
pygimp_pdb_function_new(const char *name, const char *blurb, const char *help,
			const char *author, const char *copyright,
			const char *date, GimpPDBProcType proc_type,
			int n_params, int n_return_vals,
			GimpParamDef *params, GimpParamDef *return_vals)
{
    PyGimpPDBFunction *self;
    int i;

    self = PyObject_NEW(PyGimpPDBFunction, &PyGimpPDBFunction_Type);
    if (self == NULL)
	return NULL;

    self->name = g_strdup(name);
    self->proc_name = PyString_FromString(name ? name : "");
    self->proc_blurb = PyString_FromString(blurb ? blurb : "");
    self->proc_help = PyString_FromString(help ? help : "");
    self->proc_author = PyString_FromString(author ? author : "");
    self->proc_copyright = PyString_FromString(copyright ? copyright : "");
    self->proc_date = PyString_FromString(date ? date : "");
    self->proc_type = PyInt_FromLong(proc_type);
    self->nparams = n_params;
    self->nreturn_vals = n_return_vals;
    self->params = params;
    self->return_vals = return_vals;

    self->py_params = PyTuple_New(n_params);
    for (i = 0; i < n_params; i++)
	PyTuple_SetItem(self->py_params, i,
			Py_BuildValue("(iss)",
				      params[i].type,
				      params[i].name,
				      params[i].description));

    self->py_return_vals = PyTuple_New(n_return_vals);
    for (i = 0; i < n_return_vals; i++)
	PyTuple_SetItem(self->py_return_vals, i,
			Py_BuildValue("(iss)",
				      return_vals[i].type,
				      return_vals[i].name,
				      return_vals[i].description));
    return (PyObject *)self;
}
