/* 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"

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


#if PYGTS_DEBUG
#define SELF_CHECK                                                                                                                                             \
	if (!pygts_edge_check((PyObject*)self)) {                                                                                                              \
		PyErr_SetString(PyExc_RuntimeError, "problem with self object (internal error)");                                                              \
		return NULL;                                                                                                                                   \
	}
#else
#define SELF_CHECK
#endif


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

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


static PyObject* is_unattached(PygtsEdge* self, PyObject* args)
{
	guint n;

	SELF_CHECK

	/* Check for attachments other than to the gtsobj_parent */
	n = g_slist_length(PYGTS_EDGE_AS_GTS_EDGE(self)->triangles);
	if (n > 1) {
		Py_INCREF(Py_False);
		return Py_False;
	} else if (n == 1) {
		Py_INCREF(Py_True);
		return Py_True;
	} else {
		PyErr_SetString(PyExc_RuntimeError, "Edge lost parent (internal error)");
		return NULL;
	}
}


/* replace() works, but can break Triangles and so is disabled */

/* static PyObject* */
/* replace(PygtsEdge *self, PyObject *args) */
/* { */
/*   PyObject *e2_; */
/*   PygtsEdge *e2; */
/*   GSList *parents=NULL, *i, *cur; */

/* #if PYGTS_DEBUG */
/*   if(!pygts_edge_check((PyObject*)self)) { */
/*     PyErr_SetString(PyExc_TypeError, */
/* 		    "problem with self object (internal error)"); */
/*     return NULL; */
/*   } */
/* #endif */

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

/*   /\* Convert to PygtsObjects *\/ */
/*   if(!pygts_edge_check(e2_)) { */
/*     PyErr_SetString(PyExc_TypeError,"expected an Edge"); */
/*     return NULL; */
/*   } */
/*   e2 = PYGTS_EDGE(e2_); */

/*   if(PYGTS_OBJECT(self)->gtsobj!=PYGTS_OBJECT(e2)->gtsobj) { */
/*     /\* (Ignore self-replacement) *\/ */

/*     /\* Detach and save any parent triangles *\/ */
/*     i = GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles; */
/*     while(i!=NULL) { */
/*       cur = i; */
/*       i = i->next; */
/*       if(PYGTS_IS_PARENT_TRIANGLE(cur->data)) { */
/* 	GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles =  */
/* 	  g_slist_remove_link(GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles, */
/* 			      cur); */
/* 	parents = g_slist_prepend(parents,cur->data); */
/* 	g_slist_free_1(cur); */
/*       } */
/*     } */

/*     /\* Perform the replace operation *\/ */
/*     gts_edge_replace(GTS_EDGE(PYGTS_OBJECT(self)->gtsobj), */
/* 		     GTS_EDGE(PYGTS_OBJECT(e2)->gtsobj)); */

/*     /\* Reattach the parent segments *\/ */
/*     i = parents; */
/*     while(i!=NULL) { */
/*       GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles =  */
/* 	g_slist_prepend(GTS_EDGE(PYGTS_OBJECT(self)->gtsobj)->triangles, */
/* 			i->data); */
/*       i = i->next; */
/*     } */
/*     g_slist_free(parents); */
/*   } */

/* #if PYGTS_DEBUG */
/*   if(!pygts_edge_check((PyObject*)self)) { */
/*     PyErr_SetString(PyExc_TypeError, */
/* 		    "problem with self object (internal error)"); */
/*     return NULL; */
/*   } */
/* #endif */

/*   Py_INCREF(Py_None); */
/*   return Py_None; */
/* } */


static PyObject* face_number(PygtsEdge* self, PyObject* args)
{
	PyObject*   s_;
	GtsSurface* s;

	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_AS_GTS_SURFACE(s_);

	return Py_BuildValue("i", gts_edge_face_number(PYGTS_EDGE_AS_GTS_EDGE(self), s));
}


static PyObject* belongs_to_tetrahedron(PygtsEdge* self, PyObject* args)
{
	SELF_CHECK

	if (gts_edge_belongs_to_tetrahedron(PYGTS_EDGE_AS_GTS_EDGE(self))) {
		Py_INCREF(Py_True);
		return Py_True;
	} else {
		Py_INCREF(Py_False);
		return Py_False;
	}
}


