/* pygts - python package for the manipulation of triangulated surfaces
 *
 *   Copyright (C) 2009 Thomas J. Duck
 *   All rights reserved.
 *
 *   Thomas J. Duck <tom.duck@dal.ca>
 *   Department of Physics and Atmospheric Science,
 *   Dalhousie University, Halifax, Nova Scotia, Canada, B3H 3J5
 *
 * NOTICE
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   This library 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
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the
 *   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 *   Boston, MA 02111-1307, USA.
 */

#include "pygts.h"

#if PYGTS_HAS_NUMPY
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include "numpy/arrayobject.h"
#endif

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"

static PyObject* merge(PyObject* self, PyObject* args)
{
	PyObject *   tuple, *obj;
	guint        i, N;
	GList *      vertices = NULL, *v;
	gdouble      epsilon;
	PygtsVertex* vertex;

	/* Parse the args */
	if (!PyArg_ParseTuple(args, "Od", &tuple, &epsilon)) { return NULL; }
	if (PyList_Check(tuple)) {
		tuple = PyList_AsTuple(tuple);
	} else {
		Py_INCREF(tuple);
	}
	if (!PyTuple_Check(tuple)) {
		Py_DECREF(tuple);
		PyErr_SetString(PyExc_TypeError, "expected a list or tuple of vertices");
		return NULL;
	}

	/* Assemble the GList */
	N = PyTuple_Size(tuple);
	for (i = N - 1; i > 0; i--) {
		obj = PyTuple_GET_ITEM(tuple, i);
		if (!pygts_vertex_check(obj)) {
			Py_DECREF(tuple);
			g_list_free(vertices);
			PyErr_SetString(PyExc_TypeError, "expected a list or tuple of vertices");
			return NULL;
		}
		vertices = g_list_prepend(vertices, PYGTS_VERTEX_AS_GTS_VERTEX(obj));
	}
	Py_DECREF(tuple);

	/* Make the call */
	vertices = pygts_vertices_merge(vertices, epsilon, NULL);

	/* Assemble the return tuple */
	N = g_list_length(vertices);
	if ((tuple = PyTuple_New(N)) == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create tuple");
		return NULL;
	}
	v = vertices;
	for (i = 0; i < N; i++) {
		if ((vertex = PYGTS_VERTEX(g_hash_table_lookup(obj_table, GTS_OBJECT(v->data)))) == NULL) {
			PyErr_SetString(PyExc_RuntimeError, "could not get object from table (internal error)");
			g_list_free(vertices);
			return NULL;
		}
		Py_INCREF(vertex);
		PyTuple_SET_ITEM(tuple, i, (PyObject*)vertex);
		v = g_list_next(v);
	}

	g_list_free(vertices);

	return tuple;
}


static PyObject* vertices(PyObject* self, PyObject* args)
{
	PyObject *   tuple, *obj;
	guint        i, N;
	GSList *     segments = NULL, *vertices = NULL, *v;
	PygtsVertex* vertex;

	/* Parse the args */
	if (!PyArg_ParseTuple(args, "O", &tuple)) { return NULL; }
	if (PyList_Check(tuple)) {
		tuple = PyList_AsTuple(tuple);
	} else {
		Py_INCREF(tuple);
	}
	if (!PyTuple_Check(tuple)) {
		Py_DECREF(tuple);
		PyErr_SetString(PyExc_TypeError, "expected a list or tuple of Segments");
		return NULL;
	}

	/* Assemble the GSList */
	N = PyTuple_Size(tuple);
	for (i = 0; i < N; i++) {
		obj = PyTuple_GET_ITEM(tuple, N - 1 - i);
		if (!pygts_segment_check(obj)) {
			Py_DECREF(tuple);
			g_slist_free(segments);
			PyErr_SetString(PyExc_TypeError, "expected a list or tuple of Segments");
			return NULL;
		}
		segments = g_slist_prepend(segments, PYGTS_SEGMENT_AS_GTS_SEGMENT(obj));
	}
	Py_DECREF(tuple);

	/* Make the call */
	vertices = gts_vertices_from_segments(segments);
	g_slist_free(segments);

	/* Assemble the return tuple */
	N = g_slist_length(vertices);
	if ((tuple = PyTuple_New(N)) == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create tuple");
		return NULL;
	}
	v = vertices;
	for (i = 0; i < N; i++) {
		if ((vertex = pygts_vertex_new(GTS_VERTEX(v->data))) == NULL) {
			Py_DECREF(tuple);
			g_slist_free(vertices);
			return NULL;
		}
		PyTuple_SET_ITEM(tuple, i, (PyObject*)vertex);
		v = g_slist_next(v);
	}

	g_slist_free(vertices);

	return tuple;
}


