#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <unistd.h>
#include <sys/time.h>
#include <error.h>
#include <poll.h>

#define PY_SSIZE_T_CLEAN
#include <Python.h>

#include "tipcc.h"
#define INVALID_VALUE "Argument value invalid"

PyObject*
createTipcAddrObj()
{
    PyObject *pyMod = NULL, *tipcAddrClass = NULL, *addObj = NULL;

    pyMod = PyImport_ImportModule("tipc");
    if (pyMod == NULL) {
        PyErr_SetFromErrno(PyExc_IOError);
        PyErr_Print();
        return Py_None;
    }

    tipcAddrClass = PyObject_GetAttrString(pyMod, "TipcAddr");
    if (tipcAddrClass == NULL) {
        PyErr_SetFromErrno(PyExc_IOError);
        PyErr_Print();
        return Py_None;
    }
    Py_DECREF(pyMod);

    addObj = PyObject_CallObject(tipcAddrClass, NULL);
    if (addObj == NULL) {
        PyErr_SetFromErrno(PyExc_IOError);
        PyErr_Print();
        return Py_None;
    }
    Py_DECREF(tipcAddrClass);

    return addObj;
}

static PyObject*
convertTipcAddr2py(struct tipc_addr *src)
{
    PyObject *dstObj = createTipcAddrObj();

    if (src != NULL) {
        PyObject_SetAttrString(dstObj, "srv_type",
                               PyLong_FromLong(src->type));
        PyObject_SetAttrString(dstObj, "srv_instance",
                               PyLong_FromLong(src->instance));
        PyObject_SetAttrString(dstObj, "node",
                               PyLong_FromLong(src->node));
    }

    return dstObj;
}

static void*
convertTipcAddr2c(PyObject *srcObj, struct tipc_addr *dst)
{
    PyObject *py_type = NULL, *py_instance = NULL, *py_node = NULL;

    if (srcObj == NULL || dst == NULL) {
        return dst;
    }

    py_type = PyObject_GetAttrString(srcObj, "srv_type");
    if (py_type == NULL) {
        PyErr_SetString(PyExc_ValueError, INVALID_VALUE);
        goto error;
    }
    dst->type = (uint32_t)PyLong_AsLong(py_type);

    py_instance = PyObject_GetAttrString(srcObj, "srv_instance");
    if (py_type == NULL) {
        PyErr_SetString(PyExc_ValueError, INVALID_VALUE);
        goto error;
    }
    dst->instance = (uint32_t)PyLong_AsLong(py_instance);

    py_node = PyObject_GetAttrString(srcObj, "node");
    if (py_type == NULL) {
        PyErr_SetString(PyExc_ValueError, INVALID_VALUE);
        goto error;
    }
    dst->node = (uint32_t)PyLong_AsLong(py_node);

    return dst;

error:
    Py_XDECREF(py_type);
    Py_XDECREF(py_instance);
    Py_XDECREF(py_node);

    return dst;
}

static PyObject*
_pylibtipc_socket(PyObject *self, PyObject *args)
{
    int fd, stype;

    if (!PyArg_ParseTuple(args, "i", &stype)) {
        PyErr_BadArgument();
        return NULL;
    }

    Py_BEGIN_ALLOW_THREADS
    fd = tipc_socket(stype);
    Py_END_ALLOW_THREADS

    if (fd < 0) {
        return PyErr_SetFromErrno(PyExc_IOError);
    }

    return PyLong_FromLong(fd);
}

static PyObject*
_pylibtipc_sockaddr(PyObject *self, PyObject *args)
{
    struct tipc_addr addr;
    int fd;
    int err;

    if (!PyArg_ParseTuple(args, "i", &fd)) {
        PyErr_BadArgument();
        return NULL;
    }

    err = tipc_sockaddr(fd, &addr);
    if (err != 0) {
        return NULL;
    }

    return Py_BuildValue("O&", convertTipcAddr2py, &addr);
}