static PyObject* is_boundary(PygtsEdge* self, PyObject* args)
{
	PyObject*   s_;
	GtsSurface* s;

	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_AS_GTS_SURFACE(s_);

	/* Make the call and return */
	if (gts_edge_is_boundary(PYGTS_EDGE_AS_GTS_EDGE(self), s) != NULL) {
		Py_INCREF(Py_True);
		return Py_True;
	} else {
		Py_INCREF(Py_False);
		return Py_False;
	}
}


static PyObject* contacts(PygtsEdge* self, PyObject* args)
{
	SELF_CHECK

	return Py_BuildValue("i", gts_edge_is_contact(PYGTS_EDGE_AS_GTS_EDGE(self)));
}


/* Methods table */
static PyMethodDef methods[] = {
	{ "is_ok",
	  (PyCFunction)is_ok,
	  METH_NOARGS,
	  "True if this Edge e is not degenerate or duplicate.\n"
	  "False otherwise.  Degeneracy implies e.v1.id == e.v2.id.\n"
	  "\n"
	  "Signature: e.is_ok()\n" },

	{ "is_unattached",
	  (PyCFunction)is_unattached,
	  METH_NOARGS,
	  "True if this Edge e is not part of any Triangle.\n"
	  "\n"
	  "Signature: e.is_unattached()\n" },

	/* Edge replace() method works but results in Triangles that are "not ok";
 * i.e., they have edges that don't connect.  We don't want that problem
 * and so the method has been disabled.
 */
	/*
  {"replace", (PyCFunction)replace,
   METH_VARARGS,
   "Replaces this Edge e1 with Edge e2 in all Triangles that have e1.\n"
   "Edge e1 itself is left unchanged.\n"
   "\n"
   "Signature: e1.replace(e2).\n"
  },
*/

	{ "face_number",
	  (PyCFunction)face_number,
	  METH_VARARGS,
	  "Returns number of faces using this Edge e on Surface s.\n"
	  "\n"
	  "Signature: e.face_number(s)\n" },

	{ "is_boundary",
	  (PyCFunction)is_boundary,
	  METH_VARARGS,
	  "Returns True if this Edge e is a boundary on Surface s.\n"
	  "Otherwise False.\n"
	  "\n"
	  "Signature: e.is_boundary(s)\n" },

	{ "belongs_to_tetrahedron",
	  (PyCFunction)belongs_to_tetrahedron,
	  METH_NOARGS,
	  "Returns True if this Edge e belongs to a tetrahedron.\n"
	  "Otherwise False.\n"
	  "\n"
	  "Signature: e.belongs_to_tetrahedron()\n" },

	{ "contacts",
	  (PyCFunction)contacts,
	  METH_NOARGS,
	  "Returns number of sets of connected triangles share this Edge e\n"
	  "as a contact Edge.\n"
	  "\n"
	  "Signature: e.contacts()\n" },

	{ NULL } /* Sentinel */
};


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

static GtsObject* parent(GtsEdge* e1);

