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

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>


typedef struct {
    PyObject_HEAD
    PyObject *first;
    char *data;
    off_t size;
    char *pos;
} Parser;

static void
Parser_dealloc(Parser* self)
{
    if (self->data)
        munmap(self->data, self->size);

    Py_XDECREF(self->first);
    self->ob_type->tp_free((PyObject*)self);
}

static PyObject *
Parser_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Parser *self;

    self = (Parser *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyString_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->data = NULL;
        self->size = 0;
    }

    return (PyObject *)self;
}

static int check_mapping(char *data, off_t size) {
    if (size < 2)
        return 0;

    // Assure the file ends with \n\n
    if (*(data+size-1) != '\n' || *(data+size-2) != '\n')
        return 0;

    return 1;
}

static int map_file(Parser *self, char *filename) {
    const int size_mask = getpagesize() - 1;
    struct stat stat_buf;
    off_t size;
    size_t mapsize;

    int fd = open(filename, O_RDONLY);
    if (fd == -1) {
        PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
        return -1;
    }

    if (fstat(fd, &stat_buf) == -1) {
        PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
        close(fd);
        return -1;
    }

    size = stat_buf.st_size;
    mapsize = (size + size_mask) & ~size_mask;

    char *data = mmap(0, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);
    if (data == MAP_FAILED) {
        PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
        close(fd);
        return -1;
    }
    close(fd);

    if (!check_mapping(data, size)) {
        PyErr_SetString(PyExc_RuntimeError, "File not valid");
        munmap(data, size);
    }

    if (self->data) {
        if (munmap(self->data, self->size) == -1) {
            PyErr_SetFromErrno(PyExc_IOError);
            munmap(data, size);

            self->data = NULL;
            self->size = 0;
            return -1;
        }
    }
    self->data = data;
    self->size = size;
    self->pos = data;

    return 0;
}

static int
Parser_init(Parser *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *tmp;

    if (! PyArg_ParseTuple(args, "S", &first))
        return -1; 

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

    return map_file(self, PyString_AsString(first));
}

static PyObject *
Parser_getiter(Parser *self) {
    Py_INCREF(self);
    return (PyObject*)self;
}

static char *find_line_end(char *ptr) {
    char *p = ptr;

    while (1) {
        p = strchr(p, '\n');
        if (p[1] != ' ' && p[1] != '\t')
            return p;
        p++;
    }
}

static int
insert_dict(PyObject *dict, 
            char* key_st, char* key_end,
            char* val_st, char* val_end) {

    PyObject *Key =   PyString_FromStringAndSize(key_st, key_end-key_st);
    PyObject *Value = PyString_FromStringAndSize(val_st, val_end-val_st);
    int res;

    if (!Key || !Value) {
        Py_XDECREF(Key);
        Py_XDECREF(Value);
        return 0;
    }

    res = PyDict_SetItem(dict, Key, Value);
    Py_DECREF(Key);
    Py_DECREF(Value);

    if (res == -1)
        return 0;
    return 1;
}

static int parse_record(PyObject *dict, char *start, char *end) {
    char *p = start;
    char *line_end;
    char *colon;
    char *value;

    while (p < end) {
        //printf("p = %s", p);
        if (*p == ' ' || *p == '\t') {
            PyErr_SetString(PyExc_RuntimeError, "Fuckup");
            return 0;
        }

        line_end = find_line_end(p);
        colon = strchr(p, ':');
        if (colon[1] == ' ')
            value = colon + 2;
        else
            value = colon + 1;

        if (!insert_dict(dict, p, colon, value, line_end))
            return 0;

        p = line_end + 1;
    }
    return 1;
}

static PyObject *
Parser_nextiter(Parser *self) {
    PyObject *dict;
    char *start;
    char *end;

    if (self->pos >= self->data + self->size - 2)
        return NULL;
    
    dict = PyDict_New();
    if (!dict)
        return NULL;

    start = self->pos;
    end = strstr(start, "\n\n");

    //printf("start = 0x%08lx, offset = 0x%08lx (%d), remaining = %d\n", start, start - self->data, start - self->data, self->data + self->size - start);
    if (!parse_record(dict, start, end)) {
        Py_DECREF(dict);
        return NULL;
    }

    self->pos = end + 2;

    return dict;
}

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

static PyObject *
Parser_getfirst(Parser *self, void *closure)
{
    Py_INCREF(self->first);
    return self->first;
}

static PyGetSetDef Parser_getseters[] = {
    {"filename", 
     (getter)Parser_getfirst, NULL,
     "The filename of the Package file we operate on",
     NULL},
    {NULL}  /* Sentinel */
};

static PyMethodDef Parser_methods[] = {
    {NULL}  /* Sentinel */
};

static PyTypeObject ParserType = {
    PyObject_HEAD_INIT(NULL)
    0,                         /*ob_size*/
    "pkg_ll.Parser",         /*tp_name*/
    sizeof(Parser),             /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)Parser_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,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
    "Parser objects",           /* tp_doc */
    0,		               /* tp_traverse */
    0,		               /* tp_clear */
    0,		               /* tp_richcompare */
    0,		               /* tp_weaklistoffset */
    (getiterfunc)Parser_getiter,   /* tp_iter */
    (iternextfunc)Parser_nextiter, /* tp_iternext */
    Parser_methods,             /* tp_methods */
    Parser_members,             /* tp_members */
    Parser_getseters,           /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Parser_init,      /* tp_init */
    0,                         /* tp_alloc */
    Parser_new,                 /* tp_new */
};

static int
is_pkgname_start(char c) {
    return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
}

static int
do_parse_depends(PyObject *list, char* line) {
    char *start = line;
    char *end = start;

    PyObject *pkg;

    while (start && *start) {
        end = strpbrk(start, " ,()|");
        if (!end)
            end = start + strlen(start);

        pkg = PyString_FromStringAndSize(start, end-start);
        if (!pkg)
            return 0;
        if (PyList_Append(list, pkg) != 0) {
            Py_DECREF(pkg);
            return 0;
        }
        Py_DECREF(pkg);
        
        if (!*end)
            break;

        start = strpbrk(end, ",|");
        while (start && *start && !is_pkgname_start(*start))
            start++;
    }

    return 1;
}

static PyObject*
parse_depends(PyObject *self, PyObject *args) {
    char *line;
    PyObject *list;

    if (!PyArg_ParseTuple(args, "s", &line))
        return NULL;

    list = PyList_New(0);
    if (!list)
        return NULL;

    if (!do_parse_depends(list, line)) {
        Py_DECREF(list);
        return NULL;
    }

    return list;
}

static PyMethodDef module_methods[] = {
    {"parse_depends", parse_depends, METH_VARARGS,
     "Parse a Depends: line."},
    {NULL}  /* Sentinel */
};

#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initpkg_ll(void) 
{
    PyObject* m;

    if (PyType_Ready(&ParserType) < 0)
        return;

    m = Py_InitModule3("pkg_ll", module_methods,
                       "Example module that creates an extension type.");

    if (m == NULL)
      return;

    Py_INCREF(&ParserType);
    PyModule_AddObject(m, "Parser", (PyObject *)&ParserType);
}

