/* pygts - python point 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_DEBUG
#define SELF_CHECK                                                                                                                                             \
	if (!pygts_point_check((PyObject*)self)) {                                                                                                             \
		PyErr_SetString(PyExc_RuntimeError, "problem with self object (internal error)");                                                              \
		return NULL;                                                                                                                                   \
	}
#else
#define SELF_CHECK
#endif

// https://codeyarns.com/2014/03/11/how-to-selectively-ignore-a-gcc-warning/
// https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Pragmas.html
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wwrite-strings"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#pragma GCC diagnostic ignored "-Wcast-function-type"

/*-------------------------------------------------------------------------*/
/* Methods exported to python */

static PyObject* is_ok(PygtsPoint* self, PyObject* args)
{
	if (pygts_point_is_ok(self)) {
		Py_INCREF(Py_True);
		return Py_True;
	} else {
		Py_INCREF(Py_False);
		return Py_False;
	}
}


static PyObject* set(PygtsPoint* self, PyObject* args)
{
	gdouble x = 0, y = 0, z = 0;

	SELF_CHECK

	/* Parse the args */
	if (!PyArg_ParseTuple(args, "|ddd", &x, &y, &z)) { return NULL; }

	gts_point_set(PYGTS_POINT_AS_GTS_POINT(self), x, y, z);

	Py_INCREF(Py_None);
	return Py_None;
}


static PyObject* coords(PygtsPoint* self, PyObject* args)
{
	SELF_CHECK

	return Py_BuildValue("ddd", PYGTS_POINT_AS_GTS_POINT(self)->x, PYGTS_POINT_AS_GTS_POINT(self)->y, PYGTS_POINT_AS_GTS_POINT(self)->z);
}


static PyObject* is_in_rectangle(PygtsPoint* self, PyObject* args)
{
	PyObject *  o1_, *o2_;
	PygtsPoint *p1, *p2;
	gboolean    flag = FALSE;
	gdouble     x, y;

	SELF_CHECK

	/* Parse the args */
	if (!PyArg_ParseTuple(args, "OO", &o1_, &o2_)) { return NULL; }

	/* Convert to PygtsObjects */
	if (!(pygts_point_check(o1_) && pygts_point_check(o2_))) {
		PyErr_SetString(PyExc_TypeError, "expected two Points");
		return NULL;
	}

	p1 = PYGTS_POINT(o1_);
	p2 = PYGTS_POINT(o2_);

	/* Test if point *may* be on rectangle perimeter */
	x = PYGTS_POINT_AS_GTS_POINT(self)->x;
	y = PYGTS_POINT_AS_GTS_POINT(self)->y;
	if (PYGTS_POINT_AS_GTS_POINT(p1)->x == x || PYGTS_POINT_AS_GTS_POINT(p1)->y == y || PYGTS_POINT_AS_GTS_POINT(p2)->x == x
	    || PYGTS_POINT_AS_GTS_POINT(p2)->y == y) {
		flag = TRUE;
	}

	if (gts_point_is_in_rectangle(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_POINT_AS_GTS_POINT(p1), PYGTS_POINT_AS_GTS_POINT(p2))) {
		if (flag) {
			return Py_BuildValue("i", 0);
		} else {
			return Py_BuildValue("i", 1);
		}
	} else {
		if (flag && gts_point_is_in_rectangle(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_POINT_AS_GTS_POINT(p2), PYGTS_POINT_AS_GTS_POINT(p1))) {
			return Py_BuildValue("i", 0);
		} else {
			return Py_BuildValue("i", -1);
		}
	}
}


static PyObject* distance(PygtsPoint* self, PyObject* args)
{
	PyObject*      o_;
	PygtsPoint*    p = NULL;
	PygtsSegment*  s = NULL;
	PygtsTriangle* t = NULL;

	SELF_CHECK

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

	/* Convert to PygtsObjects */
	if (pygts_point_check(o_)) {
		p = PYGTS_POINT(o_);
	} else {
		if (pygts_segment_check(o_)) {
			s = PYGTS_SEGMENT(o_);
		} else {
			if (pygts_triangle_check(o_)) {
				t = PYGTS_TRIANGLE(o_);
			} else {
				PyErr_SetString(PyExc_TypeError, "expected a Point, Segment or Triangle");
				return NULL;
			}
		}
	}

	if (p != NULL) {
		return Py_BuildValue("d", gts_point_distance(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_POINT_AS_GTS_POINT(p)));
	} else {
		if (s != NULL) {
			return Py_BuildValue("d", gts_point_segment_distance(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_SEGMENT_AS_GTS_SEGMENT(s)));
		} else {
			return Py_BuildValue("d", gts_point_triangle_distance(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t)));
		}
	}
}