static PyObject* segments(PyObject* self, PyObject* args)
{
	PyObject *    tuple, *obj;
	guint         i, n, N;
	GSList *      segments = NULL, *vertices = NULL, *s;
	PygtsSegment* segment;

	/* Parse the args */
	if (!PyArg_ParseTuple(args, "O", &tuple)) { return NULL; }
	if (PyList_Check(tuple)) {
		tuple = PyList_AsTuple(tuple);
	} else {
		Py_INCREF(tuple);
	}
	if (!PyTuple_Check(tuple)) {
		Py_DECREF(tuple);
		PyErr_SetString(PyExc_TypeError, "expected a list or tuple of vertices");
		return NULL;
	}

	/* Assemble the GSList */
	N = PyTuple_Size(tuple);
	for (i = 0; i < N; i++) {
		obj = PyTuple_GET_ITEM(tuple, i);
		if (!pygts_vertex_check(obj)) {
			Py_DECREF(tuple);
			g_slist_free(vertices);
			PyErr_SetString(PyExc_TypeError, "expected a list or tuple of vertices");
			return NULL;
		}
		vertices = g_slist_prepend(vertices, PYGTS_VERTEX_AS_GTS_VERTEX(obj));
	}
	Py_DECREF(tuple);

	/* Make the call */
	if ((segments = gts_segments_from_vertices(vertices)) == NULL) {
		PyErr_SetString(PyExc_RuntimeError, "could not retrieve segments");
		return NULL;
	}
	g_slist_free(vertices);

	/* Assemble the return tuple */
	N = g_slist_length(segments);
	if ((tuple = PyTuple_New(N)) == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create tuple");
		return NULL;
	}

	s = segments;
	n = 0;
	while (s != NULL) {
		/* Skip any parent segments */
		if (PYGTS_IS_PARENT_SEGMENT(s->data) || PYGTS_IS_PARENT_EDGE(s->data)) {
			s       = g_slist_next(s);
			segment = NULL;
			continue;
		}

		/* Fill in the tuple */
		if (GTS_IS_EDGE(s->data)) {
			segment = PYGTS_SEGMENT(pygts_edge_new(GTS_EDGE(s->data)));
		} else {
			segment = pygts_segment_new(GTS_SEGMENT(s->data));
		}
		if (segment == NULL) {
			Py_DECREF(tuple);
			g_slist_free(segments);
			return NULL;
		}
		PyTuple_SET_ITEM(tuple, n, (PyObject*)segment);
		s = g_slist_next(s);
		n += 1;
	}

	g_slist_free(segments);

	if (_PyTuple_Resize(&tuple, n) != 0) {
		Py_DECREF(tuple);
		return NULL;
	}

	return tuple;
}


