/* _qhullmodule.c - Python module for Delaunay triangulations
 *
 * Copyright 2004-2006, 2009 Floris Bruynooghe
 *
 * This file is part of Delny.
 *
 * Delny 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.
 *
 * Delny 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 Delny; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA.
 *
 *
 * Authors: Floris Bruynooghe (flub) <floris.bruynooghe@gmail.com>
 */


#include <Python.h>

#include <stdlib.h>
#include <stdio.h>

#include <numpy/arrayobject.h>
#include <qhull/qhull.h>
#include <qhull/qset.h>		/* for FOREACHneighbor_() */
#include <qhull/poly.h>		/* for qh_vertexneighbors() */


//#define QHULL_31_COMP=1
#ifdef QHULL_31_COMP
char qh_version[] = "_qhullmodule dummy version";
#endif


/* Structures */
struct facet_lists {
	PyObject *facets;
	PyObject *facets_by_index;
};


/* Function declarations */
static PyObject *qhull_delny(PyObject *dummy, PyObject *args);
static void result_qhull_failure(int qhull_exitcode);
static PyObject *result_qhull_success(void);
static struct facet_lists *build_facets(void);
static PyObject *facet_to_pylist(const facetT* facet);
static PyObject *facet_to_pylist_by_index(const facetT* facet);
static PyObject *build_neighbours(void);
static PyObject *vertex_find_neighbours(const vertexT *vertex);
static PyObject *vertex_to_pytuple(const vertexT* vertex);


/* The doc string for the delny function. */
PyDoc_STRVAR(qhull_delny__doc__,
"  delny(nodes) --> (neighbours, facets, facets_by_index)\n\
\n\
A node is represented as a sequence of it's coordinates.  When this\n\
function returns a node this sequence will always be a tuple, however\n\
in it's argument it can be any sequence including Numarray objects.\n\
\n\
points: a sequence type (including Numarray object) of the nodes.\n\
  This generally takes the form of:\n\
  [(x,y,z,...),(x,y,z,...),(x,y,z,...),...]\n\
\n\
neighbours: the neighbours as a dictionary.  Every input point is a\n\
  key and its value is a list of the neighbouring nodes.\n\
\n\
facets: this is a list of facets (triangles in 2D).  Every facet is\n\
  represented by a list of nodes.\n\
\n\
facets_by_index: same as `facets' but uses the index of the position each\n\
  node had in the input list `nodes' to represent a node, not the\n\
  coordinates of the node.");


/** The public Python call.
 *
 * @note See the docstring for documentation.
 */
static PyObject *
qhull_delny(PyObject *dummy, PyObject *args)
{
	PyObject *result = NULL;
	PyObject *input = NULL;	/* input data as we get it from Python */
	PyArrayObject *array = NULL; /* the same input data but converted */
	coordT *points;		/* 1D array of points, dim places a point */
	boolT ismalloc = False;	/* True if qhull should free points in
				   qh_freeqhull() or reallocation */
	int dim;		/* dimension */
	int numpoints;		/* the number of points */
	int exitcode;		/* 0 if no error from qhull */
	char flags[] = "qhull d Qbb Qt Qz"; /* flags for qhull */

	/* Process input. */
	if (!PyArg_ParseTuple (args, "O", &input))
		return NULL;
	array = (PyArrayObject *)
		PyArray_ContiguousFromAny(input, NPY_DOUBLE, 2, 2);
	if (array == NULL) {
		Py_DECREF(input);
		return NULL;
	}
	dim = PyArray_DIM(array, 1);
	numpoints = PyArray_DIM(array, 0);
	points = (coordT *) PyArray_DATA(array);

	/* Call qhull */
	exitcode = qh_new_qhull(dim, numpoints, points,
				ismalloc, flags, NULL, stderr);
	if (!exitcode)
		result = result_qhull_success();
	else
		result_qhull_failure(exitcode);

	/* Free the memory */
	qh_freeqhull(qh_ALL);
	Py_DECREF(array);

	/* This is still NULL when qhull failed */
	return result;
} /* qhull_delny() */


/** Set Python exceptions for when qhull failed.
 *
 * @param qhull_exitcode is the exit code returned by the qhull call.
 *
 * @note Maybe there should be specific errors for delny instead of using
 * standard ones?
 */
static void
result_qhull_failure(int qhull_exitcode)
{
	switch (qhull_exitcode) {
	case qh_ERRinput:
		PyErr_BadInternalCall ();
		break;
	case qh_ERRsingular:
		PyErr_SetString(PyExc_ArithmeticError,
				"qhull singular input data");
		break;
	case qh_ERRprec:
		PyErr_SetString(PyExc_ArithmeticError,
				"qhull precision error");
		break;
	case qh_ERRmem:
		PyErr_NoMemory();
		break;
	case qh_ERRqhull:
		PyErr_SetString(PyExc_StandardError,
				"qhull internal error");
		break;
	}
} /* result_qhull_failure() */