static PyObject* distance2(PygtsPoint* self, PyObject* args)
{
	PyObject*      o_;
	PygtsPoint*    p = NULL;
	PygtsSegment*  s = NULL;
	PygtsTriangle* t = NULL;

	SELF_CHECK

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

	/* Convert to PygtsObjects */
	if (pygts_point_check(o_)) {
		p = PYGTS_POINT(o_);
	} else {
		if (pygts_segment_check(o_)) {
			s = PYGTS_SEGMENT(o_);
		} else {
			if (pygts_triangle_check(o_)) {
				t = PYGTS_TRIANGLE(o_);
			} else {
				PyErr_SetString(PyExc_TypeError, "expected a Point, Segment or Triangle");
				return NULL;
			}
		}
	}

	if (p != NULL) {
		return Py_BuildValue("d", gts_point_distance2(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_POINT_AS_GTS_POINT(p)));
	} else {
		if (s != NULL) {
			return Py_BuildValue("d", gts_point_segment_distance2(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_SEGMENT_AS_GTS_SEGMENT(s)));
		} else {
			return Py_BuildValue("d", gts_point_triangle_distance2(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t)));
		}
	}
}


static PyObject* orientation_3d(PygtsPoint* self, PyObject* args)
{
	PyObject *  p1_, *p2_, *p3_;
	PygtsPoint *p1, *p2, *p3;

	SELF_CHECK

	/* Parse the args */
	if (!PyArg_ParseTuple(args, "OOO", &p1_, &p2_, &p3_)) { return NULL; }

	/* Convert to PygtsObjects */
	if (!pygts_point_check(p1_)) {
		PyErr_SetString(PyExc_TypeError, "expected three Points");
		return NULL;
	}
	if (!pygts_point_check(p2_)) {
		PyErr_SetString(PyExc_TypeError, "expected three Points");
		return NULL;
	}
	if (!pygts_point_check(p3_)) {
		PyErr_SetString(PyExc_TypeError, "expected three Points");
		return NULL;
	}
	p1 = PYGTS_POINT(p1_);
	p2 = PYGTS_POINT(p2_);
	p3 = PYGTS_POINT(p3_);

	return Py_BuildValue(
	        "d",
	        gts_point_orientation_3d(
	                PYGTS_POINT_AS_GTS_POINT(p1), PYGTS_POINT_AS_GTS_POINT(p2), PYGTS_POINT_AS_GTS_POINT(p3), PYGTS_POINT_AS_GTS_POINT(self)));
}


static PyObject* orientation_3d_sos(PygtsPoint* self, PyObject* args)
{
	PyObject *  p1_, *p2_, *p3_;
	PygtsPoint *p1, *p2, *p3;

	SELF_CHECK

	/* Parse the args */
	if (!PyArg_ParseTuple(args, "OOO", &p1_, &p2_, &p3_)) { return NULL; }

	/* Convert to PygtsObjects */
	if (!pygts_point_check(p1_)) {
		PyErr_SetString(PyExc_TypeError, "expected three Points");
		return NULL;
	}
	if (!pygts_point_check(p2_)) {
		PyErr_SetString(PyExc_TypeError, "expected three Points");
		return NULL;
	}
	if (!pygts_point_check(p3_)) {
		PyErr_SetString(PyExc_TypeError, "expected three Points");
		return NULL;
	}
	p1 = PYGTS_POINT(p1_);
	p2 = PYGTS_POINT(p2_);
	p3 = PYGTS_POINT(p3_);

	return Py_BuildValue(
	        "i",
	        gts_point_orientation_3d_sos(
	                PYGTS_POINT_AS_GTS_POINT(p1), PYGTS_POINT_AS_GTS_POINT(p2), PYGTS_POINT_AS_GTS_POINT(p3), PYGTS_POINT_AS_GTS_POINT(self)));
}