static PyObject* triangles(PyObject* self, PyObject* args)
{
	PyObject *     tuple, *obj;
	guint          i, n, N;
	GSList *       edges = NULL, *triangles = NULL, *t;
	PygtsTriangle* triangle;

	/* Parse the args */
	if (!PyArg_ParseTuple(args, "O", &tuple)) { return NULL; }
	if (PyList_Check(tuple)) {
		tuple = PyList_AsTuple(tuple);
	} else {
		Py_INCREF(tuple);
	}
	if (!PyTuple_Check(tuple)) {
		Py_DECREF(tuple);
		PyErr_SetString(PyExc_TypeError, "expected a list or tuple of edges");
		return NULL;
	}

	/* Assemble the GSList */
	N = PyTuple_Size(tuple);
	for (i = 0; i < N; i++) {
		obj = PyTuple_GET_ITEM(tuple, i);
		if (!pygts_edge_check(obj)) {
			Py_DECREF(tuple);
			g_slist_free(edges);
			PyErr_SetString(PyExc_TypeError, "expected a list or tuple of edges");
			return NULL;
		}
		edges = g_slist_prepend(edges, PYGTS_EDGE_AS_GTS_EDGE(obj));
	}
	Py_DECREF(tuple);

	/* Make the call */
	if ((triangles = gts_triangles_from_edges(edges)) == NULL) {
		PyErr_SetString(PyExc_RuntimeError, "could not retrieve triangles");
		return NULL;
	}
	g_slist_free(edges);

	/* Assemble the return tuple */
	N = g_slist_length(triangles);
	if ((tuple = PyTuple_New(N)) == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create tuple");
		return NULL;
	}

	t = triangles;
	n = 0;
	while (t != NULL) {
		/* Skip any parent triangles */
		if (PYGTS_IS_PARENT_TRIANGLE(t->data)) {
			t        = g_slist_next(t);
			triangle = NULL;
			continue;
		}

		/* Fill in the tuple */
		if (GTS_IS_FACE(t->data)) {
			triangle = PYGTS_TRIANGLE(pygts_face_new(GTS_FACE(t->data)));
		} else {
			triangle = pygts_triangle_new(GTS_TRIANGLE(t->data));
		}
		if (triangle == NULL) {
			Py_DECREF(tuple);
			g_slist_free(triangles);
			return NULL;
		}
		PyTuple_SET_ITEM(tuple, n, (PyObject*)triangle);
		t = g_slist_next(t);
		n += 1;
	}

	g_slist_free(triangles);

	if (_PyTuple_Resize(&tuple, n) != 0) {
		Py_DECREF(tuple);
		return NULL;
	}

	return tuple;
}


static PyObject* triangle_enclosing(PyObject* self, PyObject* args)
{
	PyObject *     tuple, *obj;
	guint          i, N;
	GSList*        points = NULL;
	GtsTriangle*   t;
	PygtsTriangle* triangle;

	/* Parse the args */
	if (!PyArg_ParseTuple(args, "O", &tuple)) { return NULL; }
	if (PyList_Check(tuple)) {
		tuple = PyList_AsTuple(tuple);
	} else {
		Py_INCREF(tuple);
	}
	if (!PyTuple_Check(tuple)) {
		Py_DECREF(tuple);
		PyErr_SetString(PyExc_TypeError, "expected a list or tuple of points");
		return NULL;
	}

	/* Assemble the GSList */
	N = PyTuple_Size(tuple);
	for (i = 0; i < N; i++) {
		obj = PyTuple_GET_ITEM(tuple, i);
		if (!pygts_point_check(obj)) {
			Py_DECREF(tuple);
			g_slist_free(points);
			PyErr_SetString(PyExc_TypeError, "expected a list or tuple of points");
			return NULL;
		}
		points = g_slist_prepend(points, PYGTS_POINT_AS_GTS_POINT(obj));
	}
	Py_DECREF(tuple);

	/* Make the call */
	t = gts_triangle_enclosing(gts_triangle_class(), points, 1.0);
	g_slist_free(points);

	if (t == NULL) {
		PyErr_SetString(PyExc_RuntimeError, "could not compute triangle");
		return NULL;
	}

	if ((triangle = pygts_triangle_new(t)) == NULL) { return NULL; }

	return (PyObject*)triangle;
}


#if PY_MAJOR_VERSION >= 3
// taken from:
// https://github.com/mapserver/mapserver/issues/4748
/* Translate Python's built-in file object to FILE * */
static FILE* streamFromPyFile(PyObject* file, const char* mode)
{
	int   fd;
	FILE* fs;

	fd = PyObject_AsFileDescriptor(file);
	if (fd < 0) return NULL;

	fd = dup(fd);
	if (fd < 0) return NULL;

	fs = fdopen(fd, mode);
	if (fs == NULL) {
		close(fd);
		return NULL;
	}
	return fs;
}
#endif

