#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <lcm/eventlog.h>

// to support python 2.5 and earlier
#ifndef Py_TYPE
#define Py_TYPE(ob) (((PyObject *) (ob))->ob_type)
#endif

// to support python 3 where strings are bytes
#if PY_MAJOR_VERSION < 3
#define PyBytes_FromString PyString_FromString
#endif

typedef struct {
    PyObject_HEAD

        lcm_eventlog_t *eventlog;
    char mode;
} PyLogObject;

PyDoc_STRVAR(pylog_doc,
             "\
Event Log parser\n\
");

// gives redefinition error in MSVC
// PyTypeObject pylcmeventlog_type;

static PyObject *pylog_close(PyLogObject *self)
{
    if (self->eventlog) {
        lcm_eventlog_destroy(self->eventlog);
        self->eventlog = NULL;
    }
    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *pylog_read_next_event(PyLogObject *self)
{
    if (!self->eventlog) {
        PyErr_SetString(PyExc_ValueError, "event log already closed");
        return NULL;
    }

    if (self->mode != 'r') {
        PyErr_SetString(PyExc_RuntimeError, "reading not allowed in write mode");
        return NULL;
    }

    lcm_eventlog_event_t *next_event = lcm_eventlog_read_next_event(self->eventlog);
    if (!next_event) {
        Py_INCREF(Py_None);
        return Py_None;
    }

    Py_ssize_t channellen = next_event->channellen;
    Py_ssize_t datalen = next_event->datalen;
#if PY_MAJOR_VERSION >= 3
    PyObject *result = Py_BuildValue("LLs#y#",  // build from bytes
                                     next_event->eventnum, next_event->timestamp,
                                     next_event->channel, channellen, next_event->data, datalen);
#else
    PyObject *result = Py_BuildValue("LLs#s#", next_event->eventnum, next_event->timestamp,
                                     next_event->channel, channellen, next_event->data, datalen);
#endif
    lcm_eventlog_free_event(next_event);

    return result;
}

static PyObject *pylog_seek(PyLogObject *self, PyObject *arg)
{
    int64_t offset = PyLong_AsLongLong(arg);
    if (PyErr_Occurred())
        return 0;

    if (!self->eventlog) {
        PyErr_SetString(PyExc_ValueError, "event log already closed");
        return NULL;
    }

    if (self->mode != 'r') {
        PyErr_SetString(PyExc_RuntimeError, "seeking not allowed in write mode");
        return NULL;
    }

    fseeko(self->eventlog->f, offset, SEEK_SET);

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *pylog_seek_to_timestamp(PyLogObject *self, PyObject *arg)
{
    int64_t timestamp = PyLong_AsLongLong(arg);
    if (PyErr_Occurred())
        return 0;

    if (!self->eventlog) {
        PyErr_SetString(PyExc_ValueError, "event log already closed");
        return NULL;
    }

    if (self->mode != 'r') {
        PyErr_SetString(PyExc_RuntimeError, "seeking not allowed in write mode");
        return NULL;
    }

    if (0 == lcm_eventlog_seek_to_timestamp(self->eventlog, timestamp)) {
        Py_INCREF(Py_None);
        return Py_None;
    } else {
        PyErr_SetFromErrno(PyExc_IOError);
        return Py_None;
    }
}

static PyObject *pylog_write_next_event(PyLogObject *self, PyObject *args)
{
    int64_t utime = 0;
    char *channel = NULL;
    Py_ssize_t channellen = 0;

    // TODO use a buffer object instead of a string
    uint8_t *data = NULL;
    Py_ssize_t datalen = 0;

    if (!PyArg_ParseTuple(args, "Ls#s#", &utime, &channel, &channellen, &data, &datalen)) {
        return NULL;
    }

    if (!self->eventlog) {
        PyErr_SetString(PyExc_ValueError, "event log already closed");
        return NULL;
    }

    if (self->mode != 'w') {
        PyErr_SetString(PyExc_RuntimeError, "writing not allowed in read mode");
        return NULL;
    }

    lcm_eventlog_event_t le;  // msvc needs init of all fields seperately
    le.eventnum = 0;
    le.timestamp = utime;
    le.channellen = channellen;
    le.datalen = datalen;
    le.channel = channel;
    le.data = data;

    if (0 != lcm_eventlog_write_event(self->eventlog, &le)) {
        PyErr_SetFromErrno(PyExc_IOError);
        return NULL;
    }

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *pylog_size(PyLogObject *self)
{
    struct stat sbuf;
    if (0 != fstat(fileno(self->eventlog->f), &sbuf)) {
        PyErr_SetFromErrno(PyExc_IOError);
        return NULL;
    }
    return PyLong_FromLongLong(sbuf.st_size);
}

static PyObject *pylog_ftell(PyLogObject *self)
{
    return PyLong_FromLongLong(ftello(self->eventlog->f));
}

static PyMethodDef pylog_methods[] = {
    {"close", (PyCFunction) pylog_close, METH_NOARGS, ""},
    {"seek", (PyCFunction) pylog_seek, METH_O, ""},
    {"seek_to_timestamp", (PyCFunction) pylog_seek_to_timestamp, METH_O, ""},
    {"read_next_event", (PyCFunction) pylog_read_next_event, METH_NOARGS, ""},
    {"write_event", (PyCFunction) pylog_write_next_event, METH_VARARGS, ""},
    {"size", (PyCFunction) pylog_size, METH_NOARGS, ""},
    {"ftell", (PyCFunction) pylog_ftell, METH_NOARGS, ""},
    {NULL, NULL}, /* sentinel */
};

// ==================== class administrative methods ====================

static PyObject *pylog_repr(PyLogObject *s)
{
    char buf[512];
    PyOS_snprintf(buf, sizeof(buf), "<Log object ... TODO>");
    return PyBytes_FromString(buf);
}

static PyObject *pylog_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    // msvc does not allow usage of new as variable name
    PyObject *newobj;

    newobj = type->tp_alloc(type, 0);
    if (newobj != NULL) {
        ((PyLogObject *) newobj)->eventlog = NULL;
        ((PyLogObject *) newobj)->mode = 0;
    }
    return newobj;
}

static void pylog_dealloc(PyLogObject *self)
{
    if (self->eventlog) {
        lcm_eventlog_destroy(self->eventlog);
    }
    Py_TYPE(self)->tp_free((PyObject *) self);
}

static int pylog_initobj(PyObject *s, PyObject *args, PyObject *kwds)
{
    PyLogObject *self = (PyLogObject *) s;
    static char *keywords[] = {"filename", "mode", 0};
    char *filename = NULL;
    char *mode = "r";

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s", keywords, &filename, &mode))
        return -1;

    if (!strcmp(mode, "r")) {
        self->mode = 'r';
    } else if (!strcmp(mode, "w")) {
        self->mode = 'w';
    } else {
        PyErr_SetString(PyExc_ValueError, "invalid mode");
        return -1;
    }

    if (self->eventlog) {
        lcm_eventlog_destroy(self->eventlog);
    }

    self->eventlog = lcm_eventlog_create(filename, mode);
    if (!self->eventlog) {
        PyErr_SetFromErrno(PyExc_IOError);
        return -1;
    }

    return 0;
}

/* Type object */
PyTypeObject pylcmeventlog_type = {
#if PY_MAJOR_VERSION >= 3
    PyVarObject_HEAD_INIT(0, 0) /* size is now part of macro */
#else
    PyObject_HEAD_INIT(0) /* Must fill in type value later */
    0,                    /* ob_size */
#endif
    "EventLog",                               /* tp_name */
    sizeof(PyLogObject),                      /* tp_basicsize */
    0,                                        /* tp_itemsize */
    (destructor) pylog_dealloc,               /* tp_dealloc */
    0,                                        /* tp_print */
    0,                                        /* tp_getattr */
    0,                                        /* tp_setattr */
    0,                                        /* tp_compare */
    (reprfunc) pylog_repr,                    /* tp_repr */
    0,                                        /* tp_as_number */
    0,                                        /* tp_as_sequence */
    0,                                        /* tp_as_mapping */
    0,                                        /* tp_hash */
    0,                                        /* tp_call */
    0,                                        /* tp_str */
    PyObject_GenericGetAttr,                  /* tp_getattro */
    0,                                        /* tp_setattro */
    0,                                        /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
    pylog_doc,                                /* tp_doc */
    0,                                        /* tp_traverse */
    0,                                        /* tp_clear */
    0,                                        /* tp_richcompare */
    0,                                        /* tp_weaklistoffset */
    0,                                        /* tp_iter */
    0,                                        /* tp_iternext */
    pylog_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 */
    pylog_initobj,                            /* tp_init */
    PyType_GenericAlloc,                      /* tp_alloc */
    pylog_new,                                /* tp_new */
    PyObject_Del,                             /* tp_free */
};
