#include <signal.h>
#include <Python.h>
#include <entity.h>

#define PY_DEBUG 1


typedef struct {
    PyObject_HEAD ENode * enode;
} PyEnodeObject;


static void
py_enode_dealloc (PyEnodeObject * self)
{
    ECHECK_RET (self->enode != NULL);

    enode_unref (self->enode);

    PyMem_DEL (self);
}

static int
py_enode_print (PyEnodeObject * self, FILE * fp, int flags)
{
    EBuf *path;

    ECHECK_RETVAL (self->enode != NULL, -1);

    path = enode_path (self->enode);

    if (path) {
	fprintf (fp, "%s", path->str);
	ebuf_free (path);
    }

    return 0;
}

/* forward decl 'cause we need it here */
static struct PyMethodDef py_enode_methods[];

static PyObject *
py_enode_getattr (PyEnodeObject * self, char *name)
{
    ECHECK_RETVAL (self->enode != NULL, NULL);
    return Py_FindMethod (py_enode_methods, (PyObject *) self, name);
}

static int
py_enode_compare (PyEnodeObject * self, PyEnodeObject * other)
{
    EBuf *s;		/* Self. */
    EBuf *o;		/* Other node. */
    int result;

    ECHECK_RETVAL (self->enode != NULL, -1);

    /* equality means that their paths are the same. */
    s = enode_path (self->enode);
    o = enode_path (other->enode);

    result = strcmp ((const gchar *) s->str, (const gchar *) o->str);

    ebuf_free (s);
    ebuf_free (o);

    return result;
}

static int
py_enode_mapping_length (PyEnodeObject * self)
{
    int result;

    ECHECK_RETVAL (self->enode != NULL, 0);

    /* count # of set items?? */
    result = g_slist_length (self->enode->attribs) / 2;

    EDEBUG (("python-embed", "mapping_length: returning %i", result));

    return result;
}

static PyObject *
py_enode_mapping_subscript (PyEnodeObject * self, PyObject * key)
{
    gchar *key_str;
    EBuf *result;
    PyObject *result_py;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyString_Check (key)) {
	PyErr_SetString (PyExc_TypeError,
			 "Only string objects may be attribute keys.");
	return NULL;
    }

    key_str = PyString_AsString (key);
    EDEBUG (("python-embed", "py_enode_mapping_subscript: key is %s", key_str));

    result = enode_attrib (self->enode, key_str, NULL);

    if (result == NULL) {
	PyErr_SetString (PyExc_SystemError,
		"py_enode_mapping_subscript: Internal error in enode_attrib");
	return NULL;
    }

    EDEBUG (("python-embed", "mapping_subscript: result->str is %s; len is %i",
	     result->str, result->len));

    result_py = PyString_FromStringAndSize (result->str, result->len);

    EDEBUG (
	    ("python-embed",
	     "mapping_subscript: returning py_string of refcnt %i",
	     result_py->ob_refcnt));
    EDEBUG (
	    ("python-embed", "mapping_subscript: who's string is ->%s",
	     ((PyStringObject *) result_py)->ob_sval));

    return result_py;
}

/* Some classic #gimp humour for you..
  
<Slow> py_enode_mapping_ass_subscript(PyEnodeObject *self, PyObject *key, PyObject *val)
* jyon smacks linux & nfs
* tc tickles jyons navel
* Slow wonders if fatjim meant that as a joke or..
<yosh> ass_subscript?
* jyon crys
<Slow> hehe
<yosh> is he making anal rape jokes now too?
<CyBeR> heh
<tc> py_squirm_ass_insert(PyObject *thingy); ?
<Slow> haha
<yosh> PyDildo *red
<tc> add that function and see what he says ;)

*/


static int
py_enode_mapping_ass_subscript (PyEnodeObject * self, PyObject * key,
				PyObject * val)
{
    gchar *key_str;
    gchar *val_str;

    ECHECK_RETVAL (self->enode != NULL, 0);

    if (!(PyString_Check (key) && (PyString_Check (val)))) {
	PyErr_SetString (PyExc_TypeError,
		"Only string objects may be attribute keys or values.");
	return 0;
    }

    key_str = PyString_AsString (key);
    val_str = PyString_AsString (val);
    EDEBUG (("python-embed", "mapping_ass_subs: key is %s", key_str));
    EDEBUG (("python-embed", "mapping_ass_subs: val is %s", val_str));

    if (val == NULL) {
	enode_attrib (self->enode, key_str, ebuf_new_with_str (""));
    } else {
	/* set the key to val */
	EDEBUG (
		("python-embed", "mapping_ass_subs: setting attrib %s to %s.",
		 key_str, val_str));
	enode_attrib (self->enode, key_str, ebuf_new_with_str (val_str));
    }

    return 0;
}

static char py_enode__doc__[] =
    "ENode class. See the entity documentation for more information.";

static PyMappingMethods py_enode_as_mapping = {
    (inquiry) py_enode_mapping_length,			/* mp_length */
    (binaryfunc) py_enode_mapping_subscript,		/* mp_subscript */
    (objobjargproc) py_enode_mapping_ass_subscript,	/* mp_ass_subscript */
};