FILE* FILE_from_py_file__raises(PyObject* f_, const char* mode)
{
	FILE* f;
#if PY_MAJOR_VERSION >= 3
	f = streamFromPyFile(f_, mode);
	if (f == NULL) {
		PyErr_SetString(PyExc_TypeError, "failed to obtained FILE* from the Python object (python 3 only).");
		return NULL;
	}
#else
	if (!PyFile_Check(f_)) {
		PyErr_SetString(PyExc_TypeError, "expected a File");
		return NULL;
	}
	f = PyFile_AsFile(f_);
#endif
	return f;
}


static PyObject* pygts_read(PygtsSurface* self, PyObject* args)
{
	PyObject*     f_;
	FILE*         f;
	GtsFile*      fp;
	guint         lineno;
	GtsSurface*   s;
	PygtsSurface* surface;

	/* Parse the args */
	if (!PyArg_ParseTuple(args, "O", &f_)) return NULL;

	f = FILE_from_py_file__raises(f_, "r");
	if (!f) return NULL;

	if (feof(f)) {
		PyErr_SetString(PyExc_EOFError, "End of File");
		return NULL;
	}

	/* Create a temporary surface to read into */
	if ((s = gts_surface_new(gts_surface_class(), gts_face_class(), gts_edge_class(), gts_vertex_class())) == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create Surface");
		return NULL;
	}

	/* Read from the file */
	fp = gts_file_new(f);
	if ((lineno = gts_surface_read(s, fp)) != 0) {
		PyErr_SetString(PyExc_RuntimeError, fp->error);
		gts_file_destroy(fp);
		return NULL;
	}
	gts_file_destroy(fp);
	if ((surface = pygts_surface_new(s)) == NULL) {
		gts_object_destroy(GTS_OBJECT(s));
		return NULL;
	}

	/* Clean up the surface */
	pygts_edge_cleanup(PYGTS_SURFACE_AS_GTS_SURFACE(surface));
	pygts_face_cleanup(PYGTS_SURFACE_AS_GTS_SURFACE(surface));

	return (PyObject*)surface;
}


static PyObject* sphere(PyObject* self, PyObject* args)
{
	PyObject*     kwds;
	PygtsSurface* surface;
	guint         geodesation_order;

	/* Parse the args */
	if (!PyArg_ParseTuple(args, "i", &geodesation_order)) return NULL;

	/* Chain up object allocation */
	args    = Py_BuildValue("()");
	kwds    = Py_BuildValue("{s:O}", "alloc_gtsobj", Py_True);
	surface = PYGTS_SURFACE(PygtsSurfaceType.tp_new(&PygtsSurfaceType, args, kwds));
	Py_DECREF(args);
	Py_DECREF(kwds);
	if (surface == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create Surface");
		return NULL;
	}

	gts_surface_generate_sphere(PYGTS_SURFACE_AS_GTS_SURFACE(surface), geodesation_order);

	pygts_object_register(PYGTS_OBJECT(surface));
	return (PyObject*)surface;
}


#if PYGTS_HAS_NUMPY

/* Helper for pygts_iso to fill f with a layer of data from scalar */
static void isofunc(gdouble** f, GtsCartesianGrid g, guint k, gpointer data)
{
	PyArrayObject* scalars = (PyArrayObject*)data;
	int            i, j;

	for (i = 0; i < PyArray_DIMS(scalars)[0]; i++) {
		for (j = 0; j < PyArray_DIMS(scalars)[1]; j++) {
			f[i][j]
			        = *((gdouble*)PyArray_DATA(scalars) + i * PyArray_STRIDES(scalars)[0] + j * PyArray_STRIDES(scalars)[1]
			            + k * PyArray_STRIDES(scalars)[2]);
		}
	}
}

#define ISO_CLEANUP                                                                                                                                            \
	if (scalars) { Py_DECREF(scalars); }                                                                                                                   \
	if (extents) { Py_DECREF(extents); }

