/* 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_face_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(PygtsFace *self, PyObject *args)
{
  if(pygts_face_is_ok(self)) {
    Py_INCREF(Py_True);
    return Py_True;
  }
  else {
    Py_INCREF(Py_False);
    return Py_False;
  }
}


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

  /* Check for attachments other than to the gtsobj_parent */
  n = g_slist_length(PYGTS_FACE_AS_GTS_FACE(self)->surfaces);
  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, "Face lost parent (internal error)");
    return NULL;
  }
}


static PyObject*
neighbor_number(PygtsFace *self, PyObject *args)
{
  PyObject *s_=NULL;
  PygtsSurface *s=NULL;

  SELF_CHECK

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

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

  return Py_BuildValue("i",
      gts_face_neighbor_number(PYGTS_FACE_AS_GTS_FACE(self),
			       PYGTS_SURFACE_AS_GTS_SURFACE(s)));
}


static PyObject*
neighbors(PygtsFace *self, PyObject *args)
{
  PyObject *s_=NULL;
  PygtsSurface *s=NULL;
  guint i,N;
  PyObject *tuple;
  GSList *faces,*f;
  PygtsFace *face;

  SELF_CHECK

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

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

  N = gts_face_neighbor_number(PYGTS_FACE_AS_GTS_FACE(self),
			       PYGTS_SURFACE_AS_GTS_SURFACE(s));

  if( (tuple=PyTuple_New(N)) == NULL) {
    PyErr_SetString(PyExc_MemoryError, "Could not create tuple");
    return NULL;
  }

  /* Get the neighbors */
  faces = gts_face_neighbors(PYGTS_FACE_AS_GTS_FACE(self),
			     PYGTS_SURFACE_AS_GTS_SURFACE(s));
  f = faces;
			     
  for(i=0;i<N;i++) {
    if( (face = pygts_face_new(GTS_FACE(f->data))) == NULL ) {
      Py_DECREF(tuple);
      return NULL;
    }
    PyTuple_SET_ITEM(tuple, i, (PyObject*)face);
    f = g_slist_next(f);
  }  

  return (PyObject*)tuple;
}


static PyObject*
is_compatible(PygtsFace *self, PyObject *args)
{
  PyObject *o1_=NULL;
  GtsEdge *e=NULL;
  PygtsTriangle *t=NULL;
  PygtsSurface *s=NULL;

  SELF_CHECK

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

  /* Convert to PygtsObjects */
  if( pygts_triangle_check(o1_) ) {
    t = PYGTS_TRIANGLE(o1_);
  }
  else {
    if( pygts_surface_check(o1_) ) {
      s = PYGTS_SURFACE(o1_);
    }
    else {
      PyErr_SetString(PyExc_TypeError, "expected a Triangle or Surface");
      return NULL;
    }
  }

  if(t!=NULL) {
    if( (e = gts_triangles_common_edge(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self),
				       PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t))) 
	== NULL ) {
      PyErr_SetString(PyExc_RuntimeError, "Faces do not share common edge");
      return NULL;
    }
    if(gts_triangles_are_compatible(PYGTS_TRIANGLE_AS_GTS_TRIANGLE(self),
				    PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t),
				    e)==TRUE) {

      Py_INCREF(Py_True);
      return Py_True;
    }
  }
  else {
    if(gts_face_is_compatible(PYGTS_FACE_AS_GTS_FACE(self),
			      PYGTS_SURFACE_AS_GTS_SURFACE(s))==TRUE) {
      Py_INCREF(Py_True);
      return Py_True;
    }
  }
  Py_INCREF(Py_False);
  return Py_False;
}


static PyObject*
is_on(PygtsFace *self, PyObject *args)
{
  PyObject *s_=NULL;
  PygtsSurface *s=NULL;

  SELF_CHECK

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

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

  if( gts_face_has_parent_surface(PYGTS_FACE_AS_GTS_FACE(self),
				  PYGTS_SURFACE_AS_GTS_SURFACE(s)) ) {
    Py_INCREF(Py_True);
    return Py_True;
  }
  else {
    Py_INCREF(Py_False);
    return Py_False;
  }
}


/* Methods table */
static PyMethodDef methods[] = {
  {"is_ok", (PyCFunction)is_ok,
   METH_NOARGS,
   "True if this Face f is non-degenerate and non-duplicate.\n"
   "False otherwise.\n"
   "\n"
   "Signature: f.is_ok()\n"
  },  

  {"is_unattached", (PyCFunction)is_unattached,
   METH_NOARGS,
   "True if this Face f is not part of any Surface.\n"
   "\n"
   "Signature: f.is_unattached().\n"
  },

  {"neighbor_number", (PyCFunction)neighbor_number,
   METH_VARARGS,
   "Returns the number of neighbors of Face f belonging to Surface s.\n"
   "\n"
   "Signature: f.neighbor_number(s).\n"
  },

  {"neighbors", (PyCFunction)neighbors,
   METH_VARARGS,
   "Returns a tuple of neighbors of this Face f belonging to Surface s.\n"
   "\n"
   "Signature: f.neighbors(s).\n"
  },

  {"is_compatible", (PyCFunction)is_compatible,
   METH_VARARGS,
   "True if Face f is compatible with all neighbors in Surface s.\n"
   "False otherwise.\n"
   "\n"
   "Signature: f.is_compatible(s).\n"
  },  

  {"is_on", (PyCFunction)is_on,
   METH_VARARGS,
   "True if this Face f is on Surface s.  False otherwise.\n"
   "\n"
   "Signature: f.is_on(s).\n"
  },  

  {NULL}  /* Sentinel */
};


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