static PyTypeObject py_enode_type = {
    PyObject_HEAD_INIT (NULL)
    0,					/* ob_size */
    "ENode",				/* tp_name */
    sizeof (PyEnodeObject),		/* tp_basicsize */
    0,					/* tp_itemsize */

    /* methods */
    (destructor) py_enode_dealloc,	/* tp_dealloc */
    (printfunc) py_enode_print,		/* tp_print */
    (getattrfunc) py_enode_getattr,	/* tp_getattr */
    (setattrfunc) 0,			/* tp_setattr */
    (cmpfunc) py_enode_compare,		/* tp_compare */
    (reprfunc) 0,			/* tp_repr */
    0,					/* tp_as_number */
    0,					/* tp_as_sequence */
    &py_enode_as_mapping,		/* tp_as_mapping */
    (hashfunc) 0,			/* tp_hash */
    (ternaryfunc) 0,			/* tp_call */
    (reprfunc) 0,			/* tp_str */

    /* Space for future expansion */
    0L, 0L, 0L, 0L,
    py_enode__doc__			/* Documentation string */
};


static PyObject *
py_enode_new_from_enode (ENode * node)
{
    PyEnodeObject *self;

    /* The previous behavior was to spew and error and return NULL if
     * node == NULL, I think this is far too pedantic, and should be handled
     * by the application.  There are several places where a None return
     * (eg, in searches) is perfectly normal.  - Ian */
    if (node == NULL) {
	Py_INCREF (Py_None);
	return Py_None;
    }

    self = PyObject_NEW (PyEnodeObject, &py_enode_type);
    if (self == NULL) {
	EDEBUG (
		("python-embed",
		 "in py_enode_new_from_enode() couldn't construct pyobject!"));
	return NULL;
    }

    enode_ref (node);
    self->enode = node;

    return (PyObject *) self;
}

PyObject *
py_enode_glist_to_pylist (GSList * gslist)
{
    PyObject *pylist;
    GSList *p;
    int len;
    int i;

    if (gslist == NULL)
	EDEBUG (
		("python-embed",
		 "py_enode_glist_to_pylist: result of enode_children was NULL."));

    len = g_slist_length (gslist);
    EDEBUG (
	    ("python-embed", "py_enode_glist_to_pylist: the list is %d long.",
	     len));

    pylist = PyList_New (len);
    for (p = gslist, i = 0; p; p = p->next, i++) {
	PyList_SetItem (pylist, i,
			py_enode_new_from_enode ((ENode *) (p->data)));
    }

    EDEBUG (
	    ("python-embed",
	     "py_enode_new_from_enode: done adding to the list."));

    return pylist;
}

gchar *
get_python_namespace (void)
{
    return (enode_call_get_namespace ("python"));
}


PyObject *
py_new_child (PyEnodeObject * self, PyObject * args)
{
    int nattribs;
    int i;
    gchar *basename;
    gchar *keychar;
    gchar *valchar;
    PyObject *attribs_dict = NULL;
    PyObject *items;
    PyObject *item;
    PyObject *key;
    PyObject *val;
    ENode *new_node;
    GSList *attribs = NULL;

    ECHECK_RETVAL (self->enode != NULL, NULL);


    if (!PyArg_ParseTuple
	(args, "s|O!", &basename, &PyDict_Type, &attribs_dict)) return NULL;

    if (attribs_dict != NULL) {
	EDEBUG (("python-embed", "py_new_child: got an attribs dict."));

	items = PyMapping_Items (attribs_dict);
	nattribs = PyList_Size (items);

	for (i = 0; i < nattribs; i++) {
	    item = PyList_GetItem (items, i);

	    key = PyTuple_GetItem (item, 0);
	    val = PyTuple_GetItem (item, 1);

	    keychar = PyString_AsString (PyObject_Str (key));
	    valchar = PyString_AsString (PyObject_Str (val));

	    EDEBUG (("python-embed", "py_new_child: adding attrib %s, val %s",
		     keychar, valchar));

	    attribs = g_slist_prepend (attribs, ebuf_new_with_str (valchar));
	    attribs = g_slist_prepend (attribs, ebuf_new_with_str (keychar));
	}


	Py_XDECREF (items);
	EDEBUG (("python-embed", "py_new_child: done adding attribs to list."));
    }


    new_node = enode_new_child (self->enode, basename, attribs);

    if (new_node == NULL) {
	PyErr_SetString (PyExc_SystemError, "Could not create new child.");
	return NULL;
    }

    return py_enode_new_from_enode (new_node);
}

PyObject *
py_type (PyEnodeObject * self, PyObject * args)
{
    PyObject *result;
    EBuf *type;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    EDEBUG (("python-embed", "py_type: self refcnt is %i", (self->ob_refcnt)));


    type = enode_type (self->enode);
    EDEBUG (("python-embed", "py_type: type str is %s", type->str));

    result = PyString_FromString (type->str);
    EDEBUG (("python-embed", "py_type: returning a pystr refcnt %i val %s",
	     (PyStringObject *) result->ob_refcnt,
	     ((PyStringObject *) result)->ob_sval));

    return result;
}