static PyObject* isosurface(PyObject* self, PyObject* args, PyObject* kwds)
{
	double           isoval[1];
	PyObject *       Oscalars = NULL, *Oextents = NULL;
	PyArrayObject *  scalars = NULL, *extents = NULL;
	GtsCartesianGrid g;
	GtsSurface*      s;
	PygtsSurface*    surface;
	char*            method = "cubes";

	static char* kwlist[] = { "scalars", "isoval", "method", "extents", NULL };

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "Od|sO", kwlist, &Oscalars, isoval, &method, &Oextents)) { return NULL; }

	if (!(scalars = (PyArrayObject*)PyArray_ContiguousFromObject(Oscalars, NPY_DOUBLE, 3, 3))) {
		ISO_CLEANUP;
		return NULL;
	}

	if (Oextents && (!(extents = (PyArrayObject*)PyArray_ContiguousFromObject(Oextents, NPY_DOUBLE, 1, 1)))) {
		ISO_CLEANUP;
		return NULL;
	}

	if (extents && PyArray_DIMS(extents)[0] < 6) {
		PyErr_SetString(PyExc_ValueError, "extents must have at least 6 elements");
		ISO_CLEANUP;
		return NULL;
	}

	if (extents) {
		int s = PyArray_STRIDES(extents)[0];
		g.x   = *((gdouble*)PyArray_DATA(extents) + 0 * s);
		g.nx  = PyArray_DIMS(scalars)[0];
		g.dx  = (*((gdouble*)PyArray_DATA(extents) + 1 * s) - *((gdouble*)PyArray_DATA(extents) + 0 * s)) / (g.nx - 1);

		g.y  = *((gdouble*)PyArray_DATA(extents) + 2 * s);
		g.ny = PyArray_DIMS(scalars)[1];
		g.dy = (*((gdouble*)PyArray_DATA(extents) + 3 * s) - *((gdouble*)PyArray_DATA(extents) + 2 * s)) / (g.ny - 1);

		g.z  = *((gdouble*)PyArray_DATA(extents) + 4 * s);
		g.nz = PyArray_DIMS(scalars)[2];
		g.dz = (*((gdouble*)PyArray_DATA(extents) + 5 * s) - *((gdouble*)PyArray_DATA(extents) + 4 * s)) / (g.nz - 1);
	} else {
		g.x  = -1.0;
		g.nx = PyArray_DIMS(scalars)[0];
		g.dx = 2.0 / (PyArray_DIMS(scalars)[0] - 1);
		g.y  = -1.0;
		g.ny = PyArray_DIMS(scalars)[1];
		g.dy = 2.0 / (PyArray_DIMS(scalars)[1] - 1);
		g.z  = -1.0;
		g.nz = PyArray_DIMS(scalars)[2];
		g.dz = 2.0 / (PyArray_DIMS(scalars)[2] - 1);
	}

	/* Create the surface */
	if ((s = gts_surface_new(gts_surface_class(), gts_face_class(), gts_edge_class(), gts_vertex_class())) == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create Surface");
		return NULL;
	}

	/* Make the call */
	switch (method[0]) {
		case 'c': /* cubes */ gts_isosurface_cartesian(s, g, isofunc, scalars, isoval[0]); break;
		case 't': /* tetra */
			gts_isosurface_tetra(s, g, isofunc, scalars, isoval[0]);
			/* *** ATTENTION ***
     * Isosurface produced is "inside-out", and so we must revert it.
     * This is a bug in GTS.
     */
			gts_surface_foreach_face(s, (GtsFunc)gts_triangle_revert, NULL);
			/* *** ATTENTION *** */
			break;
		case 'b': /* tetra bounded */
			gts_isosurface_tetra_bounded(s, g, isofunc, scalars, isoval[0]);
			/* *** ATTENTION ***
     * Isosurface produced is "inside-out", and so we must revert it.
     * This is a bug in GTS.
     */
			gts_surface_foreach_face(s, (GtsFunc)gts_triangle_revert, NULL);
			/* *** ATTENTION *** */
			break;
		case 'd': /* tetra bcl*/
			gts_isosurface_tetra_bcl(s, g, isofunc, scalars, isoval[0]);
			/* *** ATTENTION ***
     * Isosurface produced is "inside-out", and so we must revert it.
     * This is a bug in GTS.
     */
			gts_surface_foreach_face(s, (GtsFunc)gts_triangle_revert, NULL);
			/* *** ATTENTION *** */
			break;
		default:
			PyErr_SetString(PyExc_ValueError, "unknown method");
			ISO_CLEANUP;
			return NULL;
	}

	ISO_CLEANUP;

	if ((surface = pygts_surface_new(s)) == NULL) {
		gts_object_destroy(GTS_OBJECT(s));
		return NULL;
	}

	return (PyObject*)surface;
}

