/* vim: set sw=4 ts=4 sts=4 et: */
#include "l4list.h"

#include <stdlib.h>
#include <string.h>

LbsListMeta* lbs_list_meta_new (void (*free_func) (void*)) {
    LbsListMeta* list = xmalloc (sizeof (LbsListMeta));
    if (list == NULL) {
        return NULL;
    }

    list->first = NULL;
    list->last = NULL;
    list->len = 0;
    list->free_func = free_func;

    return list;
}

void lbs_list_meta_free (LbsListMeta* list) {
    if (list == NULL) {
        return;
    }
    if (list->len <= 0) {
        free (list);
        return;
    }

    LbsList *iter, *next;
    for (iter = list->first; iter != NULL; iter = next) {
        next = iter->next;
        if (iter->free_func != NULL) {
            (*(iter->free_func)) (iter->data);
        } else if (list->free_func != NULL) {
            (*(list->free_func)) (iter->data);
        }
        free (iter);
    }
    free (list);
}

static LbsList* init_first_node (LbsListMeta* list,
    void* data, void (*free_func) (void*)) {

    /* Warning: This function assumes the list is empty! */
    LbsList* node = xmalloc (sizeof (LbsList));
    if (node == NULL) {
        return NULL;
    }

    if (list != NULL) {
        list->first = node;
        list->last = node;
        list->len = 1;
    }

    node->prev = NULL;
    node->next = NULL;
    node->data = data;
    node->free_func = free_func;

    return node;
}

LbsList* lbs_list_insert_prev (LbsListMeta* list, LbsList* node,
    void* data, void (*free_func) (void*)) {

    if (list != NULL && list->len <= 0) {
        return init_first_node (list, data, free_func);
    }

    LbsList* new_node = xmalloc (sizeof (LbsList));
    if (new_node == NULL) {
        return NULL;
    }

    if (node == NULL) {
        new_node->prev = NULL;
        new_node->next = NULL;
        new_node->data = data;
        new_node->free_func = free_func;
        return new_node;
    }

    if (list != NULL) {
        list->len++;
        if (list->first == node) {
            list->first = new_node;
        }
    }

    LbsList* old_prev = node->prev;
    node->prev = new_node;
    new_node->next = node;
    new_node->prev = old_prev;
    if (old_prev != NULL) {
        old_prev->next = new_node;
    }

    new_node->data = data;
    new_node->free_func = free_func;
    return new_node;
}

LbsList* lbs_list_insert_next (LbsListMeta* list, LbsList* node,
    void* data, void (*free_func) (void*)) {

    if (list != NULL && list->len <= 0) {
        return init_first_node (list, data, free_func);
    }

    LbsList* new_node = xmalloc (sizeof (LbsList));
    if (new_node == NULL) {
        return NULL;
    }

    if (node == NULL) {
        new_node->prev = NULL;
        new_node->next = NULL;
        new_node->data = data;
        new_node->free_func = free_func;
        return new_node;
    }

    if (list != NULL) {
        list->len++;
        if(list->last == node){
            list->last = new_node;
        }
    }

    LbsList* old_next = node->next;
    node->next = new_node;
    new_node->prev = node;
    new_node->next = old_next;
    if (old_next != NULL) {
        old_next->prev = new_node;
    }

    new_node->data = data;
    new_node->free_func = free_func;
    return new_node;
}

void lbs_list_remove (LbsListMeta* list, LbsList* node) {
    /* node must not be NULL! */
    if (list != NULL) {
        if (list->first == node) {
            list->first = node->next;
        }
        if (list->last == node) {
            list->last = node->prev;
        }
        if (list->len > 0) {
            list->len--;
        }
    }

    LbsList* old_next = node->next;
    LbsList* old_prev = node->prev;

    if (node->free_func != NULL) {
        (*(node->free_func)) (node->data);
    } else if (list != NULL && list->free_func != NULL) {
        (*(list->free_func)) (node->data);
    }
    free (node);

    if (old_next != NULL) {
        old_next->prev = old_prev;
    }
    if (old_prev != NULL) {
        old_prev->next = old_next;
    }
}

LbsList* lbs_list_goto (LbsList* node, int count) {
    int i;

    if (count > 0) { /* positive */
        for (i = 0; i < count; i++) {
            node = node->next;
            if (node == NULL) {
                return NULL;
            }
        }
    } else if (count < 0) { /* negative */
        count = -count;
        for (i = 0; i < count; i++) {
            node = node->prev;
            if (node == NULL) {
                return NULL;
            }
        }
    }

    return node;
}