#ifdef C_TEST
#define npy_intp int64_t
#else
#include <Python.h>
#include <numpy/ndarraytypes.h>
//#include <numpy/ufuncobject.h>
#include <structmember.h>
#endif
#include <stdlib.h>

typedef struct ll_node_s
{
    struct ll_node_s * next;
    npy_intp value;
} ll_node_t;

void
ll_recursively_delete_list(ll_node_t * list)
{
    ll_node_t * next_node;
    while(list)
    {
        next_node = list->next;
        free(list);
        list = next_node;
    }
}

ll_node_t *
ll_node_t_new(ll_node_t * next, npy_intp value)
{
    ll_node_t * node = malloc(sizeof(ll_node_t));
    if(!node)
    {
        return NULL;
    }
    node->next = next;
    node->value = value;
    return node;
}

int
ll_insert_value(ll_node_t ** list, npy_intp value)
{
    ll_node_t * current_node;
    ll_node_t * last_node;
    ll_node_t * new_node;

    if(!*list)
    {
        *list = ll_node_t_new(NULL, value);
        if(*list)
        {
            return 0;
        }
        return 1;
    }

    current_node = *list;
    last_node = *list;
    while(current_node && current_node->value < value)
    {
        last_node = current_node;
        current_node = current_node->next;
        
    }

    if(current_node && current_node->value == value)
    {
        return 2;
    }

    
    new_node = ll_node_t_new(current_node, value);
    if(!new_node)
    {
        return 1;
    }
    // This is the case, when we set the first element.
    if(current_node == last_node)
    {   
        *list = new_node;
        return 0;
    }
    last_node->next = new_node;
    return 0;
}

int
ll_delete_value(ll_node_t ** list, npy_intp value)
{
    ll_node_t * current_node;
    ll_node_t * last_node;

    current_node = *list;
    last_node = *list;

    while(current_node && current_node->value < value)
    {
        last_node = current_node;
        current_node = current_node->next;
    }

    if(!current_node || current_node->value != value)
    {
        return 2;
    }

    if(current_node == last_node)
    {
        *list = current_node->next;
    }

    last_node->next = current_node->next;
    free(current_node);
    return 0;
}

int
ll_has_value(ll_node_t * list, npy_intp value)
{
    while(list && list->value < value)
    {
        list = list->next;
    }

    if(list && list->value == value)
    {
        return 1;
    }
    return 0;
}

#ifdef C_TEST

#include <stdio.h>

void
print_list(ll_node_t * list)
{
    while(list)
    {
        printf("%d->", list->value);
        list = list->next;
    }
    printf("NULL\n");
}

int
main(void)
{
    ll_node_t * list = NULL;
    int i;

    npy_intp array[15] = {14, 5, 3, 13, 11, 2, 6, 1, 12, 4, 9, 10, 0, 7, 8};

    for(i = 0; i < 15; i++)
    {
        ll_insert_value(&list, array[i]);
    }

    print_list(list);

    ll_delete_value(&list, 5);
    print_list(list);

    ll_delete_value(&list, 0);
    print_list(list);

    printf("HAS 2 %d\n", ll_has_value(list, 2));
    printf("HAS 5 %d\n", ll_has_value(list, 5));
    printf("HAS 20 %d\n", ll_has_value(list, 20));


    ll_recursively_delete_list(list);
    return 0;
}


#else

typedef struct 
{
    PyObject_HEAD
    ll_node_t ** lists;
    npy_intp length;
    
} ALL_Array;

static int
ALL_Array_init(ALL_Array * self
            , PyObject * args
            , PyObject * kwds)
{
    static char * kwrds[] = {"length", NULL};
    npy_intp length;

    if(!PyArg_ParseTupleAndKeywords(args, kwds, "I", kwrds, &length))
    {
        return -1;
    }

    if(length <= 0)
    {
        PyErr_SetString(PyExc_ValueError, "length must be positive");
        return -1;
    }

    self->lists = calloc(sizeof(ll_node_t), length);
    self->length = length;
    if(!self->lists)
    {
        PyErr_SetString(PyExc_MemoryError, "out of memory");
        return -1;
    }
    return 0;
}