static PyObject* is_in_circle(PygtsPoint* self, PyObject* args)
{
	PyObject *     o1_ = NULL, *o2_ = NULL, *o3_ = NULL;
	PygtsPoint *   p1 = NULL, *p2 = NULL, *p3 = NULL;
	PygtsTriangle* t = NULL;
	gdouble        result;

	SELF_CHECK

	/* Parse the args */
	if (!PyArg_ParseTuple(args, "O|OO", &o1_, &o2_, &o3_)) { return NULL; }
	if ((o2_ == NULL && o3_ != NULL) || (o2_ != NULL && o3_ == NULL)) {
		PyErr_SetString(PyExc_TypeError, "expected three Points or one Triangle");
		return NULL;
	}

	/* Convert to PygtsObjects */
	if (o2_ == NULL && o3_ == NULL) {
		if (!pygts_triangle_check(o1_)) {
			PyErr_SetString(PyExc_TypeError, "expected three Points or one Triangle");
			return NULL;
		}
		t = PYGTS_TRIANGLE(o1_);
	} else {
		if (!pygts_point_check(o1_)) {
			PyErr_SetString(PyExc_TypeError, "expected three Points or one Triangle");
			return NULL;
		}
		if (!pygts_point_check(o2_)) {
			PyErr_SetString(PyExc_TypeError, "expected three Points or one Triangle");
			return NULL;
		}
		if (!pygts_point_check(o3_)) {
			PyErr_SetString(PyExc_TypeError, "expected three Points or one Triangle");
			return NULL;
		}
		p1 = PYGTS_POINT(o1_);
		p2 = PYGTS_POINT(o2_);
		p3 = PYGTS_POINT(o3_);
	}


	if (t != NULL) {
		result = gts_point_in_triangle_circle(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t));
	} else {
		result = gts_point_in_circle(
		        PYGTS_POINT_AS_GTS_POINT(self), PYGTS_POINT_AS_GTS_POINT(p1), PYGTS_POINT_AS_GTS_POINT(p2), PYGTS_POINT_AS_GTS_POINT(p3));
	}
	if (result > 0) return Py_BuildValue("i", 1);
	if (result == 0) return Py_BuildValue("i", 0);
	return Py_BuildValue("i", -1);
}


static PyObject* is_in(PygtsPoint* self, PyObject* args)
{
	PyObject*      t_;
	PygtsTriangle* t;

	SELF_CHECK

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

	/* Convert to PygtsObjects */
	if (!pygts_triangle_check(t_)) {
		PyErr_SetString(PyExc_TypeError, "expected a Triangle");
		return NULL;
	}
	t = PYGTS_TRIANGLE(t_);

	return Py_BuildValue("i", gts_point_is_in_triangle(PYGTS_POINT_AS_GTS_POINT(self), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t)));
}


static PyObject* is_inside(PygtsPoint* self, PyObject* args)
{
	PyObject*     s_;
	PygtsSurface* s;
	GNode*        tree;
	gboolean      is_open = FALSE, ret;

	SELF_CHECK

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

	/* Convert to PygtsObjects */
	if (!pygts_surface_check(s_)) {
		PyErr_SetString(PyExc_TypeError, "expected a Surface");
		return NULL;
	}
	s = PYGTS_SURFACE(s_);

	/* Error check */
	if (!gts_surface_is_closed(PYGTS_SURFACE_AS_GTS_SURFACE(s))) {
		PyErr_SetString(PyExc_RuntimeError, "Surface is not closed");
		return NULL;
	}

	/* Determing is_open parameter; note the meaning is different from the 
   * error check above.
   */
	if (gts_surface_volume(PYGTS_SURFACE_AS_GTS_SURFACE(s)) < 0.) { is_open = TRUE; }

	/* Construct the tree */
	if ((tree = gts_bb_tree_surface(PYGTS_SURFACE_AS_GTS_SURFACE(s))) == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create GTree");
		return NULL;
	}

	/* Make the call */
	ret = gts_point_is_inside_surface(PYGTS_POINT_AS_GTS_POINT(self), tree, is_open);

	g_node_destroy(tree);

	if (ret) {
		Py_INCREF(Py_True);
		return Py_True;
	} else {
		Py_INCREF(Py_False);
		return Py_False;
	}
}


static PyObject* closest(PygtsPoint* self, PyObject* args)
{
	PyObject *     o1_, *o2_;
	PygtsPoint*    p = NULL;
	PygtsSegment*  s = NULL;
	PygtsTriangle* t = NULL;

	SELF_CHECK

	/* Parse the args */
	if (!PyArg_ParseTuple(args, "OO", &o1_, &o2_)) { return NULL; }

	/* Convert to PygtsObjects */
	if (pygts_segment_check(o1_)) {
		s = PYGTS_SEGMENT(o1_);
	} else {
		if (pygts_triangle_check(o1_)) {
			t = PYGTS_TRIANGLE(o1_);
		} else {
			PyErr_SetString(PyExc_TypeError, "expected a Segment or Triangle, and a Point");
			return NULL;
		}
	}
	if (pygts_point_check(o2_)) {
		p = PYGTS_POINT(o2_);
	} else {
		PyErr_SetString(PyExc_TypeError, "expected a Segment or Triangle, and a Point");
		return NULL;
	}

	if (s != NULL) {
		gts_point_segment_closest(PYGTS_POINT_AS_GTS_POINT(p), PYGTS_SEGMENT_AS_GTS_SEGMENT(s), PYGTS_POINT_AS_GTS_POINT(self));
	} else {
		gts_point_triangle_closest(PYGTS_POINT_AS_GTS_POINT(p), PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t), PYGTS_POINT_AS_GTS_POINT(self));
	}

	Py_INCREF(self);
	return (PyObject*)self;
}


