#include "ldapvaluelist.h"
#include "utils.h"

static int
ldapvaluelist_clear(LDAPValueList *self) {
    PyObject *tmp;

    tmp = (PyObject *)self->added;
    self->added = NULL;
    Py_XDECREF(tmp);

    tmp = (PyObject *)self->deleted;
    self->deleted = NULL;
    Py_XDECREF(tmp);

    Py_TYPE(self)->tp_base->tp_clear((PyObject *)self);

    return 0;
}

/*  Deallocate the LDAPValueList. */
static void
ldapvaluelist_dealloc(LDAPValueList *self) {
    ldapvaluelist_clear(self);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static int
ldapvaluelist_traverse(LDAPValueList *self, visitproc visit, void *arg) {
    Py_VISIT(self->deleted);
    Py_VISIT(self->added);
    return 0;
}

/*  Create a new LDAPValueList object. For tracking changes uses two other Python list,
    one for addition and an other for deletion.
*/
static PyObject *
ldapvaluelist_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
    LDAPValueList *self;

    self = (LDAPValueList *)UniqueListType.tp_new(type, args, kwds);
    if (self == NULL) return NULL;

    self->added = UniqueList_New();
    if (self->added == NULL) return NULL;

    self->deleted = UniqueList_New();
    if (self->deleted == NULL) return NULL;

    self->status = 0;

    return (PyObject *)self;
}

/*  Initialising LDAPValueList. */
static int
ldapvaluelist_init(LDAPValueList *self, PyObject *args, PyObject *kwds) {
    if (UniqueListType.tp_init((PyObject *)self, args, kwds) < 0)
        return -1;
    return 0;
}

/*  Create a new LDAPValueList object for internal use. */
LDAPValueList *
LDAPValueList_New(void) {
    LDAPValueList *self = (LDAPValueList *)LDAPValueListType.tp_new(&LDAPValueListType, NULL, NULL);
    return self;
}

/*  Append new unique item to the LDAPValueList. Case-insensitive,
    the `newitem` is also appended to the added list, or remove from the deleted list.
*/
int
LDAPValueList_Append(LDAPValueList *self, PyObject *newitem) {
    int rc = -1;

    rc = UniqueList_Append((UniqueList *)self, newitem);
    if (rc == -1) return -1;

    rc = UniqueList_Remove_wFlg(self->deleted, newitem);
    if (rc == -1) return -1;
    if (rc == 0) {
        if (UniqueList_Append(self->added, newitem) == -1) return -1;
    }
    return 0;
}

/*  Returns 1 if obj is an instance of LDAPEntry, or 0 if not.
    On error, returns -1 and sets an exception.
*/
int
LDAPValueList_Check(PyObject *obj) {
    if (obj == NULL) return -1;
    return PyObject_IsInstance(obj, (PyObject *)&LDAPValueListType);
}

/*  Removes the same items from both list. */
static int
balancing(PyObject *l1, UniqueList *l2) {
    int rc;
    Py_ssize_t i;

    for (i = 0; i < Py_SIZE(l2); i++) {
        rc = UniqueList_Remove_wFlg((UniqueList *)l1, l2->list.ob_item[i]);
        if (rc == 1) {
            UniqueList_SetSlice(l2, i, i+1, (PyObject *)NULL);
        } else if (rc == -1) return -1;
    }
    return 0;
}

int
LDAPValueList_Extend(LDAPValueList *self, PyObject *b) {
    if (balancing(b, self->deleted) != 0) return -1;
    if (UniqueList_Extend(self->added, b) != 0) return -1;
    if (UniqueList_Extend((UniqueList *)self, b) != 0) return -1;
    return 0;
}

/*  Insert new unique item to the `where` position in LDAPValueList. Case-insensitive,
    the `newitem` is also appended to the added list, or remove from the deleted list.
*/
int
LDAPValueList_Insert(LDAPValueList *self, Py_ssize_t where, PyObject *newitem) {
    int rc = -1;

    rc = UniqueList_Remove_wFlg(self->deleted, newitem);
    if (rc == -1) return -1;
    if (rc == 0) {
        if (UniqueList_Append(self->added, newitem) == -1) return -1;
    }
    return UniqueList_Insert((UniqueList *)self, where, newitem);
}