PyObject *
py_path (PyEnodeObject * self, PyObject * args)
{
    EBuf *path;
    PyObject *result;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    EDEBUG (("python-embed", "py_path: self refcnt is %i", (self->ob_refcnt)));

    path = enode_path (self->enode);

    if (path == NULL) {
	PyErr_SetString (PyExc_SystemError, "enode_path returned NULL!");
	return NULL;
    }

    EDEBUG (("python-embed", "py_path: got path str, %s", path->str));

    result = PyString_FromString (path->str);
    EDEBUG (("python-embed", "py_path: got pystring for the path"));

    ebuf_free (path);
    EDEBUG (("python-embed", "py_path: have freed path enode"));

    return result;
}

PyObject *
py_basename (PyEnodeObject * self, PyObject * args)
{
    EBuf *basename;
    PyObject *result;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    basename = enode_basename (self->enode);
    result = PyString_FromString (basename->str);
    ebuf_free (basename);

    return result;
}

PyObject *
py_description (PyEnodeObject * self, PyObject * args)
{
    gchar *desc;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    desc = enode_description (self->enode);
    if (desc == NULL)
	desc = "";

    return PyString_FromString (desc);
}


PyObject *
py_parent (PyEnodeObject * self, PyObject * args)
{
    char *search = NULL;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "|s", &search))
	return NULL;

    return py_enode_new_from_enode (enode_parent (self->enode, search));
}

PyObject *
py_child (PyEnodeObject * self, PyObject * args)
{
    char *search;
    ENode *node;
    PyObject *py_node;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "s", &search))
	return NULL;

    node = enode_child (self->enode, search);
    if (node == NULL) {
	PyErr_SetString (PyExc_SystemError,
			 "py_child: enode_child returned null.");
	return NULL;
    }

    py_node = py_enode_new_from_enode (node);

    return py_node;
}

PyObject *
py_child_rx (PyEnodeObject * self, PyObject * args)
{
    char *regex;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "s", &regex))
	return NULL;

    return py_enode_new_from_enode (enode_child_rx (self->enode, regex));
}


PyObject *
py_children (PyEnodeObject * self, PyObject * args)
{
    char *search = NULL;
    GSList *result;
    PyObject *py_result;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "|s", &search))
	return NULL;

    result = enode_children (self->enode, search);
    py_result = py_enode_glist_to_pylist (result);
    g_slist_free (result);

    return py_result;
}

PyObject *
py_children_rx (PyEnodeObject * self, PyObject * args)
{
    char *regex;
    GSList *result;
    PyObject *py_result;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "s", &regex))
	return NULL;

    result = enode_children_rx (self->enode, regex);
    py_result = py_enode_glist_to_pylist (result);
    g_slist_free (result);

    return py_result;
}

PyObject *
py_children_attrib (PyEnodeObject * self, PyObject * args)
{
    gchar *attr;
    gchar *val;
    EBuf *val_ebuf;
    GSList *result;
    PyObject *py_result;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "ss", &attr, &val))
	return NULL;

    val_ebuf = ebuf_new_with_str (val);
    result = enode_children_attrib (self->enode, attr, val_ebuf);
    ebuf_free (val_ebuf);

    py_result = py_enode_glist_to_pylist (result);
    return py_result;
}

PyObject *
py_children_attrib_rx (PyEnodeObject * self, PyObject * args)
{
    gchar *attr;
    gchar *val;
    GSList *result;
    PyObject *py_result;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "ss", &attr, &val))
	return NULL;

    result = enode_children_attrib_rx (self->enode, attr, val);
    py_result = py_enode_glist_to_pylist (result);

    return py_result;
}