/* Helper for rotate() */
gint pygts_point_rotate(GtsPoint* p, gdouble dx, gdouble dy, gdouble dz, gdouble a)
{
	GtsMatrix* m;
	GtsVector  v;

	v[0] = dx;
	v[1] = dy;
	v[2] = dz;
	if ((m = gts_matrix_rotate(NULL, v, a)) == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create matrix");
		return -1;
	}
	gts_point_transform(p, m);
	gts_matrix_destroy(m);

	return 0;
}


static PyObject* rotate(PygtsPoint* self, PyObject* args, PyObject* keywds)
{
	static char* kwlist[] = { "dx", "dy", "dz", "a", NULL };
	gdouble      dx = 0, dy = 0, dz = 0, a = 0;

	SELF_CHECK

	/* Parse the args */
	if (!PyArg_ParseTupleAndKeywords(args, keywds, "|dddd", kwlist, &dx, &dy, &dz, &a)) { return NULL; }

	if (pygts_point_rotate(PYGTS_POINT_AS_GTS_POINT(self), dx, dy, dz, a) == -1) return NULL;

	Py_INCREF(Py_None);
	return Py_None;
}


/* Helper for scale() */
gint pygts_point_scale(GtsPoint* p, gdouble dx, gdouble dy, gdouble dz)
{
	GtsMatrix* m;
	GtsVector  v;

	v[0] = dx;
	v[1] = dy;
	v[2] = dz;
	if ((m = gts_matrix_scale(NULL, v)) == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create matrix");
		return -1;
	}
	gts_point_transform(p, m);
	gts_matrix_destroy(m);

	return 0;
}


static PyObject* scale(PygtsPoint* self, PyObject* args, PyObject* keywds)
{
	static char* kwlist[] = { "dx", "dy", "dz", NULL };
	gdouble      dx = 1, dy = 1, dz = 1;

	SELF_CHECK

	/* Parse the args */
	if (!PyArg_ParseTupleAndKeywords(args, keywds, "|ddd", kwlist, &dx, &dy, &dz)) { return NULL; }

	if (pygts_point_scale(PYGTS_POINT_AS_GTS_POINT(self), dx, dy, dz) == -1) return NULL;

	Py_INCREF(Py_None);
	return Py_None;
}


/* Helper for translate() */
gint pygts_point_translate(GtsPoint* p, gdouble dx, gdouble dy, gdouble dz)
{
	GtsMatrix* m;
	GtsVector  v;

	v[0] = dx;
	v[1] = dy;
	v[2] = dz;
	if ((m = gts_matrix_translate(NULL, v)) == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create matrix");
		return -1;
	}
	gts_point_transform(p, m);
	gts_matrix_destroy(m);

	return 0;
}


static PyObject* translate(PygtsPoint* self, PyObject* args, PyObject* keywds)
{
	static char* kwlist[] = { "dx", "dy", "dz", NULL };
	gdouble      dx = 0, dy = 0, dz = 0;

	SELF_CHECK

	/* Parse the args */
	if (!PyArg_ParseTupleAndKeywords(args, keywds, "|ddd", kwlist, &dx, &dy, &dz)) { return NULL; }

	if (pygts_point_translate(PYGTS_POINT_AS_GTS_POINT(self), dx, dy, dz) == -1) return NULL;

	Py_INCREF(Py_None);
	return Py_None;
}


