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

// 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 "-Wmisleading-indentation"
#pragma GCC diagnostic ignored "-Wcast-function-type"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#pragma GCC diagnostic ignored "-Wunused-parameter"

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

static PyObject* is_unattached(PygtsObject* self, PyObject* args, PyObject* kwds)
{
	/* Objects are unattached by default */
	Py_INCREF(Py_False);
	return Py_False;
}


/* Methods table */
static PyMethodDef methods[] = {
	{ "is_unattached",
	  (PyCFunction)is_unattached,
	  METH_NOARGS,
	  "True if this Object o is not attached to another Object.\n"
	  "Otherwise False.\n"
	  "\n"
	  "Trace: o.is_unattached().\n" },

	{ NULL } /* Sentinel */
};


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

static PyObject* id(PygtsObject* self, void* closure)
{
	if (self->gtsobj == NULL) {
		PyErr_SetString(PyExc_RuntimeError, "GTS object does not exist!");
		return NULL;
	}
	/* Use the pointer of the gtsobj */
	return PyLong_FromVoidPtr((void*)(self->gtsobj));
}


/* Methods table */
static PyGetSetDef getset[] = {
	{ "id", (getter)id, NULL, "GTS object id", NULL }, { NULL } /* Sentinel */
};


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

static void dealloc(PygtsObject* self)
{
	/* De-register entry from the object table */
	pygts_object_deregister(self);

	if (self->gtsobj_parent != NULL) {
		/* Free the parent; GTS will free the child unless it is attached
     * to something else.
     */
		gts_object_destroy(self->gtsobj_parent);
		self->gtsobj_parent = NULL;
	} else {
		/* We have the only reference, and so it is safe to destroy the gtsobj 
     * (unless it was never created in the first place).
     */
		if (self->gtsobj != NULL) {
			gts_object_destroy(self->gtsobj);
			self->gtsobj = NULL;
		}
	}
	Py_TYPE(self)->tp_free((PyObject*)self);
}


static PyObject* new_(PyTypeObject* type, PyObject* args, PyObject* kwds)
{
	PygtsObject* self;

	/* Chain up object allocation */
	self = PYGTS_OBJECT(type->tp_alloc(type, 0));
	if (self == NULL) return NULL;

	/* Object initialization */
	self->gtsobj        = NULL;
	self->gtsobj_parent = NULL;

	return (PyObject*)self;
}


static int init(PygtsObject* self, PyObject* args, PyObject* kwds)
{
	if (self->gtsobj == NULL) {
		PyErr_SetString(PyExc_RuntimeError, "Cannot create abstract Object");
		return -1;
	}

	return 0;
}


/* replace with rich comparison: https://docs.python.org/2/c-api/typeobj.html#PyTypeObject.tp_richcompare */
#if PY_MAJOR_VERSION >= 3
static PyObject* rich_compare(PygtsObject* o1, PygtsObject* o2, int op)
{
	int ret = 0;
	switch (op) {
		case Py_LT: ret = (o1->gtsobj < o2->gtsobj); break;
		case Py_LE: ret = (o1->gtsobj <= o2->gtsobj); break;
		case Py_EQ: ret = (o1->gtsobj == o2->gtsobj); break;
		case Py_NE: ret = (o1->gtsobj != o2->gtsobj); break;
		case Py_GT: ret = (o1->gtsobj > o2->gtsobj); break;
		case Py_GE: ret = (o1->gtsobj >= o2->gtsobj); break;
		default: Py_RETURN_NOTIMPLEMENTED;
	}
	if (ret) Py_RETURN_TRUE;
	Py_RETURN_FALSE;
};
#else
static int compare(PygtsObject* o1, PygtsObject* o2)
{
	if (o1->gtsobj == o2->gtsobj) {
		return 0;
	} else {
		return -1;
	}
}
#endif


/* Methods table */
PyTypeObject PygtsObjectType = {
	PyVarObject_HEAD_INIT(NULL, 0) "gts.Object", /* tp_name */
	sizeof(PygtsObject),                         /* tp_basicsize */
	0,                                           /* tp_itemsize */
	(destructor)dealloc,                         /* tp_dealloc */
	0,                                           /* tp_print */
	0,                                           /* tp_getattr */
	0,                                           /* tp_setattr */
#if PY_MAJOR_VERSION >= 3
	0, /* tp_compare */
#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 */
	"Base 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_object_check(PyObject* o)
{
	if (!PyObject_TypeCheck(o, &PygtsObjectType)) {
		return FALSE;
	} else {
#if PYGTS_DEBUG
		return pygts_object_is_ok(PYGTS_OBJECT(o));
#else
		return TRUE;
#endif
	}
}


gboolean pygts_object_is_ok(PygtsObject* o)
{
	g_return_val_if_fail(o->gtsobj != NULL, FALSE);
	g_return_val_if_fail(g_hash_table_lookup(obj_table, o->gtsobj) != NULL, FALSE);
	return TRUE;
}


/*-------------------------------------------------------------------------*/
/* Object table functions */

GHashTable* obj_table; /* GtsObject key, associated PyObject value */

void pygts_object_register(PygtsObject* o)
{
	if (g_hash_table_lookup(obj_table, o->gtsobj) == NULL) { g_hash_table_insert(obj_table, o->gtsobj, o); }
}


void pygts_object_deregister(PygtsObject* o)
{
	if (o->gtsobj != NULL) {
		if (g_hash_table_lookup(obj_table, o->gtsobj) == o) { g_hash_table_remove(obj_table, o->gtsobj); }
	}
}

#pragma GCC diagnostic pop