PyObject *
py_call (PyEnodeObject * self, PyObject * args)
{
    int n_args;
    int n_pyargs;
    int i;
    gchar *function;
    gchar *fmt;
    EBuf *retval;
    GSList *call_args = NULL;

    PyObject *py_function;
    PyObject *py_fmt;
    PyObject *py_retr;
    PyObject *tmp;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    /* WARNING: VERY UGLY CODE AHEAD. it's 4:15 am. */

    n_pyargs = PyTuple_Size (args);
    EDEBUG (
	    ("python-embed-call", "py_call: got args tuple of size %i",
	     n_pyargs));
    if (n_pyargs < 1) {
	PyErr_SetString (PyExc_TypeError, "require at least a function name");
	return NULL;
    }

    py_function = PyTuple_GetItem (args, 0);
    if (!PyString_Check (py_function)) {
	PyErr_SetString (PyExc_TypeError, "expected a string");
	return NULL;
    }
    function = PyString_AsString (py_function);

    EDEBUG (
	    ("python-embed-call", "py_call: got the function name %s",
	     function));

    /* in perl enode_call() we allow a NULL argument list to mean the same as 
     * an empty one, should probably do the same here. */

    if (n_pyargs > 1) {
	EDEBUG (
		("python-embed-call", "py_call: we have args. checking.",
		 function));

	py_fmt = PyTuple_GetItem (args, 1);
	if (!PyString_Check (py_fmt)) {
	    PyErr_SetString (PyExc_TypeError, "bad arguments.");
	    return NULL;
	}
	fmt = PyString_AsString (py_fmt);

	n_args = strlen (fmt);
	EDEBUG (
		("python-embed-call", "py_call: fmt %s has %i elements", fmt,
		 n_args));

	/* note that this check BREAKS if we're using the binary stuff, i
	 * think.. */
	if (n_args != (n_pyargs - 2)) {
	    PyErr_SetString (PyExc_TypeError,
			     "argument list and actual arguments don't jive");
	    return NULL;
	}

	for (i = 0; i < n_args; i++) {
	    PyObject *item = PyTuple_GetItem (args, i + 2);

	    EDEBUG (
		    ("python-embed-call", "py_call: processing argument no. %i",
		     i));

	    tmp = PyObject_Str (item);
	    EDEBUG (("python-embed-call", "py_call: processing item is %s",
		     PyString_AsString (tmp)));
	    Py_XDECREF (tmp);

	    switch (fmt[i]) {

	    case 'n':
		{
		    ENode *node;
		    EDEBUG (
			    ("python-embed-call",
			     "py_call: processing got an enode"));

		    if (item->ob_type != &py_enode_type) {
			PyErr_SetString (PyExc_TypeError, "expected an enode");
			return NULL;
		    }
		    node = ((PyEnodeObject *) item)->enode;
		    call_args = enode_call_push_node (call_args, node);
		}
		break;


	    case 'e':
	    case 'b':		/* since python strings are capable of
				 * holding binary data (ie, with embedded
				 * NULLs etc), we can just use strings. */
		EDEBUG (
			("python-embed-call",
			 "py_call: processing got an ebufor a binary"));
		if (!PyString_Check (item)) {
		    PyErr_SetString (PyExc_TypeError,
			"expected a string for an ebuf or binary argument.");
		    return NULL;
		}

		call_args =
		    enode_call_push_data (call_args, PyString_AsString (item),
					  PyString_Size (item));
		break;


	    case 's':
		EDEBUG (
			("python-embed-call",
			 "py_call: processing got a string"));
		if (!PyString_Check (item)) {
		    PyErr_SetString (PyExc_TypeError,
				"expected a string for a string argument.");
		    return NULL;
		}

		call_args =
		    enode_call_push_str (call_args, PyString_AsString (item));
		break;


	    case 'i':
		EDEBUG (
			("python-embed-call",
			 "py_call: processing got an int"));
		if (!PyInt_Check (item)) {
		    PyErr_SetString (PyExc_TypeError, "bad arguments.");
		    return NULL;
		}

		call_args =
		    enode_call_push_int (call_args, PyInt_AS_LONG (item));
		break;

	    default:
		/* bad args string */
		enode_call_free_arg_list_items (call_args);
		PyErr_SetString (PyExc_TypeError, "Bad argument string");
		return NULL;
	    }
	}
    }


    EDEBUG (("python-embed-call", "py_call: done args processing"));

    retval = enode_call_with_list (self->enode, function, call_args);

    if (!retval) {
	EDEBUG (
		("python-embed-call",
		 "py_call: retval was NULL. returning None"));
	Py_INCREF (Py_None);

	return Py_None;
    } else {
	EDEBUG (("python-embed-call", "py_call: retval was %s", retval->str));

	py_retr = PyString_FromStringAndSize (retval->str, retval->len);
	ebuf_free (retval);

	return py_retr;
    }
}

typedef EBuf *(*enode_attr_func) (ENode *, gchar *, EBuf *);

/* Helper function for py_attrib_common */
static void
py_attrib_set_with_func (ENode * node, GSList * attrs, GSList * vals,
			 enode_attr_func func)
{
    GSList *my_attrs;
    GSList *my_vals;

    for (my_attrs = attrs, my_vals = vals;
	 my_attrs && my_vals;
	 my_attrs = my_attrs->next, my_vals = my_vals->next) {
	gchar *attrib = my_attrs->data;
	gchar *value = my_vals->data;

	EDEBUG (("python-embed", "foo!"));
	EDEBUG (("python-embed", "py_attrib_set_with_func, setting %s to %s",
		 attrib, value));

	func (node, attrib, ebuf_new_with_str (value));

	g_free (attrib);
	g_free (value);
    }
}

PyObject *
py_attrib_common (PyEnodeObject * self, PyObject * args, enode_attr_func func)
{
    PyObject *this;

    EDEBUG (("python-embed", "py_attrib_common: entering."));

    this = PyTuple_GetItem (args, 0);

    EDEBUG (("python-embed", "py_attrib_common: got argument."));

    if (PyString_Check (this)) {
	/* return the value of the attribute whose name specified by 'this' */
	gchar *val = NULL;
	EBuf *ret;

	EDEBUG (("python-embed", "in py_attrib_common, arg is a string"));
	ret = enode_attrib (self->enode, PyString_AsString (this), NULL);
	if (ret) {
	    val = strdup (ret->str);

	    EDEBUG (
		    ("python-embed",
		     "in py_attrib_common, returning a pystring of %s", val));
	    return PyString_FromString (val);
	} else {
	    return Py_None;
	}
    }

    else if (PyMapping_Check (this)) {
	/* take the mapping and apply attribute changes using stringified
	 * keys as attribute names and the values as the attribute values */

	int j;
	int len;
	GSList *attrs = NULL;
	GSList *vals = NULL;
	gchar *attr;
	gchar *val;
	PyObject *a_pair;
	PyObject *conv1;
	PyObject *conv2;
	PyObject *pairs;

	pairs = PyMapping_Items (this);

	EDEBUG (("python-embed", "in py_attrib_common, arg is a dictionary"));

	len = PyMapping_Length (this);

	for (j = 0; j < len; j++) {
	    a_pair = PyList_GetItem (pairs, j);

	    conv1 = PyObject_Str (PyTuple_GetItem (a_pair, 0));
	    attr = strdup (PyString_AsString (conv1));

	    conv2 = PyObject_Str (PyTuple_GetItem (a_pair, 1));
	    val = strdup (PyString_AsString (conv2));

	    EDEBUG (("python-embed", "in py_attrib_common, addding %s -> %s",
		     attr, val));

	    attrs = g_slist_append (attrs, attr);
	    vals = g_slist_append (vals, val);

	    Py_XDECREF (conv1);
	    Py_XDECREF (conv2);
	}

	/* This also frees the individual items as it sets them */
	py_attrib_set_with_func (self->enode, attrs, vals, func);

	g_slist_free (vals);
	g_slist_free (attrs);

	EDEBUG (
		("python-embed",
		 "py_attrib_set_with_func, cleaing up and returning 'None'"));

	Py_XDECREF (pairs);

	Py_INCREF (Py_None);
	return Py_None;
    }

    EDEBUG (("python-embed", "py_attrib_set_with_func, incorrect arguments."));
    PyErr_SetString (PyExc_TypeError,
		"Function takes one argument of string or dictionary type.");
    /* clean up here. */
    return NULL;
}