/* Methods table */
static PyMethodDef methods[] = {

	{ "is_ok",
	  (PyCFunction)is_ok,
	  METH_NOARGS,
	  "True if this Point p is OK.  False otherwise.\n"
	  "This method is useful for unit testing and debugging.\n"
	  "\n"
	  "Signature: p.is_ok().\n" },

	{ "set",
	  (PyCFunction)set,
	  METH_VARARGS,
	  "Sets x, y, and z coordinates of this Point p.\n"
	  "\n"
	  "Signature: p.set(x,y,z)\n" },

	{ "coords",
	  (PyCFunction)coords,
	  METH_VARARGS,
	  "Returns a tuple of the x, y, and z coordinates for this Point p.\n"
	  "\n"
	  "Signature: p.coords(x,y,z)\n" },

	{ "is_in_rectangle",
	  (PyCFunction)is_in_rectangle,
	  METH_VARARGS,
	  "True if this Point p is in box with bottom-left and upper-right\n"
	  "Points p1 and p2.\n"
	  "\n"
	  "Signature: p.is_in_rectange(p1,p2)\n" },

	{ "distance",
	  (PyCFunction)distance,
	  METH_VARARGS,
	  "Returns Euclidean distance between this Point p and other Point p2,\n"
	  "Segment s, or Triangle t."
	  "\n"
	  "Signature: p.distance(p2), p.distance(s) or p.distance(t)\n" },

	{ "distance2",
	  (PyCFunction)distance2,
	  METH_VARARGS,
	  "Returns squared Euclidean distance between Point p and Point p2,\n"
	  "Segment s, or Triangle t.\n"
	  "\n"
	  "Signature: p.distance2(p2), p.distance2(s), or p.distance2(t)\n" },

	{ "orientation_3d",
	  (PyCFunction)orientation_3d,
	  METH_VARARGS,
	  "Determines if this Point p is above, below or on plane of 3 Points\n"
	  "p1, p2 and p3.\n"
	  "\n"
	  "Signature: p.orientation_3d(p1,p2,p3)\n"
	  "\n"
	  "Below is defined so that p1, p2 and p3 appear in counterclockwise\n"
	  "order when viewed from above the plane.\n"
	  "\n"
	  "The return value is positive if p4 lies below the plane, negative\n"
	  "if p4 lies above the plane, and zero if the four points are\n"
	  "coplanar.  The value is an approximation of six times the signed\n"
	  "volume of the tetrahedron defined by the four points.\n" },

	{ "orientation_3d_sos",
	  (PyCFunction)orientation_3d_sos,
	  METH_VARARGS,
	  "Determines if this Point p is above, below or on plane of 3 Points\n"
	  "p1, p2 and p3.\n"
	  "\n"
	  "Signature: p.orientation_3d_sos(p1,p2,p3)\n"
	  "\n"
	  "Below is defined so that p1, p2 and p3 appear in counterclockwise\n"
	  "order when viewed from above the plane.\n"
	  "\n"
	  "The return value is +1 if p4 lies below the plane, and -1 if p4\n"
	  "lies above the plane.  Simulation of Simplicity (SoS) is used to\n"
	  "break ties when the orientation is degenerate (i.e. the point lies\n"
	  "on the plane definedby p1, p2 and p3)." },

	{ "is_in_circle",
	  (PyCFunction)is_in_circle,
	  METH_VARARGS,
	  "Tests if this Point p is inside or outside circumcircle.\n"
	  "The planar projection (x,y) of Point p is tested against the\n"
	  "circumcircle defined by the planar projection of p1, p2 and p3,\n"
	  "or alternatively the Triangle t\n"
	  "\n"
	  "Signature: p.in_circle(p1,p2,p3) or p.in_circle(t) \n"
	  "\n"
	  "Returns +1 if p lies inside, -1 if p lies outside, and 0 if p lies\n"
	  "on the circle.  The Points p1, p2, and p3 must be in\n"
	  "counterclockwise order, or the sign of the result will be reversed.\n" },

	{ "is_in",
	  (PyCFunction)is_in,
	  METH_VARARGS,
	  "Tests if this Point p is inside or outside Triangle t.\n"
	  "The planar projection (x,y) of Point p is tested against the\n"
	  "planar projection of Triangle t.\n"
	  "\n"
	  "Signature: p.in_circle(p1,p2,p3) or p.in_circle(t) \n"
	  "\n"
	  "Returns a +1 if p lies inside, -1 if p lies outside, and 0\n"
	  "if p lies on the triangle.\n" },

	{ "is_inside",
	  (PyCFunction)is_inside,
	  METH_VARARGS,
	  "True if this Point p is inside or outside Surface s.\n"
	  "False otherwise.\n"
	  "\n"
	  "Signature: p.in_inside(s)\n" },

	{ "closest",
	  (PyCFunction)closest,
	  METH_VARARGS,
	  "Set the coordinates of Point p to the Point on Segment s\n"
	  "or Triangle t closest to the Point p2\n"
	  "\n"
	  "Signature: p.closest(s,p2) or p.closest(t,p2)\n"
	  "\n"
	  "Returns the (modified) Point p.\n" },

	{ "rotate",
	  (PyCFunction)rotate,
	  METH_VARARGS | METH_KEYWORDS,
	  "Rotates Point p around vector dx,dy,dz by angle a.\n"
	  "The sense of the rotation is given by the right-hand-rule.\n"
	  "\n"
	  "Signature: p.rotate(dx=0,dy=0,dz=0,a=0)\n" },

	{ "scale",
	  (PyCFunction)scale,
	  METH_VARARGS | METH_KEYWORDS,
	  "Scales Point p by vector dx,dy,dz.\n"
	  "\n"
	  "Signature: p.scale(dx=1,dy=1,dz=1)\n" },

	{ "translate",
	  (PyCFunction)translate,
	  METH_VARARGS | METH_KEYWORDS,
	  "Translates Point p by vector dx,dy,dz.\n"
	  "\n"
	  "Signature: p.translate(dx=0,dy=0,dz=0)\n" },

	{ NULL } /* Sentinel */
};


