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

#include <pgf/pgf.h>
#include "./expr.h"
#include "./ffi.h"
#include "./transactions.h"

PyObject *
PGF_checkoutBranch(PGFObject *self, PyObject *args)
{
    PgfExn err;
    PgfRevision rev = pgf_checkout_revision(self->db, &err);

    if (handleError(err) != PGF_EXN_NONE) {
        return NULL;
    }

    pgf_free_revision(self->db, self->revision);
    self->revision = rev;

    Py_RETURN_TRUE;
}

TransactionObject *
PGF_newTransaction(PGFObject *self, PyObject *args)
{
    const char *s = NULL;
    Py_ssize_t size;
    if (!PyArg_ParseTuple(args, "|s#", &s, &size))
        return NULL;

    PgfExn err;
    PgfRevision rev = pgf_start_transaction(self->db, &err);
    if (handleError(err) != PGF_EXN_NONE) {
        return NULL;
    }

    TransactionObject *trans = (TransactionObject *)pgf_TransactionType.tp_alloc(&pgf_TransactionType, 0);
    trans->pgf = self;
    trans->revision = rev;

    return trans;
}

PyObject *
PGF_getGlobalFlag(PGFObject *self, PyObject *args)
{
    const char *s;
    Py_ssize_t size;
    if (!PyArg_ParseTuple(args, "s#", &s, &size))
        return NULL;

    PgfText *flagname = CString_AsPgfText(s, size);

    PgfExn err;
    PgfLiteral lit = pgf_get_global_flag(self->db, self->revision, flagname, &unmarshaller, &err);
    FreePgfText(flagname);
    if (handleError(err) != PGF_EXN_NONE) {
        return NULL;
    } else if (lit == 0) {
        PyErr_Format(PyExc_KeyError, "unknown global flag '%s'", s);
        return NULL;
    }

    return (PyObject *)lit;
}


PyObject *
PGF_getAbstractFlag(PGFObject *self, PyObject *args)
{
    const char *s;
    Py_ssize_t size;
    if (!PyArg_ParseTuple(args, "s#", &s, &size))
        return NULL;

    PgfText *flagname = CString_AsPgfText(s, size);

    PgfExn err;
    PgfLiteral lit = pgf_get_abstract_flag(self->db, self->revision, flagname, &unmarshaller, &err);
    FreePgfText(flagname);
    if (handleError(err) != PGF_EXN_NONE) {
        return NULL;
    } else if (lit == 0) {
        PyErr_Format(PyExc_KeyError, "unknown abstract flag '%s'", s);
        return NULL;
    }

    return (PyObject *)lit;
}

// ----------------------------------------------------------------------------

static void
Transaction_dealloc(TransactionObject *self)
{
    Py_TYPE(self)->tp_free(self);
}

static PyObject *
Transaction_commit(TransactionObject *self, PyObject *args)
{
    PgfExn err;
    pgf_commit_transaction(self->pgf->db, self->revision, &err);
    if (handleError(err) != PGF_EXN_NONE) {
        return NULL;
    }

    pgf_free_revision(self->pgf->db, self->pgf->revision);
    self->pgf->revision = self->revision;
    Py_INCREF(self->pgf);

    Py_RETURN_NONE;
}

static PyObject *
Transaction_createFunction(TransactionObject *self, PyObject *args)
{
    const char *s;
    Py_ssize_t size;
    TypeObject *type;
    Py_ssize_t arity = 0;
    prob_t prob = 0.0;
    if (!PyArg_ParseTuple(args, "s#O!nf", &s, &size, &pgf_TypeType, &type, &arity, &prob))
        return NULL;

    PgfText *name_pattern = CString_AsPgfText(s, size);

    PgfExn err;
    PgfText *name =
        pgf_create_function(self->pgf->db, self->revision, name_pattern, (PgfType) type, arity, NULL, prob, &marshaller, &err);

    FreePgfText(name_pattern);

    if (handleError(err) != PGF_EXN_NONE) {
        return NULL;
    }

    PyObject *py_name = PyUnicode_FromPgfText(name);

    FreePgfText(name);

    return py_name;
}