PyObject *
py_attrib (PyEnodeObject * self, PyObject * args)
{
    ECHECK_RETVAL (self->enode != NULL, NULL);
    return py_attrib_common (self, args, enode_attrib);
}

PyObject *
py_attrib_quiet (PyEnodeObject * self, PyObject * args)
{
    ECHECK_RETVAL (self->enode != NULL, NULL);
    return py_attrib_common (self, args, enode_attrib_quiet);
}

PyObject *
py_attrib_is_true (PyEnodeObject * self, PyObject * args)
{
    gchar *attr;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "s", &attr))
	return NULL;

    return PyInt_FromLong (enode_attrib_is_true (self->enode, attr));
}

PyObject *
py_list_set_attribs (PyEnodeObject * self, PyObject * args)
{
    int num_attrs;
    int i;
    GSList *attrs;
    GSList *p;
    PyObject *py_attrs;		/* The attribs after pyob conversion. */
    EBuf *value;		/* The value for the curent attrib. */

    ECHECK_RETVAL (self->enode != NULL, NULL);

    attrs = enode_list_set_attribs (self->enode);

    num_attrs = g_slist_length (attrs);
    py_attrs = PyTuple_New (num_attrs);

    for (i = 0, p = attrs; p; p = p->next, i++) {
	value = (EBuf *) p->data;
	PyTuple_SetItem (py_attrs, i, PyString_FromString (value->str));
    }

    g_slist_free (attrs);

    return py_attrs;
}

PyObject *
py_supported_attribs (PyEnodeObject * self, PyObject * args)
{
    int num_attrs;
    int i;
    GSList *attrs;
    GSList *p;
    PyObject *py_attrs;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    attrs = enode_supported_attribs (self->enode);

    num_attrs = g_slist_length (attrs);
    py_attrs = PyTuple_New (num_attrs);

    for (i = 0, p = attrs; p; p = p->next, i++)
	PyTuple_SetItem (py_attrs, i, PyString_FromString ((gchar *) p->data));

    g_slist_free (attrs);

    return py_attrs;
}

PyObject *
py_attrib_description (PyEnodeObject * self, PyObject * args)
{
    gchar *desc;
    gchar *attr;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    PyArg_ParseTuple (args, "s", &attr);

    desc = enode_attrib_description (self->enode, attr);
    if (desc == NULL)
	desc = "";		/* FIXME: is this the right thing to do? */

    return PyString_FromString (desc);
}

PyObject *
py_attrib_value_type (PyEnodeObject * self, PyObject * args)
{
    gchar *attr;
    gchar *valtype;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "s", &attr))
	return NULL;

    valtype = enode_attrib_value_type (self->enode, attr);
    if (valtype == NULL) {
	PyErr_SetString (PyExc_TypeError,
			 "No such attribute or No values types listed.");
	return NULL;
    }

    return PyString_FromString (valtype);
}

PyObject *
py_attrib_possible_values (PyEnodeObject * self, PyObject * args)
{
    gchar *attr;
    gchar *valopts;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "s", &attr))
	return NULL;

    valopts = enode_attrib_possible_values (self->enode, attr);
    if (valopts == NULL) {
	PyErr_SetString (PyExc_TypeError,
			 "No such attribute or No value options listed.");
	return NULL;
    }

    return PyString_FromString (valopts);
}

PyObject *
py_attribs_sync (PyEnodeObject * self, PyObject * args)
{
    ECHECK_RETVAL (self->enode != NULL, NULL);

    enode_attribs_sync (self->enode);
    Py_INCREF (Py_None);
    return Py_None;
}

PyObject *
py_get_kv (PyEnodeObject * self, PyObject * args)
{
    ECHECK_RETVAL (self->enode != NULL, NULL);

    PyErr_SetString (PyExc_TypeError, "Not Implemented.");
    return NULL;
}