static PyObject*
_pylibtipc_sock_non_block(PyObject *self, PyObject *args)
{
    int fd;
    int err;

    if (!PyArg_ParseTuple(args, "i", &fd)) {
        PyErr_BadArgument();
        return NULL;
    }
    err = tipc_sock_non_block(fd);

    return PyLong_FromLong(err);
}

static PyObject*
_pylibtipc_sock_rejectable(PyObject *self, PyObject *args)
{
    int fd;
    int err;

    if (!PyArg_ParseTuple(args, "i", &fd)) {
        PyErr_BadArgument();
        return NULL;
    }
    err = tipc_sock_rejectable(fd);

    return PyLong_FromLong(err);
}

static PyObject*
_pylibtipc_sock_importance(PyObject *self, PyObject *args)
{
    int fd;
    int err;
    int pri;

    if (!PyArg_ParseTuple(args, "iI", &fd, &pri)) {
        return NULL;
    }
    err = tipc_sock_importance(fd, pri);

    return PyLong_FromLong(err);
}

static PyObject*
_pylibtipc_close(PyObject *self, PyObject *args)
{
    int fd;
    int err;

    if (!PyArg_ParseTuple(args, "i", &fd)) {
        PyErr_BadArgument();
        return NULL;
    }
    err = tipc_close(fd);

    return PyLong_FromLong(err);
}

static PyObject*
_pylibtipc_bind(PyObject *self, PyObject *args)
{
    uint32_t type, lower, upper, scope;
    int fd;

    int err;

    if (!PyArg_ParseTuple(args, "iIIII", &fd,
                          &type, &lower, &upper, &scope))
    {
        PyErr_BadArgument();
        return NULL;
    }
    err = tipc_bind(fd, type, lower, upper, scope);

    return PyLong_FromLong(err);
}

static PyObject*
_pylibtipc_unbind(PyObject *self, PyObject *args)
{
    uint32_t type, lower, upper;
    int fd;

    int err;

    if (!PyArg_ParseTuple(args, "iIII", &fd,
                          &type, &lower, &upper))
    {
        PyErr_BadArgument();
        return NULL;
    }
    err = tipc_unbind(fd, type, lower, upper);

    return PyLong_FromLong(err);
}

static PyObject*
_pylibtipc_connect(PyObject *self, PyObject *args)
{
    struct tipc_addr addr = {0,0,0};
    int fd;
    int err = 0;

    if (!PyArg_ParseTuple(args, "iO&", &fd,
                          convertTipcAddr2c, &addr))
    {
        PyErr_BadArgument();
        return NULL;
    }
    err = tipc_connect(fd, &addr);

    return PyLong_FromLong(err);
}

static PyObject*
_pylibtipc_listen(PyObject *self, PyObject *args)
{
    int fd, backlog;
    int err = 0;

    if (!PyArg_ParseTuple(args, "ii", &fd, &backlog)) {
        PyErr_BadArgument();
        return NULL;
    }
    err = tipc_listen(fd, backlog);

    return PyLong_FromLong(err);
}

static PyObject*
_pylibtipc_accept(PyObject *self, PyObject *args)
{
    struct tipc_addr addr = {0,0,0};
    int fd;
    int newsd = 0;

    if (!PyArg_ParseTuple(args, "i", &fd)) {
        PyErr_BadArgument();
        return NULL;
    }
    newsd = tipc_accept(fd, &addr);

    return Py_BuildValue("(iO&)", newsd, convertTipcAddr2py, &addr);
}

static PyObject*
_pylibtipc_join(PyObject *self, PyObject *args)
{
    struct tipc_addr addr = {0,0,0};
    int fd;
    int err = 0;
    bool events, loopback;

    if (!PyArg_ParseTuple(args, "iO&bb", &fd,
                          convertTipcAddr2c, &addr,
                          &events, &loopback))
    {
        PyErr_BadArgument();
        return NULL;
    }
    err = tipc_join(fd, &addr, events, loopback);

    return PyLong_FromLong(err);
}

static PyObject*
_pylibtipc_leave(PyObject *self, PyObject *args)
{
    int fd;
    int err = 0;

    if (!PyArg_ParseTuple(args, "i", &fd)) {
        PyErr_BadArgument();
        return NULL;
    }
    err = tipc_leave(fd);

    return PyLong_FromLong(err);
}