static PyObject *
Transaction_dropFunction(TransactionObject *self, PyObject *args)
{
    const char *s;
    Py_ssize_t size;
    if (!PyArg_ParseTuple(args, "s#", &s, &size))
        return NULL;

    PgfText *funname = CString_AsPgfText(s, size);

    PgfExn err;
    pgf_drop_function(self->pgf->db, self->revision, funname, &err);
    FreePgfText(funname);
    if (handleError(err) != PGF_EXN_NONE) {
        return NULL;
    }

    Py_RETURN_NONE;
}

static PyObject *
Transaction_createCategory(TransactionObject *self, PyObject *args)
{
    const char *s;
    Py_ssize_t size;
    PyObject *hypos;
    prob_t prob = 0.0;
    if (!PyArg_ParseTuple(args, "s#Of", &s, &size, &hypos, &prob))
        return NULL;
    if (!PySequence_Check(hypos)) {
        PyErr_SetString(PyExc_TypeError, "context must be a sequence");
        return NULL;
    }

    PgfText *catname = CString_AsPgfText(s, size);

    Py_ssize_t n_hypos;
    PgfTypeHypo *context = PySequence_AsHypos(hypos, &n_hypos);
    if (PyErr_Occurred()) {
        FreePgfText(catname);
        return NULL;
    }

    PgfExn err;
    pgf_create_category(self->pgf->db, self->revision, catname, n_hypos, context, prob, &marshaller, &err);

    FreePgfText(catname);
    FreeHypos(context, n_hypos);

    if (handleError(err) != PGF_EXN_NONE) {
        return NULL;
    }

    Py_RETURN_NONE;
}

static PyObject *
Transaction_dropCategory(TransactionObject *self, PyObject *args)
{
    const char *s;
    Py_ssize_t size;
    if (!PyArg_ParseTuple(args, "s#", &s, &size))
        return NULL;

    PgfText *catname = CString_AsPgfText(s, size);

    PgfExn err;
    pgf_drop_category(self->pgf->db, self->revision, catname, &err);
    FreePgfText(catname);
    if (handleError(err) != PGF_EXN_NONE) {
        return NULL;
    }

    Py_RETURN_NONE;
}

static PyObject *
Transaction_setGlobalFlag(TransactionObject *self, PyObject *args)
{
    const char *s;
    Py_ssize_t size;
    PyObject *pyobj;
    if (!PyArg_ParseTuple(args, "s#O", &s, &size, &pyobj))
        return NULL;

    PgfText *flagname = CString_AsPgfText(s, size);
    if (!PyLong_Check(pyobj) && !PyFloat_Check(pyobj) && !PyUnicode_Check(pyobj)) {
        PyErr_SetString(PyExc_TypeError, "flag value must be integer, float, or string");
        return NULL;
    }

    PgfExn err;
    pgf_set_global_flag(self->pgf->db, self->revision, flagname, (PgfLiteral) pyobj, &marshaller, &err);
    FreePgfText(flagname);
    if (handleError(err) != PGF_EXN_NONE) {
        return NULL;
    }

    Py_RETURN_NONE;
}

static PyObject *
Transaction_setAbstractFlag(TransactionObject *self, PyObject *args)
{
    const char *s;
    Py_ssize_t size;
    PyObject *pyobj;
    if (!PyArg_ParseTuple(args, "s#O", &s, &size, &pyobj))
        return NULL;

    PgfText *flagname = CString_AsPgfText(s, size);
    if (!PyLong_Check(pyobj) && !PyFloat_Check(pyobj) && !PyUnicode_Check(pyobj)) {
        PyErr_SetString(PyExc_TypeError, "flag value must be integer, float, or string");
        return NULL;
    }

    PgfExn err;
    pgf_set_abstract_flag(self->pgf->db, self->revision, flagname, (PgfLiteral) pyobj, &marshaller, &err);
    FreePgfText(flagname);
    if (handleError(err) != PGF_EXN_NONE) {
        return NULL;
    }

    Py_RETURN_NONE;
}