static PyObject* new_(PyTypeObject* type, PyObject* args, PyObject* kwds)
{
	PyObject*    o;
	PygtsObject* obj;
	GtsEdge*     tmp;
	GtsObject*   edge = NULL;
	PyObject *   v1_, *v2_;
	PygtsVertex *v1, *v2;
	guint        alloc_gtsobj = TRUE;
	guint        N;

	/* 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);
	}

	/* Allocate the gtsobj (if needed) */
	if (alloc_gtsobj) {
		/* Parse the args */
		if ((N = PyTuple_Size(args)) < 2) {
			PyErr_SetString(PyExc_TypeError, "expected two Vertices");
			return NULL;
		}
		v1_ = PyTuple_GET_ITEM(args, 0);
		v2_ = PyTuple_GET_ITEM(args, 1);

		/* Convert to PygtsObjects */
		if (!pygts_vertex_check(v1_)) {
			PyErr_SetString(PyExc_TypeError, "expected two Vertices");
			return NULL;
		}
		if (!pygts_vertex_check(v2_)) {
			PyErr_SetString(PyExc_TypeError, "expected two Vertices");
			return NULL;
		}
		v1 = PYGTS_VERTEX(v1_);
		v2 = PYGTS_VERTEX(v2_);

		/* Error check */
		if (PYGTS_OBJECT(v1)->gtsobj == PYGTS_OBJECT(v2)->gtsobj) {
			PyErr_SetString(PyExc_ValueError, "Vertices given are the same");
			return NULL;
		}

		/* Create the GtsEdge */
		edge = GTS_OBJECT(gts_edge_new(gts_edge_class(), GTS_VERTEX(v1->gtsobj), GTS_VERTEX(v2->gtsobj)));
		if (edge == NULL) {
			PyErr_SetString(PyExc_MemoryError, "could not create Edge");
			return NULL;
		}

		/* Check for duplicate */
		tmp = gts_edge_is_duplicate(GTS_EDGE(edge));
		if (tmp != NULL) {
			gts_object_destroy(edge);
			edge = GTS_OBJECT(tmp);
		}

		/* If corresponding PyObject found in object table, we are done */
		if ((obj = (PygtsObject*)g_hash_table_lookup(obj_table, edge)) != NULL) {
			Py_INCREF(obj);
			return (PyObject*)obj;
		}
	}

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

	if (alloc_gtsobj) {
		obj->gtsobj = edge;

		/* Create a parent GtsTriangle */
		if ((obj->gtsobj_parent = parent(GTS_EDGE(obj->gtsobj))) == NULL) {
			gts_object_destroy(obj->gtsobj);
			obj->gtsobj = NULL;
			return NULL;
		}

		pygts_object_register(PYGTS_OBJECT(obj));
	}

	return (PyObject*)obj;
}


static int init(PygtsEdge* self, PyObject* args, PyObject* kwds)
{
	gint ret;

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

	return 0;
}