int
LDAPValueList_Remove(LDAPValueList *self, PyObject *value) {
    int cmp;
    Py_ssize_t i;

    for (i = 0; i < Py_SIZE(self); i++) {
        cmp = lower_case_match(((PyListObject *)self)->ob_item[i], value);
        if (cmp > 0) {
            if (LDAPValueList_SetSlice(self, i, i+1, (PyObject *)NULL) == 0) return 0;
            return -1;
        } else if (cmp < 0) return -1;
    }
    PyErr_SetString(PyExc_ValueError, "LDAPListValue.remove(x): x not in list");
    return -1;
}

PyObject *
LDAPValueList_Status(LDAPValueList *self) {
    PyObject *status_dict = NULL;
    PyObject *status = NULL;

    status_dict = PyDict_New();
    if (status_dict == NULL) return NULL;

    status = PyLong_FromLong((long int)self->status);
    if (status == NULL) {
        Py_DECREF(status_dict);
        return NULL;
    }

    if (PyDict_SetItemString(status_dict, "@status", status) != 0) {
        Py_DECREF(status_dict);
        Py_DECREF(status);
        return NULL;
    }
    Py_DECREF(status);
    if (PyDict_SetItemString(status_dict, "@added",
            (PyObject *)self->added) != 0) {
        Py_DECREF(status_dict);
        return NULL;
    }
    if (PyDict_SetItemString(status_dict, "@deleted",
            (PyObject *)self->deleted) != 0) {
        Py_DECREF(status_dict);
        return NULL;
    }

    return status_dict;
}

/*  Set new unique item at `i` index in LDAPValueList to `newitem`. Case-insensitive,
    the `newitem` is also appended to the added list, or remove from the deleted list.
    Same goes for the replaced item.
*/
int
LDAPValueList_SetItem(LDAPValueList *self, Py_ssize_t i, PyObject *newitem) {
    int rc = -1;
    PyObject *olditem;

    if (UniqueList_SetItem((UniqueList *)self, i, newitem) != 0) return -1;

    olditem = PyList_GetItem((PyObject *)self, i);
    if (olditem == NULL) return -1;
    rc = UniqueList_Remove_wFlg(self->added, olditem);
    if (rc == -1) return -1;
    if (rc == 0) {
        if (UniqueList_Append(self->deleted, olditem) == -1) return -1;
    }

    rc = UniqueList_Remove_wFlg(self->deleted, newitem);
    if (rc == -1) return -1;
    if (rc == 0) {
        if (UniqueList_Append(self->added, newitem) == -1) return -1;
    }
    return 0;
}

/*  Set the slice of LDAPValueList between `ilow` and `ihigh` to the contents of `itemlist`.
    The `itemlist` must be containing unique elements. New items are append to the added list,
    and removed items are append to the deleted list. The `itemlist` may be NULL, indicating
    the assignment of an empty list (slice deletion).
*/
int
LDAPValueList_SetSlice(LDAPValueList *self, Py_ssize_t ilow, Py_ssize_t ihigh, PyObject *itemlist) {
    PyObject *remove;

    /* Copying the removable items from LDAPValueList to deleted list.*/
    remove = PyList_GetSlice((PyObject *)self, ilow, ihigh);
    if (remove == NULL) return -1;
    if (balancing(remove, self->added) != 0) {
        Py_DECREF(remove);
        return -1;
    }
    if (UniqueList_Extend(self->deleted, remove) != 0) {
        Py_DECREF(remove);
        return -1;
    }
    Py_DECREF(remove);

    /* Copying new items to the added list.*/
    if (itemlist != NULL) {
        if (balancing(itemlist, self->deleted) != 0) return -1;
        if (UniqueList_Extend(self->added, itemlist) != 0) return -1;
    }

    return UniqueList_SetSlice((UniqueList *)self, ilow, ihigh, itemlist);
}