static TransactionObject *
Transaction_enter(TransactionObject *self, PyObject *Py_UNUSED(ignored))
{
    Py_INCREF(self);
    return self;
}

static PyObject *
Transaction_exit_impl(TransactionObject *self, PyObject *exc_type, PyObject *exc_value, PyObject *exc_tb)
{
    if (exc_type == Py_None && exc_value == Py_None && exc_tb == Py_None) {
        return Transaction_commit(self, NULL);
    } else {
        PyErr_SetObject(exc_type, exc_value);
        return NULL;
    }
}

// cpython/Modules/_multiprocessing/clinic/semaphore.c.h
// cpython/Modules/_sqlite/connection.c
static PyObject *
Transaction_exit(TransactionObject *self, PyObject *const *args, Py_ssize_t nargs)
{
    PyObject *return_value = NULL;
    PyObject *exc_type = Py_None;
    PyObject *exc_value = Py_None;
    PyObject *exc_tb = Py_None;

    if (nargs < 0 || nargs > 3) {
        goto exit;
    }
    if (nargs < 1) {
        goto skip_optional;
    }
    exc_type = args[0];
    if (nargs < 2) {
        goto skip_optional;
    }
    exc_value = args[1];
    if (nargs < 3) {
        goto skip_optional;
    }
    exc_tb = args[2];
skip_optional:
    return_value = Transaction_exit_impl(self, exc_type, exc_value, exc_tb);

exit:
    return return_value;
}

static PyGetSetDef Transaction_getseters[] = {
    {NULL}  /* Sentinel */
};

static PyMemberDef Transaction_members[] = {
    {NULL}  /* Sentinel */
};

static PyMethodDef Transaction_methods[] = {
    {"commit", (PyCFunction)Transaction_commit, METH_VARARGS,
     "Commit transaction"
    },

    // used in 'with'-syntax
    {"__enter__", (PyCFunction)Transaction_enter, METH_NOARGS, ""},
    {"__exit__", (PyCFunction)(void(*)(void))Transaction_exit, METH_FASTCALL, ""},

    {"createFunction", (PyCFunction)Transaction_createFunction, METH_VARARGS,
     "'createFunction(name,ty,arity,bytecode,prob)' creates a new abstract"
     "syntax function with the given name, type, arity, etc. If the name"
     "contains %d, %x or %a then the pattern is replaced with a random"
     "number in base 10, 16, or 36, which guarantees that the name is"
     "unique. The returned name is the final name after the substitution."
    },
    {"dropFunction", (PyCFunction)Transaction_dropFunction, METH_VARARGS,
     "Drop function"
    },
    {"createCategory", (PyCFunction)Transaction_createCategory, METH_VARARGS,
     "Create category"
    },
    {"dropCategory", (PyCFunction)Transaction_dropCategory, METH_VARARGS,
     "Drop category"
    },

    {"setGlobalFlag", (PyCFunction)Transaction_setGlobalFlag, METH_VARARGS,
     "Set a global flag"
    },
    {"setAbstractFlag", (PyCFunction)Transaction_setAbstractFlag, METH_VARARGS,
     "Set an abstract flag"
    },
    {NULL}  /* Sentinel */
};

PyTypeObject pgf_TransactionType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    //0,                         /*ob_size*/
    "pgf.Transaction",                 /*tp_name*/
    sizeof(TransactionObject),         /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor) Transaction_dealloc,   /*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, // (reprfunc) Transaction_str,        /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
    "Transaction object",              /*tp_doc*/
    0,                           /*tp_traverse */
    0,                           /*tp_clear */
    0,                           /*tp_richcompare */
    0,                           /*tp_weaklistoffset */
    0,                           /*tp_iter */
    0,                           /*tp_iternext */
    Transaction_methods,               /*tp_methods */
    Transaction_members,               /*tp_members */
    Transaction_getseters,             /*tp_getset */
    0,                         /*tp_base */
    0,                         /*tp_dict */
    0,                         /*tp_descr_get */
    0,                         /*tp_descr_set */
    0,                         /*tp_dictoffset */
    0,                         /*tp_init */
    0,                         /*tp_alloc */
    0,                         /*tp_new */
};