PyObject *
py_set_kv (PyEnodeObject * self, PyObject * args)
{
    ECHECK_RETVAL (self->enode != NULL, NULL);

    PyErr_SetString (PyExc_TypeError, "Not Implemented.");
    return NULL;
}

PyObject *
py_destroy (PyEnodeObject * self, PyObject * args)
{
    ECHECK_RETVAL (self->enode != NULL, NULL);

    enode_destroy (self->enode);
    /* I don't think this is correct, it should be handled by the reference
     * counting, and any further access to this node should access the
     * 'dangler' enode */
    /* self->enode = NULL; */

    Py_INCREF (Py_None);
    return Py_None;
}

PyObject *
py_destroy_children (PyEnodeObject * self, PyObject * args)
{
    ECHECK_RETVAL (self->enode != NULL, NULL);

    enode_destroy_children (self->enode);
    Py_INCREF (Py_None);
    return Py_None;
}

PyObject *
py_get_xml (PyEnodeObject * self, PyObject * args)
{
    EBuf *xml;
    PyObject *result;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    xml = enode_get_xml (self->enode);
    result = PyString_FromStringAndSize (xml->str, xml->len);
    ebuf_free (xml);

    return result;
}

PyObject *
py_get_child_xml (PyEnodeObject * self, PyObject * args)
{
    EBuf *xml;
    PyObject *result;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    xml = enode_get_child_xml (self->enode);
    result = PyString_FromStringAndSize (xml->str, xml->len);
    ebuf_free (xml);

    return result;
}

PyObject *
py_append_xml (PyEnodeObject * self, PyObject * args)
{
    gchar *xml_str;
    EBuf *xml_ebuf;
    int len;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "s#", &xml_str, &len))
	return NULL;

    xml_ebuf = ebuf_new_with_data (xml_str, len);
    enode_append_xml (self->enode, xml_ebuf);
    ebuf_free (xml_ebuf);

    Py_INCREF (Py_None);
    return Py_None;
}

PyObject *
py_set_data (PyEnodeObject * self, PyObject * args)
{
    gchar *data;
    int data_len;
    EBuf *data_ebuf;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "s#", &data, &data_len))
	return NULL;

    data_ebuf = ebuf_new_with_data (data, data_len);
    enode_set_data (self->enode, data_ebuf);
    ebuf_free (data_ebuf);

    Py_INCREF (Py_None);
    return Py_None;
}

PyObject *
py_get_data (PyEnodeObject * self, PyObject * args)
{
    PyObject *data_py;
    EBuf *data;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    data = enode_get_data (self->enode);

    if (data == NULL) {
	PyErr_SetString (PyExc_TypeError, "enode_get_data returned NULL.");
	return NULL;
    }

    data_py = PyString_FromStringAndSize (data->str, data->len);

    return data_py;
}

PyObject *
py_append_data (PyEnodeObject * self, PyObject * args)
{
    gchar *data;
    int data_len;
    EBuf *data_ebuf;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "s#", &data, &data_len))
	return NULL;

    data_ebuf = ebuf_new_with_data (data, data_len);
    enode_append_data (self->enode, data_ebuf);
    ebuf_free (data_ebuf);

    Py_INCREF (Py_None);
    return Py_None;
}

PyObject *
py_insert_data (PyEnodeObject * self, PyObject * args)
{
    gchar *data;
    int data_len;
    int offset;
    EBuf *data_ebuf;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "s#i", &data, &data_len, &offset))
	return NULL;

    data_ebuf = ebuf_new_with_data (data, data_len);
    enode_insert_data (self->enode, offset, data_ebuf);
    ebuf_free (data_ebuf);

    Py_INCREF (Py_None);
    return Py_None;
}

PyObject *
py_delete_data (PyEnodeObject * self, PyObject * args)
{
    int len;
    int offset;

    ECHECK_RETVAL (self->enode != NULL, NULL);

    if (!PyArg_ParseTuple (args, "ii", &offset, &len))
	return NULL;

    enode_delete_data (self->enode, offset, len);

    Py_INCREF (Py_None);
    return Py_None;
}

static struct PyMethodDef py_enode_methods[] = {
    /* Base Interface */
    {"new_child", (PyCFunction) py_new_child, METH_VARARGS},
    {"type", (PyCFunction) py_type, 0},
    {"path", (PyCFunction) py_path, 0},
    {"basename", (PyCFunction) py_basename, 0},
    {"description", (PyCFunction) py_description, 0},

    /* Node Search Functions */
    {"parent", (PyCFunction) py_parent, 1},
    {"child", (PyCFunction) py_child, 1},
    {"child_rx", (PyCFunction) py_child_rx, 1},
    {"children", (PyCFunction) py_children, METH_VARARGS},
    {"children_rx", (PyCFunction) py_children_rx, 1},
    {"children_attrib", (PyCFunction) py_children_attrib, 2},
    {"children_attrib_rx", (PyCFunction) py_children_attrib_rx, 2},

    /* Object Based Utils */
    {"call", (PyCFunction) py_call, METH_VARARGS},

    /* Attribute Properties and Attribute Support Queries */
    {"attrib", (PyCFunction) py_attrib, 1},
    {"attrib_quiet", (PyCFunction) py_attrib_quiet, 1},
    {"attrib_is_true", (PyCFunction) py_attrib_is_true, 1},
    {"list_set_attribs", (PyCFunction) py_list_set_attribs, 0},
    {"supported_attribs", (PyCFunction) py_supported_attribs, 0},
    {"attrib_description", (PyCFunction) py_attrib_description, 1},
    {"attrib_value_type", (PyCFunction) py_attrib_value_type, METH_VARARGS},
    