static PyObject*
_pylibtipc_topsrv_conn(PyObject *self, PyObject *args)
{
    uint32_t node;
    int sd = 0;

    if (!PyArg_ParseTuple(args, "I", &node)) {
        PyErr_BadArgument();
        return NULL;
    }
    sd = tipc_topsrv_conn(node);

    return PyLong_FromLong(sd);
}

static PyObject*
_pylibtipc_srv_subscr(PyObject *self, PyObject *args)
{
    int fd = 0;
    uint32_t type, lower, upper;
    bool all = false;
    int expire, err;

    if (!PyArg_ParseTuple(args, "iIIIbi", &fd,
                          &type, &lower, &upper,
                          &all, &expire))
    {
        PyErr_BadArgument();
        return NULL;
    }
    err = tipc_srv_subscr(fd, type, lower, upper, all, expire);

    return PyLong_FromLong(err);
}

static PyObject*
_pylibtipc_srv_evt(PyObject *self, PyObject *args)
{
    PyObject *obj_evt = NULL;
    struct tipc_addr srv = {0,0,0}, id = {0,0,0};
    bool expire, available;
    int fd = 0;
    int err = 0;

    if (!PyArg_ParseTuple(args, "i", &fd)) {
        PyErr_BadArgument();
        return NULL;
    }

    err = tipc_srv_evt(fd, &srv, &id, &available, &expire);
    if (err)
        return Py_None;

    obj_evt = Py_BuildValue("(iO&O&bb)", err,
                            convertTipcAddr2py, &srv,
                            convertTipcAddr2py, &id,
                            available, expire);

    return obj_evt;
}

static PyObject*
_pylibtipc_srv_wait(PyObject *self, PyObject *args)
{
    struct tipc_addr srv = {0,0,0};
    int expire;
    bool err = false;

    if (!PyArg_ParseTuple(args, "O&i",
                          convertTipcAddr2c, &srv, &expire))
    {
        PyErr_BadArgument();
        return NULL;
    }
    err = tipc_srv_wait(&srv, expire);

    return Py_BuildValue("i", err);
}

static PyObject*
_pylibtipc_link_evt(PyObject *self, PyObject *args)
{
    PyObject *obj_evt = NULL;
    uint32_t neigh_node = 0;
    int fd = 0;
    bool up;
    int local_bearerid = 0, remote_bearerid = 0, err = 0;

    if (!PyArg_ParseTuple(args, "i", &fd)) {
        PyErr_BadArgument();
        return NULL;
    }
    err = tipc_link_evt(fd, &neigh_node, &up,
                        &local_bearerid,
                        &remote_bearerid);
    if (err) {
        return Py_None;
    }
    obj_evt = Py_BuildValue("(Ibii)",
                             neigh_node, up,
                             local_bearerid,
                             remote_bearerid);

    return obj_evt;
}

static PyObject*
_pylibtipc_link_name(PyObject *self, PyObject *args)
{
    PyObject *obj_link = NULL;
    uint32_t peer = 0;
    int bearerid;
    char buf[256] = {0};

    if (!PyArg_ParseTuple(args, "Ii", &peer, &bearerid)) {
        PyErr_BadArgument();
        return NULL;
    }
    obj_link = Py_BuildValue("s", tipc_linkname(buf, 255, peer, bearerid));

    return obj_link;
}

static PyObject*
_pylibtipc_own_node(PyObject *self, PyObject *args)
{
    return Py_BuildValue("I", tipc_own_node());
}