static PyObject *
ldapvaluelist_append(LDAPValueList *self, PyObject *newitem) {
    if (LDAPValueList_Append(self, newitem) == 0) {
        self->status = 1;
        return Py_None;
    }
    return NULL;
}

static PyObject *
ldapvaluelist_extend(LDAPValueList *self, PyObject *b) {
    if (LDAPValueList_Extend(self, b) == 0) {
        self->status = 1;
        return Py_None;
    }
    return NULL;
}

static PyObject *
ldapvaluelist_insert(LDAPValueList *self, PyObject *args) {
    Py_ssize_t i;
    PyObject *v;

    if (!PyArg_ParseTuple(args, "nO:insert", &i, &v)) return NULL;
    if (LDAPValueList_Insert(self, i, v) == 0) {
        self->status = 1;
        return Py_None;
    }
    return NULL;
}

static PyObject *
ldapvaluelist_pop(LDAPValueList *self, PyObject *args) {
    int status;
    Py_ssize_t i = -1;
    PyObject *value;

    if (!PyArg_ParseTuple(args, "|n:pop", &i))
        return NULL;

    if (Py_SIZE(self) == 0) {
        PyErr_SetString(PyExc_IndexError, "pop from empty list");
        return NULL;
    }
    if (i < 0) i += Py_SIZE(self);
    if (i < 0 || i >= Py_SIZE(self)) {
        PyErr_SetString(PyExc_IndexError, "pop index out of range");
        return NULL;
    }
    value = ((PyListObject *)self)->ob_item[i];
    Py_INCREF(value);

    status = LDAPValueList_SetSlice(self, i, i+1, (PyObject *)NULL);
    if (status != 0) return NULL;

    self->status = 2;
    return value;
}

static PyObject *
ldapvaluelist_remove(LDAPValueList *self, PyObject *value) {
    if (LDAPValueList_Remove(self, value) == 0) {
        self->status = 2;
        return Py_None;
    }
    return NULL;
}

static PyMethodDef ldapvaluelist_methods[] = {
    {"append",  (PyCFunction)ldapvaluelist_append,
            METH_O, "Append new item to the LDAPValueList." },
    {"extend",  (PyCFunction)ldapvaluelist_extend,
            METH_O, "Extend LDAPValueList."},
    {"insert",  (PyCFunction)ldapvaluelist_insert,
            METH_VARARGS, "Insert new item in the LDAPValueList."},
    {"pop",     (PyCFunction)ldapvaluelist_pop,
            METH_VARARGS, "Pop-pop."},
    {"remove",  (PyCFunction)ldapvaluelist_remove,
            METH_O, "Remove item from LDAPValueList."},
    {NULL, NULL, 0, NULL}  /* Sentinel */
};

static int
ldapvaluelist_ass_item(LDAPValueList *self, Py_ssize_t i, PyObject *v) {
    if (i < 0 || i >= Py_SIZE(self)) {
        PyErr_SetString(PyExc_IndexError,
                        "list assignment index out of range");
        return -1;
    }
    if (v == NULL) return LDAPValueList_SetSlice(self, i, i+1, v);
    return LDAPValueList_SetItem(self, i, v);
}

static PySequenceMethods ldapvaluelist_as_sequence = {
    0,                              /* sq_length */
    0,          /* sq_concat */
    0,        /* sq_repeat */
    0,                              /* sq_item */
    0,                              /* sq_slice */
    (ssizeobjargproc)ldapvaluelist_ass_item,    /* sq_ass_item */
    0,                              /* sq_ass_slice */
    0,       /* sq_contains */
    0,  /* sq_inplace_concat */
    0,        /* sq_inplace_repeat */
};