    {"attrib_possible_values", (PyCFunction) py_attrib_possible_values,
      METH_VARARGS},

    {"attribs_sync", (PyCFunction) py_attribs_sync, METH_VARARGS},

    /* Arbitrary key/value Attachment */
    {"set_kv", (PyCFunction) py_set_kv, METH_VARARGS},
    {"get_kv", (PyCFunction) py_get_kv, METH_VARARGS},

    /* Node destruction */
    {"destroy", (PyCFunction) py_destroy, METH_VARARGS},
    {"destroy_children", (PyCFunction) py_destroy_children, METH_VARARGS},

    /* Raw XML Interfaces */
    {"get_xml", (PyCFunction) py_get_xml, 0},
    {"get_child_xml", (PyCFunction) py_get_child_xml, 0},
    {"append_xml", (PyCFunction) py_append_xml, 1},

    /* Node Data interface */
    {"set_data", (PyCFunction) py_set_data, METH_VARARGS},
    {"get_data", (PyCFunction) py_get_data, METH_VARARGS},
    {"append_data", (PyCFunction) py_append_data, METH_VARARGS},
    {"insert_data", (PyCFunction) py_insert_data, METH_VARARGS},
    {"delete_data", (PyCFunction) py_delete_data, METH_VARARGS},
    {NULL, NULL}
};

/* This function can be called globally
 */
PyObject *
py_enode_rx (PyObject * self, PyObject * args)
{
    char *regex = NULL;			/* The second or only string. */
    ENode *result;			/* The found node. */
    PyObject *py_result;		/* Python result to return out. */

    if (!PyArg_ParseTuple (args, "|s", &regex))
	return NULL;

    if (!regex)				/* We have no arg. */
	regex = "";			/* Not NULL. */

    result = enode_rx (regex);
    py_result = py_enode_new_from_enode (result);
    return py_result;
}

PyObject *
py_elist (PyObject * self, PyObject * args)
{
    char *search = NULL;		/* The string we're searching for */
    GSList *result;
    PyObject *py_result;

    if (!PyArg_ParseTuple (args, "|s", &search))
	return NULL;

    /* We can't search for a null pointer but we can search for "" */
    if (search)
	result = elist (search);
    else
	result = elist ("");

    py_result = py_enode_glist_to_pylist (result);
    g_slist_free (result);
    return py_result;
}

PyObject *
py_elist_rx (PyObject * self, PyObject * args)
{
    char *search = NULL;		/* The string we're searching for */
    GSList *result;
    PyObject *py_result;

    if (!PyArg_ParseTuple (args, "|s", &search))
	return NULL;

    /* We can't search for a null pointer but we can search for "" */
    if (search)
	result = elist_rx (search);
    else
	result = elist_rx ("");

    py_result = py_enode_glist_to_pylist (result);
    g_slist_free (result);

    return py_result;
}

PyObject *
py_enode_constructor (PyObject * self, PyObject * args)
{
    char *path;
    ENode *node;
    PyObject *py_node;

    if (!PyArg_ParseTuple (args, "s", &path)) {
	EDEBUG (("python-embed", "enode could not parse its arguments."));
	return NULL;
    }

    EDEBUG (("python-embed", "in enode() parsed args, got path='%s'", path));

    node = enode (path);

    /* Again, dumbing it down again, and returning Py_None when the node is * 
     * not found. */

    if (node == NULL) {
	/* 
	 * PyErr_SetString(PyExc_TypeError, "No such node found.");
	 * return NULL;
	 */
	EDEBUG (("python-embed", "in enode() node retrieved was NULL!"));
	Py_INCREF (Py_None);
	return Py_None;
    }
    py_node = py_enode_new_from_enode (node);

    return py_node;
}

static struct PyMethodDef py_entity_module_methods[] = {
    {"enode", py_enode_constructor, 1},
    {"enode_rx", py_enode_rx, 1},
    {"elist", py_elist, 1},
    {"elist_rx", py_elist_rx, 1},
    {NULL, NULL}
};


/* Start up the interpreter. */
void
python_start (void)
{
    int argc;
    gchar **argv;
    static struct sigaction siga;

    siga.sa_handler = SIG_DFL;
    siga.sa_flags = 0;

    Py_Initialize ();
    argc = entity_get_argc ();
    argv = entity_get_argv ();
    PySys_SetArgv (argc, argv);

    EDEBUG (("python-embed", "python started"));
    Py_InitModule ("Entity", py_entity_module_methods);
    PyRun_SimpleString ("from Entity import *");

    sigaction (SIGINT, &siga, NULL);
}