#endif /* PYGTS_HAS_NUMPY */


static PyMethodDef gts_methods[] = {

	{ "read",
	  (PyCFunction)pygts_read,
	  METH_VARARGS,
	  "Returns the data read from File f as a Surface.\n"
	  "The File data must be in GTS format (e.g., as written using\n"
	  "Surface.write())\n"
	  "\n"
	  "Signature: read(f)\n" },

	{ "sphere",
	  sphere,
	  METH_VARARGS,
	  "Returns a unit sphere generated by recursive subdivision.\n"
	  "First approximation is an isocahedron; each level of refinement\n"
	  "(geodesation_order) increases the number of triangles by a factor\n"
	  "of 4.\n"
	  "\n"
	  "Signature: sphere(geodesation_order)\n" },

#if PYGTS_HAS_NUMPY
	{ "isosurface",
	  (PyCFunction)isosurface,
	  METH_VARARGS | METH_KEYWORDS,
	  "Adds to surface new faces defining the isosurface data[x,y,z] = c\n"
	  "\n"
	  "Signature: isosurface(data, c, ...)\n"
	  "\n"
	  "data is a 3D numpy array.\n"
	  "c    is the isovalue defining the surface\n"
	  "\n"
	  "Keyword arguments:\n"
	  "extents= [xmin, xmax, ymin, ymax, zmin, zmax]\n"
	  "         A numpy array defining the extent of the data cube.\n"
	  "         Default is the cube with corners at (-1,-1,-1) and (1,1,1)\n"
	  "         Data is assumed to be regularly sampled in the cube.\n"
	  "method=  ['cube'|'tetra'|'dual'|'bounded']\n"
	  "         String (only the first character counts) specifying the\n"
	  "         method.\n"
	  "         cube    -- marching cubes (default)\n"
	  "         tetra   -- marching tetrahedra\n"
	  "         dual    -- maching tetrahedra producing dual 'body-centred'\n"
	  "                    faces relative to 'tetra'\n"
	  "         bounded -- marching tetrahedra ensuring the surface is\n"
	  "                    bounded by adding a border of large negative\n"
	  "                    values around the domain.\n"
	  "\n"
	  "By convention, the normals to the surface are pointing towards\n"
	  "positive values of data[x,y,z] - c.\n" },
#endif /* PYGTS_HAS_NUMPY */

	{ "merge",
	  merge,
	  METH_VARARGS,
	  "Merges list of Vertices that are within a box of side-length\n"
	  "epsilon of each other.\n"
	  "\n"
	  "Signature: merge(list,epsilon)\n" },

	{ "vertices",
	  vertices,
	  METH_VARARGS,
	  "Returns tuple of Vertices from a list or tuple of Segments.\n"
	  "\n"
	  "Signature: vertices(list)\n" },

	{ "segments",
	  segments,
	  METH_VARARGS,
	  "Returns tuple of Segments from a list or tuple of Vertices.\n"
	  "\n"
	  "Signature: segments(list)\n" },

	{ "triangles",
	  triangles,
	  METH_VARARGS,
	  "Returns tuple of Triangles from a list or tuple of Edges.\n"
	  "\n"
	  "Signature: triangles(list)\n" },

	{ "triangle_enclosing",
	  triangle_enclosing,
	  METH_VARARGS,
	  "Returns a Triangle that encloses the plane projection of a list\n"
	  "or tuple of Points.  The Triangle is equilateral and encloses a\n"
	  "rectangle defined by the maximum and minimum x and y coordinates\n"
	  "of the points.\n"
	  "\n"
	  "Signature: triangles(list)\n" },


	{ NULL } /* Sentinel */
};

#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif

// Python 3 compatibility macros
// taken from http://python3porting.com/cextensions.html
#if PY_MAJOR_VERSION >= 3
#define MOD_ERROR_VAL NULL
#define MOD_SUCCESS_VAL(val) val
#define MOD_DEF(ob, name, doc, methods)                                                                                                                        \
	static struct PyModuleDef moduledef = {                                                                                                                \
		PyModuleDef_HEAD_INIT, name, doc, -1, methods,                                                                                                 \
	};                                                                                                                                                     \
	ob = PyModule_Create(&moduledef);
#else
#define MOD_ERROR_VAL
#define MOD_SUCCESS_VAL(val)
#define MOD_INIT(name)
#define MOD_DEF(ob, name, doc, methods) ob = Py_InitModule3(name, methods, doc);
#endif

#if PY_MAJOR_VERSION >= 3
extern "C" __attribute__((visibility("default"))) PyObject* PyInit__gts()
#else
/* this is needed when compiling with distutils? */
PyMODINIT_FUNC __attribute__((visibility("default"))) init_gts(void)
#endif
{
	PyObject* m;

	/* Allocate the object table */
	if ((obj_table = g_hash_table_new(NULL, NULL)) == NULL) return MOD_ERROR_VAL;

	/* Set class base types and make ready (i.e., inherit methods) */
	if (PyType_Ready(&PygtsObjectType) < 0) return MOD_ERROR_VAL;

	PygtsPointType.tp_base = &PygtsObjectType;
	if (PyType_Ready(&PygtsPointType) < 0) return MOD_ERROR_VAL;

	PygtsVertexType.tp_base = &PygtsPointType;
	if (PyType_Ready(&PygtsVertexType) < 0) return MOD_ERROR_VAL;

	PygtsSegmentType.tp_base = &PygtsObjectType;
	if (PyType_Ready(&PygtsSegmentType) < 0) return MOD_ERROR_VAL;

	PygtsEdgeType.tp_base = &PygtsSegmentType;
	if (PyType_Ready(&PygtsEdgeType) < 0) return MOD_ERROR_VAL;

	PygtsTriangleType.tp_base = &PygtsObjectType;
	if (PyType_Ready(&PygtsTriangleType) < 0) return MOD_ERROR_VAL;

	PygtsFaceType.tp_base = &PygtsTriangleType;
	if (PyType_Ready(&PygtsFaceType) < 0) return MOD_ERROR_VAL;

	PygtsSurfaceType.tp_base = &PygtsObjectType;
	if (PyType_Ready(&PygtsSurfaceType) < 0) return MOD_ERROR_VAL;


	/* Initialize the module */
	MOD_DEF(m, "_gts", "Gnu Triangulated Surface Library", gts_methods);
	if (m == NULL) return MOD_ERROR_VAL;

#if PYGTS_HAS_NUMPY
	/* Make sure Surface.iso can work with numpy arrays */
	import_array()
#endif

	        /* Add new types to python */
	        Py_INCREF(&PygtsObjectType);
	PyModule_AddObject(m, "Object", (PyObject*)&PygtsObjectType);

	Py_INCREF(&PygtsPointType);
	PyModule_AddObject(m, "Point", (PyObject*)&PygtsPointType);

	Py_INCREF(&PygtsVertexType);
	PyModule_AddObject(m, "Vertex", (PyObject*)&PygtsVertexType);

	Py_INCREF(&PygtsSegmentType);
	PyModule_AddObject(m, "Segment", (PyObject*)&PygtsSegmentType);

	Py_INCREF(&PygtsEdgeType);
	PyModule_AddObject(m, "Edge", (PyObject*)&PygtsEdgeType);

	Py_INCREF(&PygtsTriangleType);
	PyModule_AddObject(m, "Triangle", (PyObject*)&PygtsTriangleType);

	Py_INCREF(&PygtsFaceType);
	PyModule_AddObject(m, "Face", (PyObject*)&PygtsFaceType);

	Py_INCREF(&PygtsSurfaceType);
	PyModule_AddObject(m, "Surface", (PyObject*)&PygtsSurfaceType);

	return MOD_SUCCESS_VAL(m);
}

#pragma GCC diagnostic pop