/*-------------------------------------------------------------------------*/
/* Attributes exported to python */

static PyObject* getx(PygtsPoint* self, void* closure)
{
	SELF_CHECK

	return Py_BuildValue("d", PYGTS_POINT_AS_GTS_POINT(self)->x);
}


static int setx(PygtsPoint* self, PyObject* value, void* closure)
{
	if (PyFloat_Check(value)) {
		PYGTS_POINT_AS_GTS_POINT(self)->x = PyFloat_AsDouble(value);
	} else if (PyLong_Check(value)) {
		PYGTS_POINT_AS_GTS_POINT(self)->x = (gdouble)PyLong_AsLong(value);
	} else {
		PyErr_SetString(PyExc_TypeError, "expected a float");
		return -1;
	}
	return 0;
}


static PyObject* gety(PygtsPoint* self, void* closure)
{
	SELF_CHECK

	return Py_BuildValue("d", PYGTS_POINT_AS_GTS_POINT(self)->y);
}


static int sety(PygtsPoint* self, PyObject* value, void* closure)
{
	if (PyFloat_Check(value)) {
		PYGTS_POINT_AS_GTS_POINT(self)->y = PyFloat_AsDouble(value);
	} else if (PyLong_Check(value)) {
		PYGTS_POINT_AS_GTS_POINT(self)->y = (gdouble)PyLong_AsLong(value);
	} else {
		PyErr_SetString(PyExc_TypeError, "expected a float");
		return -1;
	}
	return 0;
}


static PyObject* getz(PygtsPoint* self, void* closure)
{
	SELF_CHECK

	return Py_BuildValue("d", PYGTS_POINT_AS_GTS_POINT(self)->z);
}


static int setz(PygtsPoint* self, PyObject* value, void* closure)
{
	if (PyFloat_Check(value)) {
		PYGTS_POINT_AS_GTS_POINT(self)->z = PyFloat_AsDouble(value);
	} else if (PyLong_Check(value)) {
		PYGTS_POINT_AS_GTS_POINT(self)->z = (gdouble)PyLong_AsLong(value);
	} else {
		PyErr_SetString(PyExc_TypeError, "expected a float");
		return -1;
	}
	return 0;
}


/* Methods table */
static PyGetSetDef getset[] = {
	{ "x", (getter)getx, (setter)setx, "x value", NULL },
	{ "y", (getter)gety, (setter)sety, "y value", NULL },
	{ "z", (getter)getz, (setter)setz, "z value", NULL },
	{ NULL } /* Sentinel */
};


/*-------------------------------------------------------------------------*/
/* Python type methods */

static PyObject* new_(PyTypeObject* type, PyObject* args, PyObject* kwds)
{
	PyObject*    o;
	PygtsObject* obj;
	guint        alloc_gtsobj = TRUE;

	/* Parse the args */
	if (kwds) {
		o = PyDict_GetItemString(kwds, "alloc_gtsobj");
		if (o == Py_False) { alloc_gtsobj = FALSE; }
		if (o != NULL) { PyDict_DelItemString(kwds, "alloc_gtsobj"); }
	}
	if (kwds) {
		Py_INCREF(Py_False);
		PyDict_SetItemString(kwds, "alloc_gtsobj", Py_False);
	}

	/* Chain up */
	obj = PYGTS_OBJECT(PygtsObjectType.tp_new(type, args, kwds));

	/* Allocate the gtsobj (if needed) */
	if (alloc_gtsobj) {
		obj->gtsobj = GTS_OBJECT(gts_point_new(gts_point_class(), 0, 0, 0));
		if (obj->gtsobj == NULL) {
			PyErr_SetString(PyExc_MemoryError, "could not create Point");
			return NULL;
		}

		pygts_object_register(obj);
	}

	return (PyObject*)obj;
}