/** Create the result of the qhull call.
 *
 * This funcion assumes the qhull call was successful and that all the qhull
 * information can be found under the global `qh' macro/structure.
 *
 * @return New reference to tuple of the neighbours, facets and
 * facets_by_index in that order.
 */
static PyObject *
result_qhull_success(void)
{
	PyObject *result = NULL;     /* tuple to return */
	PyObject *neighbours = NULL; /* PyList of neighbours */
	struct facet_lists *facets;  /* two PyLists of the facets */

	neighbours = build_neighbours();
	facets = build_facets();
	result = PyTuple_New(3);
	if (neighbours == NULL || facets == NULL || result == NULL)
		goto cleanup;
	PyTuple_SetItem (result, 0, neighbours);
	PyTuple_SetItem (result, 1, facets->facets);
	PyTuple_SetItem (result, 2, facets->facets_by_index);

	return result;

  cleanup:
	Py_XDECREF(result);
	Py_XDECREF(neighbours);
	if (facets != NULL)
		PyMem_Free(facets);
	return NULL;
} /* qhull_result_success() */


/** Build two lists of all the facets; one by coordinates, one by index.
 *
 * A facet is represented by a list of tuples, with each tuple being the
 * coordinates of a node in the facet.  Alternatively as just the index of the
 * node that specifies the position in the input list.
 *
 * @return struct facet_lists.  Note that this needs to be PyMem_Free()ed when
 * it is no longer needed.
 */
static struct facet_lists *
build_facets(void)
{
	struct facet_lists *result;	  /* return value */
	PyObject *facets = NULL;	  /* PyList of facets */
	PyObject *facets_by_index = NULL; /* PyList of facets by node index */
	PyObject *tmp = NULL;		  /* used to fill up result lists */
	facetT *facet;			  /* needed by FORALLfacets */
	int func_retval;		  /* return values of functions */

	facets = PyList_New(0);
	facets_by_index = PyList_New(0);
	if (facets == NULL || facets_by_index == NULL)
		goto cleanup;
	FORALLfacets {
		if (facet->upperdelaunay)
			continue;
		tmp = facet_to_pylist(facet);
		if (tmp == NULL)
			goto cleanup;
		func_retval = PyList_Append(facets, tmp);
		if (func_retval == -1)
			goto cleanup;
		Py_DECREF(tmp);
		tmp = facet_to_pylist_by_index(facet);
		if (tmp == NULL)
			goto cleanup;
		func_retval = PyList_Append(facets_by_index, tmp);
		if (func_retval == -1)
			goto cleanup;
		Py_DECREF(tmp);
	}
	result = (struct facet_lists *)
		PyMem_Malloc(sizeof(struct facet_lists));
	if (result == NULL) {
		Py_DECREF(facets);
		Py_DECREF(facets_by_index);
		return  (struct facet_lists *) PyErr_NoMemory();
	}
	result->facets = facets;
	result->facets_by_index = facets_by_index;
	return result;

  cleanup:
	Py_XDECREF(facets);
	Py_XDECREF(facets_by_index);
	Py_XDECREF(tmp);
	return NULL;
} /* build_factes() */


/** Convert a facet to it's PyList representation.
 *
 * The PyList representation is a list of tuples where the elements of
 * the tuples are the coordinates of the nodes in the facet.
 *
 * @return new reference to PyList of PyTuple's in facet.
 */
static PyObject *
facet_to_pylist(const facetT* facet)
{
	PyObject *facet_as_list = NULL; /* PyList to return */
	PyObject *node = NULL;		/* PyTuple to fill up facet_as_list */
	vertexT *vertex, **vertexp;	/* needed by FOREACHvertex_() */
	int func_retval;		/* return values of functions */

	facet_as_list = PyList_New(0);
	if (facet_as_list == NULL)
		return NULL;
	FOREACHvertex_(facet->vertices)	{
		node = vertex_to_pytuple(vertex);
		if (node == NULL)
			goto cleanup;
		func_retval = PyList_Append(facet_as_list, node);
		if (func_retval == -1)
			goto cleanup;
		Py_DECREF(node);
	}

	return facet_as_list;

  cleanup:
	Py_XDECREF(facet_as_list);
	Py_XDECREF(node);
	return NULL;
} /* facet_to_pylist() */


/** Convert a facet to it's PyList (by index) representation.
 *
 * This PyList representation of a facet is a list with only the index
 * numbers (integers) of every node in the facet.
 *
 * @result new reference to PyList of node indices in the facet.
 */
static PyObject *
facet_to_pylist_by_index(const facetT* facet)
{
	PyObject *facet_as_list = NULL; /* PyList to return */
	PyObject *node_index = NULL;	/* PyInt to fill up facet_as_list */
	vertexT *vertex, **vertexp;	/* needed by FOREACHvertex_() */
	int func_retval;		/* return values of functions */

	facet_as_list = PyList_New(0);
	if (facet_as_list == NULL)
		return NULL;
	FOREACHvertex_(facet->vertices)	{
		node_index = PyInt_FromLong(qh_pointid(vertex->point));
		if (node_index == NULL)
			goto cleanup;
		func_retval = PyList_Append(facet_as_list, node_index);
		if (func_retval == -1)
			goto cleanup;
		Py_DECREF(node_index);
	}

	return facet_as_list;

  cleanup:
	Py_XDECREF(facet_as_list);
	Py_XDECREF(node_index);
	return NULL;
} /* facet_to_pylist_by_index() */