static GtsObject * parent(GtsFace *face);

static PyObject *
new_(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
  PyObject *o;
  PygtsObject *obj;
  guint alloc_gtsobj = TRUE;
  PyObject *o1_,*o2_,*o3_;
  GtsVertex *v1=NULL, *v2=NULL, *v3=NULL;
  GtsEdge *e1=NULL,*e2=NULL,*e3=NULL,*e;
  GtsSegment *s1,*s2,*s3;
  gboolean flag=FALSE;  /* Flag when the args are gts.Point objects */
  GtsFace *f;
  GtsTriangle *t;
  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)) < 3 ) {
      PyErr_SetString(PyExc_TypeError,"expected three Edges or three Vertices");
      return NULL;
    }
    o1_ = PyTuple_GET_ITEM(args,0);
    o2_ = PyTuple_GET_ITEM(args,1);
    o3_ = PyTuple_GET_ITEM(args,2);

    /* Convert to PygtsObjects */
    if( pygts_edge_check(o1_) ) {
      e1 = PYGTS_EDGE_AS_GTS_EDGE(o1_);
    }
    else {
      if( pygts_vertex_check(o1_) ) {
	v1 = PYGTS_VERTEX_AS_GTS_VERTEX(o1_);
	flag = TRUE;
      }
    }

    if( pygts_edge_check(o2_) ) {
      e2 = PYGTS_EDGE_AS_GTS_EDGE(o2_);
    }
    else {
      if( pygts_vertex_check(o2_) ) {
	v2 = PYGTS_VERTEX_AS_GTS_VERTEX(o2_);
	flag = TRUE;
      }
    }

    if( pygts_edge_check(o3_) ) {
      e3 = PYGTS_EDGE_AS_GTS_EDGE(o3_);
    }
    else {
      if(pygts_vertex_check(o3_)) {
	v3 = PYGTS_VERTEX_AS_GTS_VERTEX(o3_);
	flag = TRUE;
      }
    }
    
    /* Check for three edges or three vertices */
    if( !((e1!=NULL && e2!=NULL && e3!=NULL) ||
	  (v1!=NULL && v2!=NULL && v3!=NULL)) ) {
      PyErr_SetString(PyExc_TypeError,
		      "three Edge or three Vertex objects expected");
      return NULL;
    }

    if(flag) {

      /* Create gts edges */
      if( (e1 = gts_edge_new(gts_edge_class(),v1,v2)) == NULL ) {
	PyErr_SetString(PyExc_MemoryError, "could not create Edge");
	return NULL;
      }
      if( (e2 = gts_edge_new(gts_edge_class(),v2,v3)) == NULL ) {
	PyErr_SetString(PyExc_MemoryError, "could not create Edge");
	gts_object_destroy(GTS_OBJECT(e1));
	return NULL;
      }
      if( (e3 = gts_edge_new(gts_edge_class(),v3,v1)) == NULL ) {
	PyErr_SetString(PyExc_MemoryError, "could not create Edge");
	gts_object_destroy(GTS_OBJECT(e1));
	gts_object_destroy(GTS_OBJECT(e2));
	return NULL;
      }
      
      /* Check for duplicates */
      if( (e = gts_edge_is_duplicate(e1)) != NULL ) {
	gts_object_destroy(GTS_OBJECT(e1));
	e1 = e;
      }
      if( (e = gts_edge_is_duplicate(e2)) != NULL ) {
	gts_object_destroy(GTS_OBJECT(e2));
	e2 = e;
      }
      if( (e = gts_edge_is_duplicate(e3)) != NULL ) {
	gts_object_destroy(GTS_OBJECT(e3));
	e3 = e;
      }
    }
  
    /* Check that edges connect */
    s1 = GTS_SEGMENT(e1);
    s2 = GTS_SEGMENT(e2);
    s3 = GTS_SEGMENT(e3);
    if( !((s1->v1==s3->v2 && s1->v2==s2->v1 && s2->v2==s3->v1) ||
	  (s1->v1==s3->v2 && s1->v2==s2->v2 && s2->v1==s3->v1) ||
	  (s1->v1==s3->v1 && s1->v2==s2->v1 && s2->v2==s3->v2) ||
	  (s1->v2==s3->v2 && s1->v1==s2->v1 && s2->v2==s3->v1) ||
	  (s1->v1==s3->v1 && s1->v2==s2->v2 && s2->v1==s3->v2) ||
	  (s1->v2==s3->v2 && s1->v1==s2->v2 && s2->v1==s3->v1) ||
	  (s1->v2==s3->v1 && s1->v1==s2->v1 && s2->v2==s3->v2) ||
	  (s1->v2==s3->v1 && s1->v1==s2->v2 && s2->v1==s3->v2)) ) {
      PyErr_SetString(PyExc_RuntimeError, "Edges in face must connect");
      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
	gts_object_destroy(GTS_OBJECT(e1));
      }
      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
	gts_object_destroy(GTS_OBJECT(e2));
      }
      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
	gts_object_destroy(GTS_OBJECT(e3));
      }
      return NULL;
    }

    /* Create the GtsFace */
    if( (f = gts_face_new(gts_face_class(),e1,e2,e3)) == NULL )  {
      PyErr_SetString(PyExc_MemoryError, "could not create Face");
      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
	gts_object_destroy(GTS_OBJECT(e1));
      }
      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
	gts_object_destroy(GTS_OBJECT(e2));
      }
      if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) {
	gts_object_destroy(GTS_OBJECT(e3));
      }
      return NULL;
    }

    /* Check for duplicate */
    t = gts_triangle_is_duplicate(GTS_TRIANGLE(f));
    if( t != NULL ) {
      gts_object_destroy(GTS_OBJECT(f));
      if(!GTS_IS_FACE(t)) {
	PyErr_SetString(PyExc_TypeError, "expected a Face (internal error)");
      }
      f = GTS_FACE(t);
    }

    /* If corresponding PyObject found in object table, we are done */
    if( (obj=(PygtsObject*)g_hash_table_lookup(obj_table,GTS_OBJECT(f))) != NULL ) {
      Py_INCREF(obj);
      return (PyObject*)obj;
    }
  }
  
  /* Chain up */
  obj = PYGTS_OBJECT(PygtsTriangleType.tp_new(type,args,kwds));

  if( alloc_gtsobj ) {

    obj->gtsobj = GTS_OBJECT(f);

    /* Create the parent GtsSurface */
    if( (obj->gtsobj_parent = parent(GTS_FACE(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(PygtsFace *self, PyObject *args, PyObject *kwds)
{
  gint ret;

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

  return 0;
}


/* Methods table */
PyTypeObject PygtsFaceType = {
    PyVarObject_HEAD_INIT(NULL,0)
    "gts.Face",              /* tp_name */
    sizeof(PygtsFace),       /* 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 */
    "Face 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_face_check(PyObject* o)
{
  if(! PyObject_TypeCheck(o, &PygtsFaceType)) {
    return FALSE;
  }
  else {
#if PYGTS_DEBUG
    return pygts_face_is_ok(PYGTS_FACE(o));
#else
    return TRUE;
#endif
  }
}


gboolean 
pygts_face_is_ok(PygtsFace *f)
{
  GSList *parent;
  PygtsObject *obj;

  obj = PYGTS_OBJECT(f);

  if(!pygts_triangle_is_ok(PYGTS_TRIANGLE(f))) return FALSE;

  /* Check for a valid parent */
  g_return_val_if_fail(obj->gtsobj_parent!=NULL,FALSE);
  g_return_val_if_fail(GTS_IS_SURFACE(obj->gtsobj_parent),FALSE);
  parent = g_slist_find(GTS_FACE(obj->gtsobj)->surfaces,
			obj->gtsobj_parent);
  g_return_val_if_fail(parent!=NULL,FALSE);

  return TRUE;
}


static GtsObject *
parent(GtsFace *face) {
  GtsSurface *p;

  p = gts_surface_new(gts_surface_class(), gts_face_class(),
		      gts_edge_class(), gts_vertex_class());

  if( p == NULL )  {
    PyErr_SetString(PyExc_MemoryError, "could not create parent");
    return NULL;
  }
  gts_surface_add_face(p,face);

  return GTS_OBJECT(p);
}


PygtsFace *
pygts_face_new(GtsFace *f)
{
  PyObject *args, *kwds;
  PygtsObject *face;

  /* Check for Face in the object table */
  if( (face=PYGTS_OBJECT(g_hash_table_lookup(obj_table,GTS_OBJECT(f))))
      != NULL ) {
    Py_INCREF(face);
    return PYGTS_FACE(face);
  }

  /* Build a new Face */
  args = Py_BuildValue("OOO",Py_None,Py_None,Py_None);
  kwds = Py_BuildValue("{s:O}","alloc_gtsobj",Py_False);
  face = PYGTS_OBJECT(PygtsFaceType.tp_new(&PygtsFaceType, args, kwds));
  Py_DECREF(args);
  Py_DECREF(kwds);
  if( face == NULL ) {
    PyErr_SetString(PyExc_MemoryError, "could not create Face");
    return NULL;
  }
  face->gtsobj = GTS_OBJECT(f);

  /* Attach the parent */
  if( (face->gtsobj_parent = parent(f)) == NULL ) {
    Py_DECREF(face);
    return NULL;
  }
  
  /* Register and return */
  pygts_object_register(face);
  return PYGTS_FACE(face);
}

#pragma GCC diagnostic pop