static PyObject*
_pylibtipc_recvfrom(PyObject *self, PyObject *args)
{
    struct tipc_addr socketid = {0,0,0};
    struct tipc_addr memberid = {0,0,0};
    PyObject *rbuf = NULL;
    int ret = 0, err = 0;
    char *buf = NULL;
    size_t len = 0;
    int fd = 0;

    if (!PyArg_ParseTuple(args, "iK", &fd, &len)) {
        PyErr_BadArgument();
        return NULL;
    }

    buf = (char *)malloc(len);
    if (!buf) {
        PyErr_NoMemory();
        return Py_None;
    }
    memset(buf, 0, len);

    Py_BEGIN_ALLOW_THREADS
    ret = tipc_recvfrom(fd, buf, len, &socketid, &memberid, &err);
    if (ret > 0) {
        rbuf = PyByteArray_FromStringAndSize(buf, len);
    } else {
        rbuf = PyByteArray_FromStringAndSize("", 0);
    }
    free(buf);
    Py_END_ALLOW_THREADS

    return Py_BuildValue("iOO&O&i", ret,
                         rbuf,
                         convertTipcAddr2py, &socketid,
                         convertTipcAddr2py, &memberid,
                         err);
}

static PyObject*
_pylibtipc_recv(PyObject *self, PyObject *args)
{
    PyObject *rbuf = NULL;
    char *buf = NULL;
    size_t len = 0;
    bool waitall;
    int ret = 0;
    int fd = 0;

    if (!PyArg_ParseTuple(args, "iKb", &fd, &len, &waitall)) {
        PyErr_BadArgument();
        return NULL;
    }

    buf = (char *)malloc(len);
    if (!buf) {
        PyErr_NoMemory();
        return Py_None;
    }
    memset(buf, 0, len);

    Py_BEGIN_ALLOW_THREADS
    ret = tipc_recv(fd, buf, len, waitall);
    if (ret > 0) {
        rbuf = PyByteArray_FromStringAndSize(buf, len);
    } else {
        rbuf = PyByteArray_FromStringAndSize("", 0);
    }
    free(buf);
    Py_END_ALLOW_THREADS

    return Py_BuildValue("iO", ret, rbuf);
}

static PyObject*
_pylibtipc_send(PyObject *self, PyObject *args)
{
    int fd = 0;
    size_t len = 0;
    int ret = 0;
    char *buf = NULL;
    PyObject *sbuf = NULL;

    if (!PyArg_ParseTuple(args, "iO", &fd, &sbuf)) {
        PyErr_BadArgument();
        return NULL;
    }

    if (!PyByteArray_Check(sbuf)) {
        PyErr_BadArgument();
        return NULL;
    }

    Py_BEGIN_ALLOW_THREADS
    buf = PyByteArray_AsString(sbuf);
    len = PyByteArray_Size(sbuf);
    ret = tipc_send(fd, buf, len);
    Py_END_ALLOW_THREADS

    return PyLong_FromLong(ret);
}

static PyObject*
_pylibtipc_sendto(PyObject *self, PyObject *args)
{
    int fd = 0;
    size_t len = 0;
    int ret = 0;
    char *buf = NULL;
    PyObject *sbuf = NULL;
    struct tipc_addr dst = {0,0,0};

    if (!PyArg_ParseTuple(args, "iOO&", &fd,
                          &sbuf,
                          convertTipcAddr2c, &dst))
    {
        PyErr_BadArgument();
        return NULL;
    }

    if (!PyByteArray_Check(sbuf)) {
        PyErr_BadArgument();
        return NULL;
    }

    Py_BEGIN_ALLOW_THREADS
    buf = PyByteArray_AsString(sbuf);
    len = PyByteArray_Size(sbuf);
    ret = tipc_sendto(fd, buf, len, &dst);
    Py_END_ALLOW_THREADS

    return PyLong_FromLong(ret);
}

static PyObject*
_pylibtipc_mcast(PyObject *self, PyObject *args)
{
    int fd = 0;
    size_t len = 0;
    int ret = 0;
    char *buf = NULL;

    PyObject *sbuf = NULL;
    struct tipc_addr dst = {0,0,0};

    if (!PyArg_ParseTuple(args, "iOO&", &fd,
                          &sbuf,
                          convertTipcAddr2c, &dst))
    {
        PyErr_BadArgument();
        return NULL;
    }

    if (!PyByteArray_Check(sbuf)) {
        PyErr_BadArgument();
        return NULL;
    }

    Py_BEGIN_ALLOW_THREADS
    buf = PyByteArray_AsString(sbuf);
    len = PyByteArray_Size(sbuf);
    ret = tipc_mcast(fd, buf, len, &dst);
    Py_END_ALLOW_THREADS

    return PyLong_FromLong(ret);
}

