#ifndef _PY_TRIPLE_OBJ_H
#define _PY_TRIPLE_OBJ_H

#define PY_SSIZE_T_CLEAN

#include <Python.h>
#include <structmember.h>

#include "py_term.h"


typedef struct {
    PyObject_HEAD
    LSUP_Triple * ob_struct;
    PyObject *s;
    PyObject *p;
    PyObject *o;
} TripleObject;


static int
Triple_init (TripleObject *self, PyObject *args)
{
    PyObject *s = NULL, *p = NULL, *o = NULL, *tmp;

    if (! PyArg_ParseTuple (args, "OOO", &s, &p, &o)) return -1;

    // TODO check for null s, p, o
    tmp = self->s;
    Py_INCREF(s);
    self->s = s;
    Py_XDECREF(tmp);

    tmp = self->p;
    Py_INCREF(p);
    self->p = p;
    Py_XDECREF(tmp);

    tmp = self->o;
    Py_INCREF(o);
    self->o = o;
    Py_XDECREF(tmp);

    self->ob_struct = LSUP_triple_new (
            ((TermObject *) self->s)->ob_struct,
            ((TermObject *) self->p)->ob_struct,
            ((TermObject *) self->o)->ob_struct);
    if (!self->ob_struct) {
        PyErr_SetString (PyExc_ValueError, "Could not create triple.");
        return -1;
    }

    return 0;
}


static void
Triple_dealloc (TripleObject *self)
{
    Py_XDECREF(self->s);
    Py_XDECREF(self->p);
    Py_XDECREF(self->o);
    free (self->ob_struct); // Term pointers were allocated independently.
    Py_TYPE (self)->tp_free ((PyObject *) self);
}


static PyMemberDef Triple_members[] = {
    {"s", T_OBJECT_EX, offsetof(TripleObject, s), 0, "Triple subject."},
    {"p", T_OBJECT_EX, offsetof(TripleObject, p), 0, "Triple predicate."},
    {"o", T_OBJECT_EX, offsetof(TripleObject, o), 0, "Triple object."},
    {NULL}
};


static PyObject *
Triple_richcmp (PyObject *obj1, PyObject *obj2, int op)
{
    // Only equality and non-equality supported.
    if (op != Py_EQ && op != Py_NE) Py_RETURN_NOTIMPLEMENTED;

    LSUP_Triple *t1 = ((TripleObject *) obj1)->ob_struct;
    LSUP_Triple *t2 = ((TripleObject *) obj2)->ob_struct;

    if (
        (
            LSUP_term_equals (t1->s, t2->s) &&
            LSUP_term_equals (t1->p, t2->p) &&
            LSUP_term_equals (t1->o, t2->o)
        ) ^ (op == Py_NE)
    ) Py_RETURN_TRUE;

    Py_RETURN_FALSE;
}


static Py_hash_t
Triple_hash (PyObject *self)
{ return LSUP_triple_hash (((TripleObject *)self)->ob_struct); }


PyTypeObject TripleType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "triple.Triple",
    .tp_doc = "RDF triple.",
    .tp_basicsize = sizeof(TripleObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = PyType_GenericNew,
    .tp_init = (initproc) Triple_init,
    .tp_dealloc = (destructor) Triple_dealloc,
    .tp_members = Triple_members,
    .tp_richcompare = Triple_richcmp,
    .tp_hash = Triple_hash,
};


/** @brief Build a triple object from a LSUP_Triple struct.
 */
PyObject *
build_triple (LSUP_Triple *spo)
{
    PyObject *s_obj, *p_obj, *o_obj, *spo_obj, *term_args, *trp_args;

    term_args = Py_BuildValue (
            "bszz", spo->s->type, spo->s->data,
            spo->s->datatype, spo->s->lang);
    s_obj = PyObject_CallObject ((PyObject *)&TermType, term_args);
    Py_DECREF (term_args);
    if (UNLIKELY (!s_obj)) return NULL;

    term_args = Py_BuildValue (
            "bszz", spo->p->type, spo->p->data,
            spo->p->datatype, spo->p->lang);
    p_obj = PyObject_CallObject ((PyObject *)&TermType, term_args);
    if (UNLIKELY (!p_obj)) return NULL;
    Py_DECREF (term_args);

    term_args = Py_BuildValue (
            "bszz", spo->o->type, spo->o->data,
            spo->o->datatype, spo->o->lang);
    o_obj = PyObject_CallObject ((PyObject *)&TermType, term_args);
    if (UNLIKELY (!o_obj)) return NULL;
    Py_DECREF (term_args);

    trp_args = Py_BuildValue ("OOO", s_obj, p_obj, o_obj);
    spo_obj = PyObject_CallObject ((PyObject *)&TripleType, trp_args);
    if (UNLIKELY (!spo_obj)) return NULL;
    Py_DECREF (trp_args);

    return spo_obj;
}

#endif