static PyObject *
ALL_Array_getitem(ALL_Array * self
                , PyObject * args)
{
    // Somehow PyArg_ParseTuple does utter crap if I 
    // do not set i and j explicitly before calling PyArg_ParseTuple.
    npy_intp i = 0xdeadbeef, j = 0xdeadbeef, swp, result;
    PyObject * result_obj;

    if(!PyArg_ParseTuple(args, "II", &i, &j))
    {
        return NULL;
    }

    if(i < j)
    {
        swp = i;
        i = j;
        j = swp;
    }

    if(j < 0)
    {
        PyErr_SetString(PyExc_KeyError, "index must be positive");
        return NULL;
    }
    if(i >= self->length)
    {
        PyErr_SetString(PyExc_KeyError, "index out of range");
        return NULL;
    }

    result  = ll_has_value(self->lists[i], j);
    result_obj = Py_BuildValue("i", result);
    return result_obj;
}

static PyObject *
ALL_Array_setitem(ALL_Array * self
                , PyObject * args)
{
    npy_intp i = 0xdeadbeef, j = 0xdeadbeef, swp, value = 0xdeadbeef;
    //npy_intp i, j, swp, value;
    int result;

    if(!PyArg_ParseTuple(args, "IIp", &i, &j, &value))
    {
        return NULL;
    }


    if(i < j)
    {
        swp = i;
        i = j;
        j = swp;
    }

    if(i >= self->length)
    {
        PyErr_SetString(PyExc_KeyError, "index out of range");
        return NULL;
    }

    if(value)
    {
        result = ll_insert_value(&(self->lists[i]), j);
        if(result == 2)
        {
            PyErr_SetString(PyExc_ValueError, "element is already set");
            return NULL;
        }
        if(result == 1)
        {
            PyErr_SetString(PyExc_MemoryError, "failed to allocate new node");
            return NULL;
        }
        Py_RETURN_NONE;
    }
    else
    {
        result = ll_delete_value(&(self->lists[i]), j);

        if(result)
        {
            PyErr_SetString(PyExc_ValueError, "element is not set");
            return NULL;
        }
        Py_RETURN_NONE;
    }
}

static void
ALL_Array_dealloc(ALL_Array * self)
{
    int i;
    for(i = 0; i < self->length; i++)
    {
        ll_recursively_delete_list(self->lists[i]);
    }
    free(self->lists);
    Py_TYPE(self)->tp_free((PyObject *) self);
}

static PyMemberDef ALL_Array_members[] = {{NULL}};
static PyMethodDef ALL_Array_methods[] = {
    {"setitem", (PyCFunction) ALL_Array_setitem, METH_VARARGS, "sets an item"}
    , {"getitem", (PyCFunction) ALL_Array_getitem, METH_VARARGS, "gets an item"}
    , {NULL}
};

static PyTypeObject ALL_ArrayType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "sorted_list.ALL_Array",
    .tp_doc = "special type for graph representation",
    .tp_basicsize = sizeof(ALL_Array),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_new = PyType_GenericNew,
    .tp_init = (initproc) ALL_Array_init,
    .tp_dealloc = (destructor) ALL_Array_dealloc,
    .tp_members = ALL_Array_members,
    .tp_methods = ALL_Array_methods,
};

static PyModuleDef sorted_listmodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "sorted_list",
    .m_doc = "module containing a special graph storage class",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_sorted_list(void)
{
    PyObject * m;
    if(PyType_Ready(&ALL_ArrayType) < 0)
    {
        return NULL;
    }

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

    Py_INCREF(&ALL_ArrayType);
    if(PyModule_AddObject(m, "ALL_Array", (PyObject *) &ALL_ArrayType) < 0)
    {
        Py_DECREF(&ALL_ArrayType);
        Py_DECREF(m);
        return NULL;
    }
    return m;
}
#endif
