#include "Python.h"
#include <openvswitch/json.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    struct json_parser *_parser;
} json_ParserObject;

static void
Parser_dealloc(json_ParserObject * p)
{
    json_parser_abort(p->_parser);
    Py_TYPE(p)->tp_free(p);
}

static PyObject *
Parser_new(PyTypeObject * type, PyObject * args, PyObject * kwargs)
{
    json_ParserObject *self;
    static char *kwlist[] = { "check_trailer", NULL };
    PyObject *check_trailer = NULL;
    int ct_int = 0;

    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist,
                                     &check_trailer)) {
        return NULL;
    }

    if (check_trailer != NULL) {
        ct_int = PyObject_IsTrue(check_trailer);
        if (ct_int < 0) {
            return NULL;
        } else if (ct_int) {
            ct_int = JSPF_TRAILER;
        }
    }

    self = (json_ParserObject *) type->tp_alloc(type, 0);
    if (self != NULL) {
        self->_parser = json_parser_create(ct_int);
    }

    return (PyObject *) self;
}

static PyObject *
Parser_feed(json_ParserObject * self, PyObject * args)
{
    Py_ssize_t input_sz;
    PyObject *input;
    size_t rd;
    const char *input_str;

    if (self->_parser == NULL) {
        return NULL;
    }

    if (!PyArg_UnpackTuple(args, "input", 1, 1, &input)) {
        return NULL;
    }
    if ((input_str = PyUnicode_AsUTF8AndSize(input, &input_sz)) == NULL) {
        return NULL;
    }

    rd = json_parser_feed(self->_parser, input_str, (size_t) input_sz);

    return PyLong_FromSize_t(rd);
}

static PyObject *
Parser_is_done(json_ParserObject * self)
{
    if (self->_parser == NULL) {
        return NULL;
    }
    return PyBool_FromLong(json_parser_is_done(self->_parser));
}

static PyObject *
json_to_python(struct json *json)
{
    switch (json->type) {
    case JSON_NULL:
        Py_RETURN_NONE;
    case JSON_FALSE:
        Py_RETURN_FALSE;
    case JSON_TRUE:
        Py_RETURN_TRUE;
    case JSON_OBJECT:{
            struct shash_node *node;
            PyObject *dict = PyDict_New();

            if (dict == NULL) {
                return PyErr_NoMemory();
            }
            SHASH_FOR_EACH (node, json->object) {
                PyObject *key = PyUnicode_FromString(node->name);
                PyObject *val = json_to_python(node->data);

                if (!(key && val) || PyDict_SetItem(dict, key, val)) {
                    Py_XDECREF(key);
                    Py_XDECREF(val);
                    Py_XDECREF(dict);
                    return NULL;
                }

                Py_XDECREF(key);
                Py_XDECREF(val);
            }
            return dict;
        }
    case JSON_ARRAY:{
            size_t i;
            PyObject *arr = PyList_New(json->array.n);

            if (arr == NULL) {
                return PyErr_NoMemory();
            }
            for (i = 0; i < json->array.n; i++) {
                PyObject *item = json_to_python(json->array.elems[i]);

                if (!item || PyList_SetItem(arr, i, item)) {
                    Py_XDECREF(arr);
                    return NULL;
                }
            }
            return arr;
        }
    case JSON_REAL:
        if (json->real != 0) {
            return PyFloat_FromDouble(json->real);
        } /* fall through to treat 0 as int */
    case JSON_INTEGER:
        return PyLong_FromLong((long) json->integer);

    case JSON_STRING:
        return PyUnicode_FromString(json->string);
    default:
        return NULL;
    }
}

static PyObject *
Parser_finish(json_ParserObject * self)
{
    struct json *json;
    PyObject *obj;

    if (self->_parser == NULL) {
        return NULL;
    }

    json = json_parser_finish(self->_parser);
    self->_parser = NULL;
    obj = json_to_python(json);
    json_destroy(json);
    return obj;
}

static PyMethodDef Parser_methods[] = {
    {"feed", (PyCFunction) Parser_feed, METH_VARARGS,
     "Feed data to the parser and return the index of the last object."},
    {"is_done", (PyCFunction) Parser_is_done, METH_NOARGS,
     "Whether the parser has finished decoding an object."},
    {"finish", (PyCFunction) Parser_finish, METH_NOARGS,
     "Finish parsing and return Python object parsed."},
    {NULL},
};

static PyTypeObject json_ParserType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "ovs._json.Parser",
    .tp_doc = "Parser objects",
    .tp_basicsize = sizeof(json_ParserObject),
    .tp_itemsize = 0,
    .tp_dealloc = (destructor) Parser_dealloc,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_methods = Parser_methods,
    .tp_new = Parser_new,
};

static struct PyModuleDef moduledef = {
    PyModuleDef_HEAD_INIT,
    .m_name = "ovs._json",
    .m_doc = "OVS JSON Parser module",
    .m_size = 0,
};

PyMODINIT_FUNC
PyInit__json(void)
{
    PyObject *m;

    if (PyType_Ready(&json_ParserType) < 0) {
        return NULL;
    }

    m = PyModule_Create(&moduledef);
    if (!m) {
        return NULL;
    }

    Py_INCREF(&json_ParserType);
    if (PyModule_AddObject(m, "Parser", (PyObject *) &json_ParserType) < 0) {
        Py_DECREF(&json_ParserType);
        Py_DECREF(m);
        return NULL;
    }
    return m;
}