/* Methods table */
PyTypeObject PygtsEdgeType = {
	PyVarObject_HEAD_INIT(NULL, 0) "gts.Edge", /* tp_name */
	sizeof(PygtsEdge),                         /* tp_basicsize */
	0,                                         /* tp_itemsize */
	0,                                         /* tp_dealloc */
	0,                                         /* tp_print */
	0,                                         /* tp_getattr */
	0,                                         /* tp_setattr */
	0,                                         /* tp_compare */
	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 */
	"Edge object",                             /* tp_doc */
	0,                                         /* tp_traverse */
	0,                                         /* tp_clear */
	0,                                         /* tp_richcompare */
	0,                                         /* tp_weaklistoffset */
	0,                                         /* tp_iter */
	0,                                         /* tp_iternext */
	methods,                                   /* tp_methods */
	0,                                         /* tp_members */
	0,                                         /* tp_getset */
	0,                                         /* tp_base */
	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_edge_check(PyObject* o)
{
	if (!PyObject_TypeCheck(o, &PygtsEdgeType)) {
		return FALSE;
	} else {
#if PYGTS_DEBUG
		return pygts_edge_is_ok(PYGTS_EDGE(o));
#else
		return TRUE;
#endif
	}
}

gboolean pygts_edge_is_ok(PygtsEdge* e)
{
	GSList*      parent;
	PygtsObject* obj;

	obj = PYGTS_OBJECT(e);

	if (!pygts_segment_is_ok(PYGTS_SEGMENT(e))) return FALSE;

	/* Check for a valid parent */
	g_return_val_if_fail(obj->gtsobj_parent != NULL, FALSE);
	g_return_val_if_fail(PYGTS_IS_PARENT_TRIANGLE(obj->gtsobj_parent), FALSE);
	parent = g_slist_find(GTS_EDGE(obj->gtsobj)->triangles, obj->gtsobj_parent);
	g_return_val_if_fail(parent != NULL, FALSE);

	return TRUE;
}


static GtsObject* parent(GtsEdge* e1)
{
	GtsVertex *  v1, *v2, *v3;
	GtsPoint *   p1, *p2;
	GtsEdge *    e2, *e3;
	GtsTriangle* p;

	/* Create a third vertex for the triangle */
	v1 = GTS_SEGMENT(e1)->v1;
	v2 = GTS_SEGMENT(e1)->v2;
	p1 = GTS_POINT(v1);
	p2 = GTS_POINT(v2);
	v3 = gts_vertex_new(pygts_parent_vertex_class(), p1->x + p2->x, p1->y + p2->y, p1->z + p2->z);
	if (v3 == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create Vertex");
		return NULL;
	}

	/* Create another two edges */
	if ((e2 = gts_edge_new(pygts_parent_edge_class(), v2, v3)) == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create Edge");
		return NULL;
	}
	if ((e3 = gts_edge_new(pygts_parent_edge_class(), v3, v1)) == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create Edge");
		gts_object_destroy(GTS_OBJECT(e2));
		return NULL;
	}

	/* Create and return the parent */
	if ((p = gts_triangle_new(pygts_parent_triangle_class(), e1, e2, e3)) == NULL) {
		gts_object_destroy(GTS_OBJECT(e2));
		gts_object_destroy(GTS_OBJECT(e3));
		PyErr_SetString(PyExc_MemoryError, "could not create Triangle");
		return NULL;
	}

	return GTS_OBJECT(p);
}


PygtsEdge* pygts_edge_new(GtsEdge* e)
{
	PyObject *   args, *kwds;
	PygtsObject* edge;

	/* Check for Edge in the object table */
	if ((edge = PYGTS_OBJECT(g_hash_table_lookup(obj_table, GTS_OBJECT(e)))) != NULL) {
		Py_INCREF(edge);
		return PYGTS_EDGE(edge);
	}

	/* Build a new Edge */
	args = Py_BuildValue("OO", Py_None, Py_None);
	kwds = Py_BuildValue("{s:O}", "alloc_gtsobj", Py_False);
	edge = PYGTS_EDGE(PygtsEdgeType.tp_new(&PygtsEdgeType, args, kwds));
	Py_DECREF(args);
	Py_DECREF(kwds);
	if (edge == NULL) {
		PyErr_SetString(PyExc_MemoryError, "could not create Edge");
		return NULL;
	}
	edge->gtsobj = GTS_OBJECT(e);

	/* Attach the parent */
	if ((edge->gtsobj_parent = parent(e)) == NULL) {
		Py_DECREF(edge);
		return NULL;
	}

	/* Register and return */
	pygts_object_register(edge);
	return PYGTS_EDGE(edge);
}


GtsTriangleClass* pygts_parent_triangle_class(void)
{
	static GtsTriangleClass* klass = NULL;
	GtsObjectClass*          super = NULL;

	if (klass == NULL) {
		super = GTS_OBJECT_CLASS(gts_triangle_class());

		GtsObjectClassInfo pygts_parent_triangle_info = { "PygtsParentTriangle",
			                                          sizeof(PygtsParentTriangle),
			                                          sizeof(GtsTriangleClass),
			                                          (GtsObjectClassInitFunc)(super->info.class_init_func),
			                                          (GtsObjectInitFunc)(super->info.object_init_func),
			                                          (GtsArgSetFunc)NULL,
			                                          (GtsArgGetFunc)NULL };
		klass                                         = (GtsTriangleClass*)gts_object_class_new(gts_object_class(), &pygts_parent_triangle_info);
	}

	return klass;
}


GtsEdgeClass* pygts_parent_edge_class(void)
{
	static GtsEdgeClass* klass = NULL;
	GtsObjectClass*      super = NULL;

	if (klass == NULL) {
		super = GTS_OBJECT_CLASS(pygts_parent_segment_class());

		GtsObjectClassInfo pygts_parent_edge_info = { "PygtsParentEdge",
			                                      sizeof(PygtsParentEdge),
			                                      sizeof(GtsEdgeClass),
			                                      (GtsObjectClassInitFunc)(super->info.class_init_func),
			                                      (GtsObjectInitFunc)(super->info.object_init_func),
			                                      (GtsArgSetFunc)NULL,
			                                      (GtsArgGetFunc)NULL };
		klass                                     = (GtsEdgeClass*)gts_object_class_new(gts_object_class(), &pygts_parent_edge_info);
	}

	return klass;
}

#pragma GCC diagnostic pop