/** Build a dictionary of all the neighbouring connections of the convex hull.
 *
 * In the Delaunay triangulation (or convex hull) all vertices are connected
 * to their `nearest neighbours' via `edges' or `ridges'.  This function will
 * build an association between a node and all its neighbours so one can
 * easily find the ridges.
 *
 * Since we use the Qz option to qhull we get an extra vertex back.  This
 * vertex will not have any neighbours so we can use this to strip this bogus
 * vertex.
 *
 * @return new reference to a PyDict where the key is a node as a PyTuple of
 *   its coordinates and the value is a PyList of the neighbours as PyTuples of
 *   their coordinates.
 */
static PyObject *
build_neighbours(void)
{
	PyObject *neighbours_dict = NULL; /* return value */
	PyObject *node = NULL;		  /* key of the above dict */
	PyObject *neighbours = NULL;	  /* value of the above dict */
	vertexT *vertex;		  /* needed by FORALLvertices */

	neighbours_dict = PyDict_New();
	if (neighbours_dict == NULL)
		goto cleanup;
	if (!qh VERTEXneighbors)
		qh_vertexneighbors();
	FORALLvertices {
		node = vertex_to_pytuple(vertex);
		if (node == NULL)
			goto cleanup;
		neighbours = vertex_find_neighbours(vertex);
		if (neighbours == NULL)
			goto cleanup;
		if (PySequence_Size(neighbours) == 0)
			continue;
		PyDict_SetItem(neighbours_dict, node, neighbours);
		Py_DECREF(node);
		Py_DECREF(neighbours);
	}

	return neighbours_dict;

  cleanup:
	Py_XDECREF(neighbours_dict);
	Py_XDECREF(node);
	Py_XDECREF(neighbours);
	return NULL;
} /* build_neighbours() */


/** Find all the neighbours of a certain vertex.
 *
 * @return new reference to PyList of PyTuples (the neighbours).
 */
static PyObject *
vertex_find_neighbours(const vertexT *myvertex)
{
	PyObject *neighbours = NULL;   /* list of neighbours to return */
	PyObject *myvertex_as_pytuple = NULL;
	PyObject *neighbour = NULL;    /* to fill neighbours */
	vertexT *vertex, **vertexp;    /* needed by FOREACHvertex_() */
	facetT *neighbor, **neighborp; /* needed by FOREACHneigbor_() */

	neighbours = PyList_New(0);
	myvertex_as_pytuple = vertex_to_pytuple(myvertex);
	if (neighbours == NULL || myvertex_as_pytuple == NULL)
		goto cleanup;
	FOREACHneighbor_(myvertex) {
		/* NOTE: `neighbor' is a facet, not a vertex. */
		if (neighbor->upperdelaunay)
			continue;
		/* FIXME: too much indentation? */
		FOREACHvertex_(neighbor->vertices) {
			neighbour = vertex_to_pytuple(vertex);
			if (neighbour == NULL)
				goto cleanup;
			if (PyObject_Compare(myvertex_as_pytuple, neighbour)
			    && !PySequence_Contains(neighbours, neighbour))
				PyList_Append(neighbours, neighbour);
			Py_DECREF(neighbour);
		}
	}

	return neighbours;

  cleanup:
	Py_XDECREF(neighbours);
	Py_XDECREF(myvertex_as_pytuple);
	Py_XDECREF(neighbour);
	return NULL;
} /* vertex_find_neighbours() */


/** Create a PyTuple from a vertexT.
 *
 * @return a new reference to PyTuple of the coordinates.
 */
static PyObject *
vertex_to_pytuple(const vertexT* vertex)
{
	PyObject *tuple = NULL;
	PyObject *tmp = NULL;
	int i;

	tuple = PyTuple_New(qh hull_dim - 1);
	if (tuple == NULL)
		goto cleanup;
	for (i = 0; i < (qh hull_dim - 1); i++) {
		tmp = PyFloat_FromDouble((double)vertex->point[i]);
		if (tmp == NULL)
			goto cleanup;
		PyTuple_SetItem(tuple, i, tmp);
	}

	return tuple;

  cleanup:
	Py_XDECREF(tuple);
	Py_XDECREF(tmp);
	return NULL;
} /* vertex_to_pytuple() */



/* Tell Python wich methods are available in this module. */
static PyMethodDef QhullMethods[] = {
	{"delny", qhull_delny, METH_VARARGS, qhull_delny__doc__},
	{NULL, NULL, 0, NULL}
};


/* Initialise the module. */
PyMODINIT_FUNC
init_qhull(void)
{
	(void) Py_InitModule("_qhull", QhullMethods);
	import_array()
}



/**** For Emacs
 * Local Variables: 
 * c-file-style: "python"
 * End:
 ****/
