/* pygts - python segment 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_segment_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"

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

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


static PyObject*
intersects(PygtsSegment *self, PyObject *args)
{
  PyObject *s_;
  GtsSegment *s;

  SELF_CHECK

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

  /* Convert to PygtsObjects */
  if(!pygts_segment_check(s_)) {
    PyErr_SetString(PyExc_TypeError,"expected a Segment");
    return NULL;
  }
  s = PYGTS_SEGMENT_AS_GTS_SEGMENT(s_);

  return Py_BuildValue("i",
      gts_segments_are_intersecting(PYGTS_SEGMENT_AS_GTS_SEGMENT(self),s));
}


static PyObject*
connects(PygtsSegment *self, PyObject *args)
{
  PyObject *v1_,*v2_;
  GtsVertex *v1,*v2;

  SELF_CHECK

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

  /* Convert to PygtsObjects */
  if(!pygts_vertex_check(v1_)) {
    PyErr_SetString(PyExc_TypeError,"expected a Vertex");
    return NULL;
  }
  v1 = PYGTS_VERTEX_AS_GTS_VERTEX(v1_);

  if(!pygts_vertex_check(v2_)) {
    PyErr_SetString(PyExc_TypeError,"expected a Vertex");
    return NULL;
  }
  v2 = PYGTS_VERTEX_AS_GTS_VERTEX(v2_);

  if(gts_segment_connect(PYGTS_SEGMENT_AS_GTS_SEGMENT(self),v1,v2)) {
    Py_INCREF(Py_True);
    return Py_True;
  }
  else {
    Py_INCREF(Py_False);
    return Py_False;
  }
}


static PyObject*
touches(PygtsSegment *self, PyObject *args)
{
  PyObject *s_;
  GtsSegment *s;

  SELF_CHECK

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

  /* Convert to PygtsObjects */
  if(!pygts_segment_check(s_)) {
    PyErr_SetString(PyExc_TypeError,"expected a Segment");
    return NULL;
  }
  s = PYGTS_SEGMENT_AS_GTS_SEGMENT(s_);

  if(gts_segments_touch(PYGTS_SEGMENT_AS_GTS_SEGMENT(self),s)) {
    Py_INCREF(Py_True);
    return Py_True;
  }
  else {
    Py_INCREF(Py_False);
    return Py_False;
  }
}


static PyObject*
midvertex(PygtsSegment *self, PyObject *args)
{
  PygtsVertex *vertex;
  GtsVertex *v;

  SELF_CHECK

  v = gts_segment_midvertex(PYGTS_SEGMENT_AS_GTS_SEGMENT(self),
			    gts_vertex_class());

  if( (vertex = pygts_vertex_new(v)) == NULL ) {
    return NULL;
  }

  return (PyObject*)vertex;
}


static PyObject*
intersection(PygtsSegment *self, PyObject *args)
{
  PyObject *t_,*boundary_=NULL;
  PygtsTriangle *t;
  gboolean boundary=TRUE;
  GtsVertex *v;
  PygtsObject *vertex;

  SELF_CHECK

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

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

  if( boundary_ != NULL ) {
    if(PyBool_Check(boundary_)==FALSE) {
      PyErr_SetString(PyExc_TypeError,"expected a Triangle and boolean");
      return NULL;
    }
    if( boundary_ == Py_False ){  /* Default TRUE */
      boundary = FALSE;
    }
  }

  v = GTS_VERTEX( gts_segment_triangle_intersection(
		      PYGTS_SEGMENT_AS_GTS_SEGMENT(self),
		      PYGTS_TRIANGLE_AS_GTS_TRIANGLE(t),
		      boundary,
		      GTS_POINT_CLASS(gts_vertex_class())) );

  if( v == NULL ) {
    Py_INCREF(Py_None);
    return Py_None;
  }

  if( (vertex = pygts_vertex_new(v)) == NULL ) {
    return NULL;
  }

  return (PyObject *)vertex;
}


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

  {"intersects", (PyCFunction)intersects,
   METH_VARARGS,
   "Checks if this Segment s1 intersects with Segment s2.\n"
   "Returns 1 if they intersect, 0 if an endpoint of one Segment lies\n"
   "on the other Segment, -1 otherwise\n"
   "\n"
   "Signature: s1.intersects(s2).\n"
  },  

  {"connects", (PyCFunction)connects,
   METH_VARARGS,
   "Returns True if this Segment s1 connects Vertices v1 and v2.\n"
   "False otherwise.\n"
   "\n"
   "Signature: s1.connects(v1,v2).\n"
  },  

  {"touches", (PyCFunction)touches,
   METH_VARARGS,
   "Returns True if this Segment s1 touches Segment s2\n"
   "(i.e., they share a common Vertex).  False otherwise.\n"
   "\n"
   "Signature: s1.touches(s2).\n"
  },  

  {"midvertex", (PyCFunction)midvertex,
   METH_NOARGS,
   "Returns a new Vertex at the mid-point of this Segment s.\n"
   "\n"
   "Signature: s.midvertex().\n"
  },  

  {"intersection", (PyCFunction)intersection,
   METH_VARARGS,
   "Returns the intersection of Segment s with Triangle t\n"
   "\n"
   "This function is geometrically robust in the sense that it will\n"
   "return None if s and t do not intersect and will return a\n"
   "Vertex if they do. However, the point coordinates are subject\n"
   "to round-off errors.  None will be returned if s is contained\n"
   "in the plane defined by t.\n"
   "\n"
   "Signature: s.intersection(t) or s.intersection(t,boundary).\n"
   "\n"
   "If boundary is True (default), the boundary of s is taken into\n"
   "account.\n"
   "\n"
   "Returns a summit of t (if boundary is True), one of the endpoints\n"
   "of s, a new Vertex at the intersection of s with t, or None if\n"
   "s and t don't intersect.\n"
  },  

  {NULL}  /* Sentinel */
};


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