static int init(PygtsPoint* self, PyObject* args, PyObject* kwds)
{
	PygtsObject* obj;
	gdouble      x = 0, y = 0, z = 0;
	guint        a;
	gint         ret;
	static char* kwlist[] = { "x", "y", "z", "alloc_gtsobj", NULL };

	obj = PYGTS_OBJECT(self);

	/* Parse the args */
	if (!PyArg_ParseTupleAndKeywords(args, kwds, "|dddi", kwlist, &x, &y, &z, &a)) { return -1; }

	/* Initialize */
	gts_point_set(GTS_POINT(obj->gtsobj), x, y, z);

	/* Chain up */
	if ((ret = PygtsObjectType.tp_init((PyObject*)self, args, kwds)) != 0) { return ret; }

	return 0;
}

static int compare(PyObject* p1_, PyObject* p2_)
{
	GtsPoint *p1, *p2;

#if PYGTS_DEBUG
	pygts_point_check(p1_);
	pygts_point_check(p2_);
#endif

	p1 = PYGTS_POINT_AS_GTS_POINT(p1_);
	p2 = PYGTS_POINT_AS_GTS_POINT(p2_);

	return pygts_point_compare(p1, p2);
}

#if PY_MAJOR_VERSION >= 3
static PyObject* rich_compare(PyObject* o1, PyObject* o2, int op)
{
	if (o2 == Py_None) Py_RETURN_FALSE; // comparing with nothing
	switch (op) {
		case Py_EQ: {
			if (compare(o1, o2)) Py_RETURN_TRUE;
			Py_RETURN_FALSE;
		}
		default: Py_RETURN_NOTIMPLEMENTED;
	}
}
#endif


/* Methods table */
PyTypeObject PygtsPointType = {
	PyVarObject_HEAD_INIT(NULL, 0) "gts.Point", /* tp_name */
	sizeof(PygtsPoint),                         /* tp_basicsize */
	0,                                          /* tp_itemsize */
	0,                                          /* tp_dealloc */
	0,                                          /* tp_print */
	0,                                          /* tp_getattr */
	0,                                          /* tp_setattr */
#if PY_MAJOR_VERSION >= 3
	0,
#else
	(cmpfunc)compare, /* tp_compare */
#endif
	0,                                        /* tp_repr */
	0,                                        /* tp_as_number */
	0,                                        /* tp_as_sequence */
	0,                                        /* tp_as_mapping */
	0,                                        /* tp_hash */
	0,                                        /* tp_call */
	0,                                        /* tp_str */
	0,                                        /* tp_getattro */
	0,                                        /* tp_setattro */
	0,                                        /* tp_as_buffer */
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
	"Point object",                           /* tp_doc */
	0,                                        /* tp_traverse */
	0,                                        /* tp_clear */
#if PY_MAJOR_VERSION >= 3
	(richcmpfunc)rich_compare, /* tp_richcompare */
#else
	0,                /* tp_richcompare */
#endif
	0,              /* tp_weaklistoffset */
	0,              /* tp_iter */
	0,              /* tp_iternext */
	methods,        /* tp_methods */
	0,              /* tp_members */
	getset,         /* tp_getset */
	0,              /* tp_base: attached in pygts.c */
	0,              /* tp_dict */
	0,              /* tp_descr_get */
	0,              /* tp_descr_set */
	0,              /* tp_dictoffset */
	(initproc)init, /* tp_init */
	0,              /* tp_alloc */
	(newfunc)new_   /* tp_new */
};


/*-------------------------------------------------------------------------*/
/* Pygts functions */

gboolean pygts_point_check(PyObject* o)
{
	gboolean  check = FALSE;
	guint     i, N;
	PyObject* obj;

	/* Check for a Point */
	if (PyObject_TypeCheck(o, &PygtsPointType)) { check = TRUE; }

	/* Convert list into tuple */
	if (PyList_Check(o)) {
		o = PyList_AsTuple(o);
	} else {
		Py_INCREF(o);
	}

	/* Check for a tuple of floats */
	if (PyTuple_Check(o)) {
		if ((N = PyTuple_Size(o)) <= 3) {
			check = TRUE;
			for (i = 0; i < N; i++) {
				obj = PyTuple_GET_ITEM(o, i);
				if (!PyFloat_Check(obj) && !PyLong_Check(obj)) { check = FALSE; }
			}
		}
	}
	Py_DECREF(o);

	if (!check) {
		return FALSE;
	} else {
#if PYGTS_DEBUG
		if (PyObject_TypeCheck(o, &PygtsPointType)) { return pygts_point_is_ok(PYGTS_POINT(o)); }
#endif
		return TRUE;
	}
}