static EBuf *
python_function_execute (ENode * calling_node, gchar * function, GSList * args)
{
    PyObject *args_obj;
    PyObject *func = NULL;
    PyObject *item = NULL;
    PyObject *result;
    PyObject *string_rep;
    PyObject *modules;
    PyObject *module;
    PyObject *attribs;

    GSList *tmp;
    GSList *objlist = NULL;	/* Keep track of objects which need freeing
				 * so we can free them when done */

    EBuf *return_val;
    gchar *namespace;
    LangArg *arg;

    gint i = 0;

    ECHECK_RETVAL (calling_node != NULL, NULL);
    ECHECK_RETVAL (function != NULL, NULL);

    EDEBUG (("python-embed", "Trying to call %s", function));

    /* 
     * Find the function (todo: the module dict should be cached, n'estpas?) */

    namespace = get_python_namespace ();

    modules = PyImport_GetModuleDict ();	/* can't fail */
    module = PyDict_GetItemString (modules, namespace);
    if (!module) {
	g_warning ("module %s not found trying to execute %s", namespace,
		   function);
	PyErr_Print ();
	return NULL;
    }

    attribs = PyModule_GetDict (module);	/* can't fail */

    func = PyDict_GetItemString (attribs, function);
    if (!func) {
	g_warning ("function (%s) doesn't exist in %s", function, namespace);
	return NULL;
    }

    /* Munge function arguments into a Python arguments tuple.
     * Check the value types and make an appropriate python value. */

    EDEBUG (("python-embed", "Munging Arguments:"));

    args_obj = PyTuple_New (g_slist_length (args));
    objlist = g_slist_append (objlist, args_obj);

    for (tmp = args; tmp; tmp = tmp->next) {
	arg = (LangArg *) tmp->data;

	if (arg->type == LANG_NODE) {
	    item = py_enode_new_from_enode (arg->data);
	    PyTuple_SetItem (args_obj, i, item);

	} else if (arg->type == LANG_STRING) {
	    item = Py_BuildValue ("s", (char *) arg->data);
	    PyTuple_SetItem (args_obj, i, item);

	} else if (arg->type == LANG_INT) {
	    item = Py_BuildValue ("i", arg->intdata);
	    PyTuple_SetItem (args_obj, i, item);

	} else if (arg->type == LANG_BINSTRING) {
	    item = Py_BuildValue ("s#", (char *) arg->data, arg->size);
	    PyTuple_SetItem (args_obj, i, item);

	} else if (arg->type == LANG_DOUBLE) {
	    item = Py_BuildValue ("d", arg->doubledata);
	    PyTuple_SetItem (args_obj, i, item);
	}

	i++;
	enode_call_free_arg (arg);
    }

    EDEBUG (("python-embed", "Done munging arguments, calling function"));

    result = PyEval_CallObject (func, args_obj);

    /* Do cleanup stuff now */
    for (tmp = objlist; tmp; tmp = tmp->next) {
	PyObject *obj = tmp->data;

	EDEBUG (("python-embed", "XDECREFfing a  %s, refcount %i",
		 obj->ob_type->tp_name, obj->ob_refcnt));
	Py_XDECREF (obj);
    }
    g_slist_free (objlist);

    if (!result) {
	g_warning ("function (%s) didn't execute correctly", function);
	PyErr_Print ();
	return NULL;
    }

    if (result != Py_None) {
	string_rep = PyObject_Str (result);
	return_val = ebuf_new_with_str (PyString_AsString (string_rep));
	EDEBUG (("python-embed", "returning value '%s'", return_val->str));
	/* Hopefully this is correct.. */
	Py_XDECREF (string_rep);
    } else {
	return_val = NULL;
    }
    return return_val;
}

/* Render the python node */
void
python_render (ENode * node)
{
    static gint initialized = FALSE;
    static EBuf *py_cmd = NULL;

    gchar *namespace;
    PyObject *code_pyobj;

    ECHECK_RET (node != NULL);
    ECHECK_RET (node->data != NULL);


    if (!initialized) {
	python_start ();
	initialized = TRUE;
    }

    if (!py_cmd)
	py_cmd = ebuf_new_sized (1024);

    ebuf_truncate (py_cmd, 0);
    ebuf_append_str (py_cmd, "\nfrom Entity import *\n");
    ebuf_append_ebuf (py_cmd, node->data);

    enode_call_reference_push (node);

    namespace = get_python_namespace ();

    code_pyobj = Py_CompileString (py_cmd->str, namespace, Py_file_input);
    if (code_pyobj == NULL) {
	g_warning
	    ("Could not compile a python data section"
	     " .. Here, have some traceback:");
	if (PyErr_Occurred () != NULL)
	    PyErr_Print ();
	return;
    }

    /* actually build the new module */
    PyImport_ExecCodeModule (namespace, code_pyobj);


    ebuf_truncate (py_cmd, 0);
    ebuf_append_str (py_cmd, "import ");
    ebuf_append_str (py_cmd, namespace);
    ebuf_append_str (py_cmd, "\n");
    PyRun_SimpleString (py_cmd->str);	/* this happens in main */


    enode_call_reference_pop ();
}

#ifdef STATIC_PYTHON
void
python_init (RendererFlags flags)
#else
void
renderer_init (RendererFlags flags)
#endif
{
    Element *element;

    if (flags & RENDERER_REGISTER) {
	element = g_new0 (Element, 1);
	element->render_func = python_render;
	element->tag = "python";
	element_register (element);

	/* Set both python and py so we can use either name. */
	language_register ("python", python_function_execute);
	language_register ("py", python_function_execute);
    }
}


syntax highlighted by Code2HTML, v. 0.9.1