static PyMethodDef PyTipcMethods[] = {
    {"socket", _pylibtipc_socket, METH_VARARGS, NULL},
    {"sock_non_block", _pylibtipc_sock_non_block, METH_VARARGS, NULL},
    {"sock_rejectable", _pylibtipc_sock_rejectable, METH_VARARGS, NULL},
    {"sock_importance", _pylibtipc_sock_importance, METH_VARARGS, NULL},
    {"close", _pylibtipc_close, METH_VARARGS, NULL},
    {"sockaddr", _pylibtipc_sockaddr, METH_VARARGS, NULL},
    {"bind", _pylibtipc_bind, METH_VARARGS, NULL},
    {"unbind", _pylibtipc_unbind, METH_VARARGS, NULL},
    {"connect", _pylibtipc_connect, METH_VARARGS, NULL},
    {"listen", _pylibtipc_listen, METH_VARARGS, NULL},
    {"accept", _pylibtipc_accept, METH_VARARGS, NULL},
    {"join", _pylibtipc_join, METH_VARARGS, NULL},
    {"leave", _pylibtipc_leave, METH_VARARGS, NULL},
    {"recvfrom", _pylibtipc_recvfrom, METH_VARARGS, NULL},
    {"recv", _pylibtipc_recv, METH_VARARGS, NULL},
    {"sendto", _pylibtipc_sendto, METH_VARARGS, NULL},
    {"send", _pylibtipc_send, METH_VARARGS, NULL},
    {"mcast", _pylibtipc_mcast, METH_VARARGS, NULL},
    {"topsrv_conn", _pylibtipc_topsrv_conn, METH_VARARGS, NULL},
    {"srv_subscr", _pylibtipc_srv_subscr, METH_VARARGS, NULL},
    {"srv_evt", _pylibtipc_srv_evt, METH_VARARGS, NULL},
    {"srv_wait", _pylibtipc_srv_wait, METH_VARARGS, NULL},
    {"link_evt", _pylibtipc_link_evt, METH_VARARGS, NULL},
    {"link_name", _pylibtipc_link_name, METH_VARARGS, NULL},
    {"own_node", _pylibtipc_own_node, METH_VARARGS, NULL},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef tipccmodule = {
    PyModuleDef_HEAD_INIT,
    "_pylibtipc",
    NULL,
    -1,
    PyTipcMethods
};

PyMODINIT_FUNC
PyInit__pylibtipc(void)
{
    PyObject *mod = PyModule_Create(&tipccmodule);

    if (mod == NULL)
        return NULL;

    PyModule_AddIntConstant(mod, "SOCK_STREAM", SOCK_STREAM);
    PyModule_AddIntConstant(mod, "SOCK_DGRAM", SOCK_DGRAM);
    PyModule_AddIntConstant(mod, "SOCK_RDM", SOCK_RDM);
    PyModule_AddIntConstant(mod, "SOCK_SEQPACKET", SOCK_SEQPACKET);

    PyModule_AddIntConstant(mod, "TIPC_LOW_IMPORTANCE", TIPC_LOW_IMPORTANCE);
    PyModule_AddIntConstant(mod, "TIPC_MEDIUM_IMPORTANCE", TIPC_MEDIUM_IMPORTANCE);
    PyModule_AddIntConstant(mod, "TIPC_HIGH_IMPORTANCE", TIPC_HIGH_IMPORTANCE);
    PyModule_AddIntConstant(mod, "TIPC_CRITICAL_IMPORTANCE", TIPC_CRITICAL_IMPORTANCE);

    /*
     * Application-accessible service types
     */
    PyModule_AddIntConstant(mod, "TIPC_NODE_STATE", TIPC_NODE_STATE);
    PyModule_AddIntConstant(mod, "TIPC_LINK_STATE", TIPC_LINK_STATE);
    PyModule_AddIntConstant(mod, "TIPC_WAIT_FOREVER", TIPC_WAIT_FOREVER);

    return mod;
};