gboolean pygts_point_is_ok(PygtsPoint* p) { return pygts_object_is_ok(PYGTS_OBJECT(p)); }


PygtsPoint* pygts_point_new(GtsPoint* p)
{
	PyObject *   args, *kwds;
	PygtsObject* point;

	/* Check for Point in the object table */
	if ((point = PYGTS_OBJECT(g_hash_table_lookup(obj_table, GTS_OBJECT(p)))) != NULL) {
		Py_INCREF(point);
		return PYGTS_POINT(point);
	}

	/* Build a new Point */
	args  = Py_BuildValue("ddd", 0, 0, 0);
	kwds  = Py_BuildValue("{s:O}", "alloc_gtsobj", Py_False);
	point = PYGTS_POINT(PygtsPointType.tp_new(&PygtsPointType, args, kwds));
	Py_DECREF(args);
	Py_DECREF(kwds);
	if (point == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create Point");
		return NULL;
	}
	point->gtsobj = GTS_OBJECT(p);

	/* Register and return */
	pygts_object_register(point);
	return PYGTS_POINT(point);
}


PygtsPoint* pygts_point_from_sequence(PyObject* tuple)
{
	guint       i, N;
	gdouble     x = 0, y = 0, z = 0;
	PyObject*   obj;
	GtsPoint*   p;
	PygtsPoint* point;

	/* Convert list into tuple */
	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;
	}

	/* Get the tuple size */
	if ((N = PyTuple_Size(tuple)) > 3) {
		PyErr_SetString(PyExc_RuntimeError, "expected a list or tuple of up to three floats");
		Py_DECREF(tuple);
		return NULL;
	}

	/* Get the coordinates */
	for (i = 0; i < N; i++) {
		obj = PyTuple_GET_ITEM(tuple, i);

		if (!PyFloat_Check(obj) && !PyLong_Check(obj)) {
			PyErr_SetString(PyExc_TypeError, "expected a list or tuple of floats");
			Py_DECREF(tuple);
			return NULL;
		}
		if (i == 0) {
			if (PyFloat_Check(obj)) x = PyFloat_AsDouble(obj);
			else
				x = (double)PyLong_AsLong(obj);
		}
		if (i == 1) {
			if (PyFloat_Check(obj)) y = PyFloat_AsDouble(obj);
			else
				y = (double)PyLong_AsLong(obj);
		}
		if (i == 2) {
			if (PyFloat_Check(obj)) z = PyFloat_AsDouble(obj);
			else
				z = (double)PyLong_AsLong(obj);
		}
	}
	Py_DECREF(tuple);

	/* Create the vertex */
	if ((p = gts_point_new(gts_point_class(), x, y, z)) == NULL) { PyErr_SetString(PyExc_MemoryError, "could not create Point"); }
	if ((point = pygts_point_new(p)) == NULL) {
		gts_object_destroy(GTS_OBJECT(p));
		return NULL;
	}

	return point;
}


int pygts_point_compare(GtsPoint* p1, GtsPoint* p2)
{
	double r1, r2;

	if ((p1->x == p2->x) && (p1->y == p2->y) && (p1->z == p2->z)) { return 0; }

	/* Compare distances from origin */
	r1 = sqrt(pow(p1->x, 2) + pow(p1->y, 2) + pow(p1->z, 2));
	r2 = sqrt(pow(p2->x, 2) + pow(p2->y, 2) + pow(p2->z, 2));
	if (r1 < r2) return -1;
	if (r1 > r2) return 1;

	/* Compare horizontal distances from origin */
	r1 = sqrt(pow(p1->x, 2) + pow(p1->y, 2));
	r2 = sqrt(pow(p2->x, 2) + pow(p2->y, 2));
	if (r1 < r2) return -1;
	if (r1 > r2) return 1;

	/* Compare x */
	r1 = p1->x;
	r2 = p2->x;
	if (r1 < r2) return -1;
	if (r1 > r2) return 1;

	/* Compare y */
	r1 = p1->y;
	r2 = p2->y;
	if (r1 < r2) return -1;
	if (r1 > r2) return 1;

	/* Compare z */
	r1 = p1->z;
	r2 = p2->z;
	if (r1 < r2) return -1;
	return 1;
}

#pragma GCC diagnostic pop