static PyObject *
get_v1(PygtsSegment *self, void *closure)
{
  PygtsObject *v1;

  SELF_CHECK

  if( (v1=pygts_vertex_new(PYGTS_SEGMENT_AS_GTS_SEGMENT(self)->v1)) == NULL ) {
    return NULL;
  }

  return (PyObject *)v1;
}


static PyObject *
get_v2(PygtsSegment *self, void *closure)
{
  PygtsObject *v2;

  SELF_CHECK

  if( (v2=pygts_vertex_new(PYGTS_SEGMENT_AS_GTS_SEGMENT(self)->v2)) == NULL ) {
    return NULL;
  }

  return (PyObject *)v2;
}


/* Methods table */
static PyGetSetDef getset[] = {
    {"v1", (getter)get_v1, NULL, "Vertex 1", NULL},
    {"v2", (getter)get_v2, NULL, "Vertex 2", NULL},
    {NULL}  /* Sentinel */
};


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

static PyObject *
new_(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
  PyObject *o;
  PygtsObject *obj;
  GtsSegment *tmp;
  GtsObject *segment=NULL;
  PyObject *v1_=NULL,*v2_=NULL;
  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 are identical");
      return NULL;
    }

    /* Create the GtsSegment */
    segment = GTS_OBJECT(gts_segment_new(gts_segment_class(),
					 GTS_VERTEX(v1->gtsobj),
					 GTS_VERTEX(v2->gtsobj)));
    if( segment == NULL )  {
      PyErr_SetString(PyExc_MemoryError, "could not create Segment");
      return NULL;
    }

    /* Check for duplicate */
    tmp = gts_segment_is_duplicate(GTS_SEGMENT(segment));
    if( tmp != NULL ) {
      gts_object_destroy(segment);
      segment = GTS_OBJECT(tmp);
    }

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

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

  if( alloc_gtsobj ) {
    obj->gtsobj = segment;
    pygts_object_register(PYGTS_OBJECT(obj));
  }

  return (PyObject*)obj;
}


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

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

  return 0;
}


static int 
compare(PyObject *s1_, PyObject *s2_)
{
  GtsSegment *s1, *s2;

#if PYGTS_DEBUG
  pygts_segment_check(s1_);
  pygts_segment_check(s2_);
#endif

  s1 = PYGTS_SEGMENT_AS_GTS_SEGMENT(s1_);
  s2 = PYGTS_SEGMENT_AS_GTS_SEGMENT(s2_);
  
  return pygts_segment_compare(s1,s2);
  
}

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



/* Methods table */
PyTypeObject PygtsSegmentType = {
    PyVarObject_HEAD_INIT(NULL,0)
    "gts.Segment",           /* tp_name */
    sizeof(PygtsSegment),    /* 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 */
    "Segment 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 */
    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_segment_check(PyObject* o)
{
  if(! PyObject_TypeCheck(o, &PygtsSegmentType)) {
    return FALSE;
  }
  else {
#if PYGTS_DEBUG
    return pygts_segment_is_ok(PYGTS_SEGMENT(o));
#else
    return TRUE;
#endif
  }
}


gboolean 
pygts_segment_is_ok(PygtsSegment *s)
{
  if(!pygts_object_is_ok(PYGTS_OBJECT(s))) return FALSE;
  return gts_segment_is_ok(PYGTS_SEGMENT_AS_GTS_SEGMENT(s));
}


PygtsSegment * 
pygts_segment_new(GtsSegment *s)
{
  PyObject *args, *kwds;
  PygtsObject *segment;

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

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

  /* Register and return */
  pygts_object_register(segment);
  return PYGTS_SEGMENT(segment);
}


int 
pygts_segment_compare(GtsSegment* s1,GtsSegment* s2)
{
  if( (pygts_point_compare(GTS_POINT(s1->v1),GTS_POINT(s2->v1))==0 &&
       pygts_point_compare(GTS_POINT(s1->v2),GTS_POINT(s2->v2))==0) || 
      (pygts_point_compare(GTS_POINT(s1->v1),GTS_POINT(s2->v2))==0 &&
       pygts_point_compare(GTS_POINT(s1->v2),GTS_POINT(s2->v1))==0) ) {
    return 0;
  }
  return -1;
}

#pragma GCC diagnostic pop