/*  This function is based on list_ass_subscript from Python source listobject.c.
    But it uses LDAPValueList's setslice and setitem functions instead of memory opertation.
    It is probably a much more slower, but cleaner solution.
*/
static int
ldapvaluelist_ass_subscript(LDAPValueList *self, PyObject *item, PyObject *value) {
    size_t cur;
    Py_ssize_t i;
    Py_ssize_t start, stop, step, slicelength;
    PyObject *seq;
    PyObject **seqitems;

    if (PyIndex_Check(item)) {
        i = PyNumber_AsSsize_t(item, PyExc_IndexError);
        if (i == -1 && PyErr_Occurred()) return -1;
        if (i < 0) i += PyList_GET_SIZE(self);
        return ldapvaluelist_ass_item(self, i, value);
    } else if (PySlice_Check(item)) {
        if (PySlice_GetIndicesEx(item, Py_SIZE(self), &start, &stop, &step, &slicelength) < 0) {
            return -1;
        }

        if (step == 1) return LDAPValueList_SetSlice(self, start, stop, value);

        /* Make sure s[5:2] = [..] inserts at the right place:
           before 5, not before 2. */
        if ((step < 0 && start < stop) || (step > 0 && start > stop)) {
            stop = start;
        }

        if (value == NULL) {
            /* delete slice */
            if (slicelength <= 0) return 0;

            if (step < 0) {
                stop = start + 1;
                start = stop + step*(slicelength - 1) - 1;
                step = -step;
            }

            for (cur = start, i = 0; cur < (size_t)stop; cur += step, i++) {
                if (LDAPValueList_SetSlice(self, cur-i, cur+1-i, (PyObject *)NULL) != 0) {
                    return -1;
                }
            }
            return 0;
        } else {
            /* assign slice */
            /* protect against a[::-1] = a */
            if (self == (LDAPValueList*)value) {
                seq = PyList_GetSlice(value, 0, PyList_GET_SIZE(value));
            } else {
                seq = PySequence_Fast(value, "must assign iterable to extended slice");
            }
            if (!seq) return -1;

            if (PySequence_Fast_GET_SIZE(seq) != slicelength) {
                PyErr_Format(PyExc_ValueError, "attempt to assign sequence of size %zd to extended slice of "
                        "size %zd", PySequence_Fast_GET_SIZE(seq), slicelength);
                Py_DECREF(seq);
                return -1;
            }

            if (!slicelength) {
                Py_DECREF(seq);
                return 0;
            }

            seqitems = PySequence_Fast_ITEMS(seq);
            for (cur = start, i = 0; i < slicelength; cur += (size_t)step, i++) {
                if (LDAPValueList_SetItem(self, cur, seqitems[i]) != 0) {
                    return -1;
                }
            }
            Py_DECREF(seq);
            return 0;
        }
    } else {
        PyErr_Format(PyExc_TypeError, "list indices must be integers, not %.200s", item->ob_type->tp_name);
        return -1;
    }
}

static PyMappingMethods ldapvaluelist_as_mapping = {
    0,                                  /* mp_length */
    0,                                  /* mp_subscript */
    (objobjargproc)ldapvaluelist_ass_subscript, /* mp_ass_subscript */
};

PyTypeObject LDAPValueListType = {
    PyObject_HEAD_INIT(NULL)
    "_bonsai.ldapvaluelist",      /* tp_name */
    sizeof(LDAPValueList),       /* tp_basicsize */
    0,                       /* tp_itemsize */
    (destructor)ldapvaluelist_dealloc,       /* tp_dealloc */
    0,                       /* tp_print */
    0,                       /* tp_getattr */
    0,                       /* tp_setattr */
    0,                       /* tp_reserved */
    0,                       /* tp_repr */
    0,                       /* tp_as_number */
    &ldapvaluelist_as_sequence,        /* tp_as_sequence */
    &ldapvaluelist_as_mapping,       /* 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 |
        Py_TPFLAGS_HAVE_GC, /* tp_flags */
    0,                       /* tp_doc */
    (traverseproc)ldapvaluelist_traverse, /* tp_traverse */
    (inquiry)ldapvaluelist_clear, /* tp_clear */
    0,                       /* tp_richcompare */
    0,                       /* tp_weaklistoffset */
    0,                       /* tp_iter */
    0,                       /* tp_iternext */
    ldapvaluelist_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 */
    (initproc)ldapvaluelist_init,/* tp_init */
    0,                       /* tp_alloc */
    ldapvaluelist_new,      /* tp_new */
};
