/*
 * Copyright (C) 1997-2004, Michael Jennings
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

static const char __attribute__((unused)) cvs_ident[] = "$Id: linked_list.c 37181 2008-10-26 22:57:42Z mej $";

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <libast_internal.h>

/* *INDENT-OFF* */
SPIF_DECL_OBJ(linked_list_iterator) {
    SPIF_DECL_PARENT_TYPE(obj);
    SPIF_DECL_PROPERTY(linked_list, subject);
    SPIF_DECL_PROPERTY(linked_list_item, current);
};
/* *INDENT-ON* */

static spif_linked_list_item_t spif_linked_list_item_new(void);
static spif_bool_t spif_linked_list_item_init(spif_linked_list_item_t);
static spif_bool_t spif_linked_list_item_done(spif_linked_list_item_t);
static spif_bool_t spif_linked_list_item_del(spif_linked_list_item_t);
static spif_str_t spif_linked_list_item_show(spif_linked_list_item_t, spif_charptr_t, spif_str_t, size_t);
static spif_cmp_t spif_linked_list_item_comp(spif_linked_list_item_t, spif_linked_list_item_t);
static spif_linked_list_item_t spif_linked_list_item_dup(spif_linked_list_item_t);
static spif_classname_t spif_linked_list_item_type(spif_linked_list_item_t);
SPIF_DECL_PROPERTY_FUNC(linked_list_item, obj, data);
SPIF_DECL_PROPERTY_FUNC(linked_list_item, linked_list_item, next);

static spif_linked_list_t spif_linked_list_new(void);
static spif_linked_list_t spif_linked_list_vector_new(void);
static spif_linked_list_t spif_linked_list_map_new(void);
static spif_bool_t spif_linked_list_init(spif_linked_list_t);
static spif_bool_t spif_linked_list_vector_init(spif_linked_list_t);
static spif_bool_t spif_linked_list_map_init(spif_linked_list_t);
static spif_bool_t spif_linked_list_done(spif_linked_list_t);
static spif_bool_t spif_linked_list_del(spif_linked_list_t);
static spif_str_t spif_linked_list_show(spif_linked_list_t, spif_charptr_t, spif_str_t, size_t);
static spif_cmp_t spif_linked_list_comp(spif_linked_list_t, spif_linked_list_t);
static spif_linked_list_t spif_linked_list_dup(spif_linked_list_t);
static spif_linked_list_t spif_linked_list_vector_dup(spif_linked_list_t);
static spif_linked_list_t spif_linked_list_map_dup(spif_linked_list_t);
static spif_classname_t spif_linked_list_type(spif_linked_list_t);
static spif_bool_t spif_linked_list_append(spif_linked_list_t self, spif_obj_t obj);
static spif_bool_t spif_linked_list_contains(spif_linked_list_t self, spif_obj_t obj);
static spif_bool_t spif_linked_list_vector_contains(spif_linked_list_t self, spif_obj_t obj);
static spif_listidx_t spif_linked_list_count(spif_linked_list_t self);
static spif_obj_t spif_linked_list_find(spif_linked_list_t self, spif_obj_t obj);
static spif_obj_t spif_linked_list_vector_find(spif_linked_list_t self, spif_obj_t obj);
static spif_obj_t spif_linked_list_get(spif_linked_list_t self, spif_listidx_t idx);
static spif_obj_t spif_linked_list_map_get(spif_linked_list_t self, spif_obj_t key);
static spif_list_t spif_linked_list_get_keys(spif_linked_list_t self, spif_list_t key_list);
static spif_list_t spif_linked_list_get_pairs(spif_linked_list_t self, spif_list_t pair_list);
static spif_list_t spif_linked_list_get_values(spif_linked_list_t self, spif_list_t value_list);
static spif_bool_t spif_linked_list_has_key(spif_linked_list_t self, spif_obj_t key);
static spif_bool_t spif_linked_list_has_value(spif_linked_list_t self, spif_obj_t value);
static spif_listidx_t spif_linked_list_index(spif_linked_list_t self, spif_obj_t obj);
static spif_bool_t spif_linked_list_insert(spif_linked_list_t self, spif_obj_t obj);
static spif_bool_t spif_linked_list_insert_at(spif_linked_list_t self, spif_obj_t obj, spif_listidx_t idx);
static spif_iterator_t spif_linked_list_iterator(spif_linked_list_t self);
static spif_bool_t spif_linked_list_prepend(spif_linked_list_t self, spif_obj_t obj);
static spif_obj_t spif_linked_list_remove(spif_linked_list_t self, spif_obj_t item);
static spif_obj_t spif_linked_list_map_remove(spif_linked_list_t self, spif_obj_t item);
static spif_obj_t spif_linked_list_remove_at(spif_linked_list_t self, spif_listidx_t idx);
static spif_bool_t spif_linked_list_reverse(spif_linked_list_t self);
static spif_bool_t spif_linked_list_set(spif_linked_list_t self, spif_obj_t key, spif_obj_t value);
static spif_obj_t * spif_linked_list_to_array(spif_linked_list_t self);
SPIF_DECL_PROPERTY_FUNC(linked_list, listidx, len);
SPIF_DECL_PROPERTY_FUNC(linked_list, linked_list_item, head);

static spif_linked_list_iterator_t spif_linked_list_iterator_new(spif_linked_list_t subject);
static spif_bool_t spif_linked_list_iterator_init(spif_linked_list_iterator_t self, spif_linked_list_t subject);
static spif_bool_t spif_linked_list_iterator_done(spif_linked_list_iterator_t self);
static spif_bool_t spif_linked_list_iterator_del(spif_linked_list_iterator_t self);
static spif_str_t spif_linked_list_iterator_show(spif_linked_list_iterator_t self, spif_charptr_t name, spif_str_t buff, size_t indent);
static spif_cmp_t spif_linked_list_iterator_comp(spif_linked_list_iterator_t self, spif_linked_list_iterator_t other);
static spif_linked_list_iterator_t spif_linked_list_iterator_dup(spif_linked_list_iterator_t self);
static spif_classname_t spif_linked_list_iterator_type(spif_linked_list_iterator_t self);
static spif_bool_t spif_linked_list_iterator_has_next(spif_linked_list_iterator_t self);
static spif_obj_t spif_linked_list_iterator_next(spif_linked_list_iterator_t self);
SPIF_DECL_PROPERTY_FUNC(linked_list_iterator, linked_list, subject);
SPIF_DECL_PROPERTY_FUNC(linked_list_iterator, linked_list_item, current);

/* *INDENT-OFF* */
static SPIF_CONST_TYPE(class) lli_class = {
    SPIF_DECL_CLASSNAME(linked_list_item),
    (spif_func_t) spif_linked_list_item_new,
    (spif_func_t) spif_linked_list_item_init,
    (spif_func_t) spif_linked_list_item_done,
    (spif_func_t) spif_linked_list_item_del,
    (spif_func_t) spif_linked_list_item_show,
    (spif_func_t) spif_linked_list_item_comp,
    (spif_func_t) spif_linked_list_item_dup,
    (spif_func_t) spif_linked_list_item_type
};
SPIF_TYPE(class) SPIF_CLASS_VAR(linked_list_item) = &lli_class;

static spif_const_listclass_t ll_class = {
    {
        SPIF_DECL_CLASSNAME(linked_list),
        (spif_func_t) spif_linked_list_new,
        (spif_func_t) spif_linked_list_init,
        (spif_func_t) spif_linked_list_done,
        (spif_func_t) spif_linked_list_del,
        (spif_func_t) spif_linked_list_show,
        (spif_func_t) spif_linked_list_comp,
        (spif_func_t) spif_linked_list_dup,
        (spif_func_t) spif_linked_list_type
    },
    (spif_func_t) spif_linked_list_append,
    (spif_func_t) spif_linked_list_contains,
    (spif_func_t) spif_linked_list_count,
    (spif_func_t) spif_linked_list_find,
    (spif_func_t) spif_linked_list_get,
    (spif_func_t) spif_linked_list_index,
    (spif_func_t) spif_linked_list_insert,
    (spif_func_t) spif_linked_list_insert_at,
    (spif_func_t) spif_linked_list_iterator,
    (spif_func_t) spif_linked_list_prepend,
    (spif_func_t) spif_linked_list_remove,
    (spif_func_t) spif_linked_list_remove_at,
    (spif_func_t) spif_linked_list_reverse,
    (spif_func_t) spif_linked_list_to_array
};
spif_listclass_t SPIF_LISTCLASS_VAR(linked_list) = &ll_class;

static spif_const_vectorclass_t llv_class = {
    {
        SPIF_DECL_CLASSNAME(linked_list),
        (spif_func_t) spif_linked_list_vector_new,
        (spif_func_t) spif_linked_list_vector_init,
        (spif_func_t) spif_linked_list_done,
        (spif_func_t) spif_linked_list_del,
        (spif_func_t) spif_linked_list_show,
        (spif_func_t) spif_linked_list_comp,
        (spif_func_t) spif_linked_list_vector_dup,
        (spif_func_t) spif_linked_list_type
    },
    (spif_func_t) spif_linked_list_vector_contains,
    (spif_func_t) spif_linked_list_count,
    (spif_func_t) spif_linked_list_vector_find,
    (spif_func_t) spif_linked_list_insert,
    (spif_func_t) spif_linked_list_iterator,
    (spif_func_t) spif_linked_list_remove,
    (spif_func_t) spif_linked_list_to_array
};
spif_vectorclass_t SPIF_VECTORCLASS_VAR(linked_list) = &llv_class;

static spif_const_mapclass_t llm_class = {
    {
        SPIF_DECL_CLASSNAME(linked_list),
        (spif_func_t) spif_linked_list_map_new,
        (spif_func_t) spif_linked_list_map_init,
        (spif_func_t) spif_linked_list_done,
        (spif_func_t) spif_linked_list_del,
        (spif_func_t) spif_linked_list_show,
        (spif_func_t) spif_linked_list_comp,
        (spif_func_t) spif_linked_list_map_dup,
        (spif_func_t) spif_linked_list_type
    },
    (spif_func_t) spif_linked_list_count,
    (spif_func_t) spif_linked_list_map_get,
    (spif_func_t) spif_linked_list_get_keys,
    (spif_func_t) spif_linked_list_get_pairs,
    (spif_func_t) spif_linked_list_get_values,
    (spif_func_t) spif_linked_list_has_key,
    (spif_func_t) spif_linked_list_has_value,
    (spif_func_t) spif_linked_list_iterator,
    (spif_func_t) spif_linked_list_map_remove,
    (spif_func_t) spif_linked_list_set
};
spif_mapclass_t SPIF_MAPCLASS_VAR(linked_list) = &llm_class;

static spif_const_iteratorclass_t li_class = {
    {
        SPIF_DECL_CLASSNAME(linked_list),
        (spif_func_t) spif_linked_list_iterator_new,
        (spif_func_t) spif_linked_list_iterator_init,
        (spif_func_t) spif_linked_list_iterator_done,
        (spif_func_t) spif_linked_list_iterator_del,
        (spif_func_t) spif_linked_list_iterator_show,
        (spif_func_t) spif_linked_list_iterator_comp,
        (spif_func_t) spif_linked_list_iterator_dup,
        (spif_func_t) spif_linked_list_iterator_type
    },
    (spif_func_t) spif_linked_list_iterator_has_next,
    (spif_func_t) spif_linked_list_iterator_next
};
spif_iteratorclass_t SPIF_ITERATORCLASS_VAR(linked_list) = &li_class;
/* *INDENT-ON* */

static spif_linked_list_item_t
spif_linked_list_item_new(void)
{
    spif_linked_list_item_t self;

    self = SPIF_ALLOC(linked_list_item);
    if (!spif_linked_list_item_init(self)) {
        SPIF_DEALLOC(self);
        self = (spif_linked_list_item_t) NULL;
    }
    return self;
}

static spif_bool_t
spif_linked_list_item_init(spif_linked_list_item_t self)
{
    ASSERT_RVAL(!SPIF_LINKED_LIST_ITEM_ISNULL(self), FALSE);
    self->data = (spif_obj_t) NULL;
    self->next = (spif_linked_list_item_t) NULL;
    return TRUE;
}

static spif_bool_t
spif_linked_list_item_done(spif_linked_list_item_t self)
{
    ASSERT_RVAL(!SPIF_LINKED_LIST_ITEM_ISNULL(self), FALSE);
    if (self->data != (spif_obj_t) NULL) {
        SPIF_OBJ_DEL(self->data);
    }
    self->data = (spif_obj_t) NULL;
    self->next = (spif_linked_list_item_t) NULL;
    return TRUE;
}

static spif_bool_t
spif_linked_list_item_del(spif_linked_list_item_t self)
{
    ASSERT_RVAL(!SPIF_LINKED_LIST_ITEM_ISNULL(self), FALSE);
    spif_linked_list_item_done(self);
    SPIF_DEALLOC(self);
    return TRUE;
}

static spif_str_t
spif_linked_list_item_show(spif_linked_list_item_t self, spif_charptr_t name, spif_str_t buff, size_t indent)
{
    spif_char_t tmp[4096];

    if (SPIF_LINKED_LIST_ITEM_ISNULL(self)) {
        SPIF_OBJ_SHOW_NULL(linked_list_item, name, buff, indent, tmp);
        return buff;
    }

    memset(tmp, ' ', indent);
    snprintf((char *) tmp + indent, sizeof(tmp) - indent,
             "(spif_linked_list_item_t) %s (%9p -> %9p):  ",
             name, (spif_ptr_t) self, (spif_ptr_t) self->next);
    if (SPIF_STR_ISNULL(buff)) {
        buff = spif_str_new_from_ptr(tmp);
    } else {
        spif_str_append_from_ptr(buff, tmp);
    }
    if (SPIF_LINKED_LIST_ITEM_ISNULL(self->data)) {
        spif_str_append_from_ptr(buff, (spif_charptr_t) SPIF_NULLSTR_TYPE(obj) "\n");
    } else {
        buff = SPIF_OBJ_SHOW(self->data, buff, 0);
    }
    return buff;
}

static spif_cmp_t
spif_linked_list_item_comp(spif_linked_list_item_t self, spif_linked_list_item_t other)
{
    SPIF_OBJ_COMP_CHECK_NULL(self, other);
    SPIF_OBJ_COMP_CHECK_NULL(self->data, other->data);
    return ((spif_cmp_t) SPIF_OBJ_COMP(SPIF_OBJ(self->data), SPIF_OBJ(other->data)));
}

static spif_linked_list_item_t
spif_linked_list_item_dup(spif_linked_list_item_t self)
{
    spif_linked_list_item_t tmp;

    ASSERT_RVAL(!SPIF_LINKED_LIST_ITEM_ISNULL(self), FALSE);
    tmp = spif_linked_list_item_new();
    if (!SPIF_OBJ_ISNULL(self->data)) {
        tmp->data = SPIF_OBJ_DUP(self->data);
    }
    return tmp;
}

static spif_classname_t
spif_linked_list_item_type(spif_linked_list_item_t self)
{
    ASSERT_RVAL(!SPIF_LINKED_LIST_ITEM_ISNULL(self), (spif_classname_t) NULL);
    return SPIF_OBJ_CLASSNAME(self);
}

SPIF_DEFINE_PROPERTY_FUNC(linked_list_item, obj, data)
SPIF_DEFINE_PROPERTY_FUNC_NONOBJ(linked_list_item, linked_list_item, next)


static spif_linked_list_t
spif_linked_list_new(void)
{
    spif_linked_list_t self;

    self = SPIF_ALLOC(linked_list);
    if (!spif_linked_list_init(self)) {
        SPIF_DEALLOC(self);
        self = (spif_linked_list_t) NULL;
    }
    return self;
}

static spif_linked_list_t
spif_linked_list_vector_new(void)
{
    spif_linked_list_t self;

    self = SPIF_ALLOC(linked_list);
    if (!spif_linked_list_vector_init(self)) {
        SPIF_DEALLOC(self);
        self = (spif_linked_list_t) NULL;
    }
    return self;
}

static spif_linked_list_t
spif_linked_list_map_new(void)
{
    spif_linked_list_t self;

    self = SPIF_ALLOC(linked_list);
    if (!spif_linked_list_map_init(self)) {
        SPIF_DEALLOC(self);
        self = (spif_linked_list_t) NULL;
    }
    return self;
}

static spif_bool_t
spif_linked_list_init(spif_linked_list_t self)
{
    spif_bool_t t;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), FALSE);
    /* ***NOT NEEDED*** spif_obj_init(SPIF_OBJ(self)); */
    t = spif_obj_set_class(SPIF_OBJ(self), SPIF_CLASS(SPIF_LISTCLASS_VAR(linked_list)));
    self->len = 0;
    self->head = (spif_linked_list_item_t) NULL;
    return t;
}

static spif_bool_t
spif_linked_list_vector_init(spif_linked_list_t self)
{
    spif_bool_t t;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), FALSE);
    /* ***NOT NEEDED*** spif_obj_init(SPIF_OBJ(self)); */
    t = spif_obj_set_class(SPIF_OBJ(self), SPIF_CLASS(SPIF_VECTORCLASS_VAR(linked_list)));
    self->len = 0;
    self->head = (spif_linked_list_item_t) NULL;
    return t;
}

static spif_bool_t
spif_linked_list_map_init(spif_linked_list_t self)
{
    spif_bool_t t;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), FALSE);
    /* ***NOT NEEDED*** spif_obj_init(SPIF_OBJ(self)); */
    t = spif_obj_set_class(SPIF_OBJ(self), SPIF_CLASS(SPIF_MAPCLASS_VAR(linked_list)));
    self->len = 0;
    self->head = (spif_linked_list_item_t) NULL;
    return t;
}

static spif_bool_t
spif_linked_list_done(spif_linked_list_t self)
{
    spif_linked_list_item_t current;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), FALSE);
    if (self->len) {
        for (current = self->head; current;) {
            spif_linked_list_item_t tmp;

            tmp = current;
            current = current->next;
            spif_linked_list_item_del(tmp);
        }
        self->len = 0;
        self->head = (spif_linked_list_item_t) NULL;
    }
    return TRUE;
}

static spif_bool_t
spif_linked_list_del(spif_linked_list_t self)
{
    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), FALSE);
    spif_linked_list_done(self);
    SPIF_DEALLOC(self);
    return TRUE;
}

static spif_str_t
spif_linked_list_show(spif_linked_list_t self, spif_charptr_t name, spif_str_t buff, size_t indent)
{
    spif_char_t tmp[4096];
    spif_linked_list_item_t current;
    spif_listidx_t i;

    if (SPIF_LIST_ISNULL(self)) {
        SPIF_OBJ_SHOW_NULL(linked_list, name, buff, indent, tmp);
        return buff;
    }

    memset(tmp, ' ', indent);
    snprintf((char *) tmp + indent, sizeof(tmp) - indent,
             "(spif_linked_list_t) %s:  %10p {\n", name, (spif_ptr_t) self);
    if (SPIF_STR_ISNULL(buff)) {
        buff = spif_str_new_from_ptr(tmp);
    } else {
        spif_str_append_from_ptr(buff, tmp);
    }

    snprintf((char *) tmp + indent, sizeof(tmp) - indent, "  len:  %lu\n",
             (unsigned long) self->len);
    spif_str_append_from_ptr(buff, tmp);

    if (SPIF_LINKED_LIST_ITEM_ISNULL(self->head)) {
        spif_str_append_from_ptr(buff, (spif_charptr_t) SPIF_NULLSTR_TYPE(obj) "\n");
    } else {
        for (current = self->head, i = 0; current; current = current->next, i++) {
            sprintf((char *) tmp, "item %d", i);
            buff = spif_linked_list_item_show(current, tmp, buff, indent + 2);
        }
    }

    memset(tmp, ' ', indent);
    snprintf((char *) tmp + indent, sizeof(tmp) - indent, "}\n");
    spif_str_append_from_ptr(buff, tmp);
    return buff;
}

static spif_cmp_t
spif_linked_list_comp(spif_linked_list_t self, spif_linked_list_t other)
{
    SPIF_OBJ_COMP_CHECK_NULL(self, other);
    /* FIXME:  This should probably do something more intelligent. */
    return (SPIF_OBJ_COMP(SPIF_OBJ(self), SPIF_OBJ(other)));
}

static spif_linked_list_t
spif_linked_list_dup(spif_linked_list_t self)
{
    spif_linked_list_t tmp;
    spif_linked_list_item_t src, dest;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), (spif_linked_list_t) NULL);
    tmp = spif_linked_list_new();
    memcpy(tmp, self, SPIF_SIZEOF_TYPE(linked_list));
    tmp->head = spif_linked_list_item_dup(self->head);
    for (src = self->head, dest = tmp->head; src->next; src = src->next, dest = dest->next) {
        dest->next = spif_linked_list_item_dup(src->next);
    }
    dest->next = (spif_linked_list_item_t) NULL;
    return tmp;
}

static spif_linked_list_t
spif_linked_list_vector_dup(spif_linked_list_t self)
{
    spif_linked_list_t tmp;
    spif_linked_list_item_t src, dest;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), (spif_linked_list_t) NULL);
    tmp = spif_linked_list_vector_new();
    memcpy(tmp, self, SPIF_SIZEOF_TYPE(linked_list));
    tmp->head = spif_linked_list_item_dup(self->head);
    for (src = self->head, dest = tmp->head; src->next; src = src->next, dest = dest->next) {
        dest->next = spif_linked_list_item_dup(src->next);
    }
    dest->next = (spif_linked_list_item_t) NULL;
    return tmp;
}

static spif_linked_list_t
spif_linked_list_map_dup(spif_linked_list_t self)
{
    spif_linked_list_t tmp;
    spif_linked_list_item_t src, dest;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), (spif_linked_list_t) NULL);
    tmp = spif_linked_list_map_new();
    memcpy(tmp, self, SPIF_SIZEOF_TYPE(linked_list));
    tmp->head = spif_linked_list_item_dup(self->head);
    for (src = self->head, dest = tmp->head; src->next; src = src->next, dest = dest->next) {
        dest->next = spif_linked_list_item_dup(src->next);
    }
    dest->next = (spif_linked_list_item_t) NULL;
    return tmp;
}

static spif_classname_t
spif_linked_list_type(spif_linked_list_t self)
{
    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), (spif_classname_t) NULL);
    return SPIF_OBJ_CLASSNAME(self);
}

static spif_bool_t
spif_linked_list_append(spif_linked_list_t self, spif_obj_t obj)
{
    spif_linked_list_item_t item, current;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), FALSE);
    /* Create list member object "item" */
    item = spif_linked_list_item_new();
    spif_linked_list_item_set_data(item, obj);

    /* Append "item" to the end of the list. */
    if (self->head) {
        for (current = self->head; current->next; current = current->next);
        current->next = item;
    } else {
        self->head = item;
    }

    self->len++;
    return TRUE;
}

static spif_bool_t
spif_linked_list_contains(spif_linked_list_t self, spif_obj_t obj)
{
    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), FALSE);
    return ((SPIF_OBJ_ISNULL(spif_linked_list_find(self, obj))) ? (FALSE) : (TRUE));
}

static spif_bool_t
spif_linked_list_vector_contains(spif_linked_list_t self, spif_obj_t obj)
{
    ASSERT_RVAL(!SPIF_VECTOR_ISNULL(self), FALSE);
    return ((SPIF_OBJ_ISNULL(spif_linked_list_vector_find(self, obj))) ? (FALSE) : (TRUE));
}

static spif_listidx_t
spif_linked_list_count(spif_linked_list_t self)
{
    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), (spif_listidx_t) NULL);
    return self->len;
}

static spif_obj_t
spif_linked_list_find(spif_linked_list_t self, spif_obj_t obj)
{
    spif_linked_list_item_t current;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), (spif_obj_t) NULL);
    REQUIRE_RVAL(!SPIF_OBJ_ISNULL(obj), (spif_obj_t) NULL);
    for (current = self->head; current; current = current->next) {
        /* current->data may be NULL here, so use obj methods. */
        if (SPIF_CMP_IS_EQUAL(SPIF_OBJ_COMP(obj, current->data))) {
            return current->data;
        }
    }
    return (spif_obj_t) NULL;
}

static spif_obj_t
spif_linked_list_vector_find(spif_linked_list_t self, spif_obj_t obj)
{
    spif_linked_list_item_t current;

    ASSERT_RVAL(!SPIF_VECTOR_ISNULL(self), (spif_obj_t) NULL);
    REQUIRE_RVAL(!SPIF_OBJ_ISNULL(obj), (spif_obj_t) NULL);
    for (current = self->head; current; current = current->next) {
        spif_cmp_t c;

        /* current->data is always non-NULL in vectors. */
        ASSERT_RVAL(!SPIF_OBJ_ISNULL(current->data), (spif_obj_t) NULL);
        c = SPIF_OBJ_COMP(current->data, obj);
        if (SPIF_CMP_IS_EQUAL(c)) {
            return current->data;
        } else if (SPIF_CMP_IS_GREATER(c)) {
            break;
        }
    }
    return (spif_obj_t) NULL;
}

static spif_obj_t
spif_linked_list_get(spif_linked_list_t self, spif_listidx_t idx)
{
    spif_listidx_t i;
    spif_linked_list_item_t current;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), (spif_obj_t) NULL);
    if (idx < 0) {
        /* Negative indexes go backward from the end of the list. */
        idx += self->len;
    }
    REQUIRE_RVAL(idx >= 0, (spif_obj_t) NULL);
    REQUIRE_RVAL(idx < self->len, (spif_obj_t) NULL);
    for (current = self->head, i = 0; current && i < idx; i++, current = current->next);
    return (current ? (current->data) : (spif_obj_t) NULL);
}

static spif_obj_t
spif_linked_list_map_get(spif_linked_list_t self, spif_obj_t key)
{
    spif_linked_list_item_t current;

    ASSERT_RVAL(!SPIF_VECTOR_ISNULL(self), (spif_obj_t) NULL);
    REQUIRE_RVAL(!SPIF_OBJ_ISNULL(key), (spif_obj_t) NULL);
    for (current = self->head; current; current = current->next) {
        spif_cmp_t c;

        /* current->data is always non-NULL in maps. */
        ASSERT_RVAL(!SPIF_OBJ_ISNULL(current->data), (spif_obj_t) NULL);
        c = SPIF_OBJ_COMP(current->data, key);
        if (SPIF_CMP_IS_EQUAL(c)) {
            return SPIF_OBJPAIR(current->data)->value;
        } else if (SPIF_CMP_IS_GREATER(c)) {
            break;
        }
    }
    return (spif_obj_t) NULL;
}

static spif_list_t
spif_linked_list_get_keys(spif_linked_list_t self, spif_list_t key_list)
{
    spif_linked_list_item_t current;

    ASSERT_RVAL(!SPIF_VECTOR_ISNULL(self), (spif_list_t) NULL);
    if (SPIF_LIST_ISNULL(key_list)) {
        key_list = SPIF_LIST_NEW(linked_list);
    }
    for (current = self->head; current; current = current->next) {
        /* current->data is always non-NULL in maps. */
        SPIF_LIST_APPEND(key_list, SPIF_OBJ_DUP(SPIF_OBJPAIR(current->data)->key));
    }
    return key_list;
}

static spif_list_t
spif_linked_list_get_pairs(spif_linked_list_t self, spif_list_t pair_list)
{
    spif_linked_list_item_t current;

    ASSERT_RVAL(!SPIF_VECTOR_ISNULL(self), (spif_list_t) NULL);
    if (SPIF_LIST_ISNULL(pair_list)) {
        pair_list = SPIF_LIST_NEW(linked_list);
    }
    for (current = self->head; current; current = current->next) {
        /* current->data is always non-NULL in maps. */
        SPIF_LIST_APPEND(pair_list, SPIF_OBJ_DUP(SPIF_OBJPAIR(current->data)));
    }
    return pair_list;
}

static spif_list_t
spif_linked_list_get_values(spif_linked_list_t self, spif_list_t value_list)
{
    spif_linked_list_item_t current;

    ASSERT_RVAL(!SPIF_VECTOR_ISNULL(self), (spif_list_t) NULL);
    if (SPIF_LIST_ISNULL(value_list)) {
        value_list = SPIF_LIST_NEW(linked_list);
    }
    for (current = self->head; current; current = current->next) {
        /* current->data is always non-NULL in maps. */
        SPIF_LIST_APPEND(value_list, SPIF_OBJ_DUP(SPIF_OBJPAIR(current->data)->value));
    }
    return value_list;
}

static spif_bool_t
spif_linked_list_has_key(spif_linked_list_t self, spif_obj_t key)
{
    return ((SPIF_OBJ_ISNULL(spif_linked_list_map_get(self, key))) ? FALSE : TRUE);
}

static spif_bool_t
spif_linked_list_has_value(spif_linked_list_t self, spif_obj_t value)
{
    spif_linked_list_item_t current;

    ASSERT_RVAL(!SPIF_VECTOR_ISNULL(self), FALSE);

    for (current = self->head; current; current = current->next) {
        spif_objpair_t pair;

        /* current->data is always non-NULL in maps. */
        pair = SPIF_OBJPAIR(current->data);
        if (SPIF_OBJ_ISNULL(value) && SPIF_OBJ_ISNULL(pair->value)) {
            return TRUE;
        } else if (SPIF_CMP_IS_EQUAL(SPIF_OBJ_COMP(pair->value, value))) {
            return TRUE;
        }
    }
    return FALSE;
}

static spif_listidx_t
spif_linked_list_index(spif_linked_list_t self, spif_obj_t obj)
{
    spif_listidx_t i;
    spif_linked_list_item_t current;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), (spif_listidx_t) -1);
    for (current = self->head, i = 0; current && !SPIF_CMP_IS_EQUAL(SPIF_OBJ_COMP(obj, current->data)); i++, current = current->next);
    return (current ? i : ((spif_listidx_t) -1));
}

static spif_bool_t
spif_linked_list_insert(spif_linked_list_t self, spif_obj_t obj)
{
    spif_linked_list_item_t item, current;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), FALSE);
    item = spif_linked_list_item_new();
    spif_linked_list_item_set_data(item, obj);

    if (SPIF_LINKED_LIST_ITEM_ISNULL(self->head)) {
        self->head = item;
    } else if (SPIF_CMP_IS_LESS(spif_linked_list_item_comp(item, self->head))) {
        item->next = self->head;
        self->head = item;
    } else {
        for (current = self->head;
             current->next && SPIF_CMP_IS_GREATER(spif_linked_list_item_comp(item, current->next));
             current = current->next);
        item->next = current->next;
        current->next = item;
    }
    self->len++;
    return TRUE;
}

static spif_bool_t
spif_linked_list_insert_at(spif_linked_list_t self, spif_obj_t obj, spif_listidx_t idx)
{
    spif_listidx_t i;
    spif_linked_list_item_t item, current;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), FALSE);
    if (idx < 0) {
        /* Negative indexes go backward from the end of the list. */
        idx += self->len;
    }
    REQUIRE_RVAL((idx + 1) >= 0, FALSE);

    if (idx == 0 || SPIF_LINKED_LIST_ITEM_ISNULL(self->head)) {
        return spif_linked_list_prepend(self, obj);
    }
    for (current = self->head, i = 1; current->next && i < idx; i++, current = current->next);
    for (; i < idx; i++, current = current->next) {
        current->next = spif_linked_list_item_new();
        self->len++;
    }

    item = spif_linked_list_item_new();
    spif_linked_list_item_set_data(item, obj);

    item->next = current->next;
    current->next = item;
    self->len++;
    return TRUE;
}

static spif_iterator_t
spif_linked_list_iterator(spif_linked_list_t self)
{
    return (spif_iterator_t) spif_linked_list_iterator_new(self);
}

static spif_bool_t
spif_linked_list_prepend(spif_linked_list_t self, spif_obj_t obj)
{
    spif_linked_list_item_t item, current;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), FALSE);
    /* Create list member object "item" */
    item = spif_linked_list_item_new();
    spif_linked_list_item_set_data(item, obj);

    /* Set "item" to be at the front of the list. */
    current = self->head;
    self->head = item;
    item->next = current;

    self->len++;
    return TRUE;
}

static spif_obj_t
spif_linked_list_remove(spif_linked_list_t self, spif_obj_t item)
{
    spif_linked_list_item_t current, tmp;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), (spif_obj_t) NULL);
    REQUIRE_RVAL(!SPIF_OBJ_ISNULL(item), (spif_obj_t) NULL);
    if (SPIF_LINKED_LIST_ITEM_ISNULL(self->head)) {
        return (spif_obj_t) NULL;
    } else if (SPIF_CMP_IS_EQUAL(SPIF_OBJ_COMP(item, self->head->data))) {
        tmp = self->head;
        self->head = self->head->next;
    } else {
        for (current = self->head; current->next && !SPIF_CMP_IS_EQUAL(SPIF_OBJ_COMP(item, current->next->data)); current = current->next);
        if (current->next) {
            tmp = current->next;
            current->next = current->next->next;
        } else {
            return (spif_obj_t) NULL;
        }
    }
    item = tmp->data;
    tmp->data = (spif_obj_t) NULL;
    spif_linked_list_item_del(tmp);

    self->len--;
    return item;
}

static spif_obj_t
spif_linked_list_map_remove(spif_linked_list_t self, spif_obj_t item)
{
    spif_linked_list_item_t current, tmp;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), (spif_obj_t) NULL);
    REQUIRE_RVAL(!SPIF_OBJ_ISNULL(item), (spif_obj_t) NULL);
    if (SPIF_LINKED_LIST_ITEM_ISNULL(self->head)) {
        return (spif_obj_t) NULL;
    } else if (SPIF_CMP_IS_EQUAL(SPIF_OBJ_COMP(self->head->data, item))) {
        tmp = self->head;
        self->head = self->head->next;
    } else {
        for (current = self->head; current->next && !SPIF_CMP_IS_EQUAL(SPIF_OBJ_COMP(current->next->data, item)); current = current->next);
        if (current->next) {
            tmp = current->next;
            current->next = current->next->next;
        } else {
            return (spif_obj_t) NULL;
        }
    }
    item = tmp->data;
    tmp->data = (spif_obj_t) NULL;
    spif_linked_list_item_del(tmp);

    self->len--;
    return item;
}

static spif_obj_t
spif_linked_list_remove_at(spif_linked_list_t self, spif_listidx_t idx)
{
    spif_listidx_t i;
    spif_linked_list_item_t item, current;
    spif_obj_t tmp;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), (spif_obj_t) NULL);
    if (idx < 0) {
        /* Negative indexes go backward from the end of the list. */
        idx += self->len;
    }
    REQUIRE_RVAL(idx >= 0, (spif_obj_t) NULL);
    REQUIRE_RVAL(idx < self->len, (spif_obj_t) NULL);

    if (SPIF_LINKED_LIST_ITEM_ISNULL(self->head)) {
        return (spif_obj_t) NULL;
    } else if (idx == 0) {
        item = self->head;
        self->head = item->next;
    } else {
        for (current = self->head, i = 1; current->next && i < idx; i++, current = current->next);
        if (i != idx) {
            return (spif_obj_t) NULL;
        }
        item = current->next;
        current->next = item->next;
    }
    self->len--;
    tmp = spif_linked_list_item_get_data(item);
    item->data = (spif_obj_t) NULL;
    spif_linked_list_item_del(item);
    return tmp;
}

static spif_bool_t
spif_linked_list_reverse(spif_linked_list_t self)
{
    spif_linked_list_item_t current, tmp, previous;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), FALSE);
    for (previous = (spif_linked_list_item_t) NULL, current = self->head; current; previous = tmp) {
        tmp = current;
        current = current->next;
        tmp->next = previous;
    }
    self->head = tmp;
    return TRUE;
}

static spif_bool_t
spif_linked_list_set(spif_linked_list_t self, spif_obj_t key, spif_obj_t value)
{
    spif_linked_list_item_t current;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), FALSE);
    REQUIRE_RVAL(!SPIF_OBJ_ISNULL(key), FALSE);

    if (SPIF_OBJ_IS_OBJPAIR(key) && SPIF_OBJ_ISNULL(value)) {
        value = SPIF_OBJ(SPIF_OBJPAIR(key)->value);
        key = SPIF_OBJ(SPIF_OBJPAIR(key)->key);
    }
    for (current = self->head; current; current = current->next) {
        if (SPIF_CMP_IS_EQUAL(SPIF_OBJ_COMP(current->data, key))) {
            break;
        }
    }

    if (SPIF_LINKED_LIST_ITEM_ISNULL(current)) {
        spif_linked_list_insert(self, SPIF_OBJ(spif_objpair_new_from_both(key, value)));
        return FALSE;
    } else {
        spif_objpair_set_value(SPIF_OBJPAIR(current->data), SPIF_OBJ_DUP(value));
        return TRUE;
    }
}

static spif_obj_t *
spif_linked_list_to_array(spif_linked_list_t self)
{
    spif_obj_t *tmp;
    spif_linked_list_item_t current;
    spif_listidx_t i;

    ASSERT_RVAL(!SPIF_LIST_ISNULL(self), (spif_obj_t *) NULL);
    tmp = (spif_obj_t *) MALLOC(SPIF_SIZEOF_TYPE(obj) * self->len);
    for (i = 0, current = self->head; i < self->len; current = current->next, i++) {
        tmp[i] = (spif_obj_t) SPIF_OBJ(spif_linked_list_item_get_data(current));
    }
    return tmp;
}

SPIF_DEFINE_PROPERTY_FUNC_NONOBJ(linked_list, listidx, len)
SPIF_DEFINE_PROPERTY_FUNC_NONOBJ(linked_list, linked_list_item, head)


static spif_linked_list_iterator_t
spif_linked_list_iterator_new(spif_linked_list_t subject)
{
    spif_linked_list_iterator_t self;

    self = SPIF_ALLOC(linked_list_iterator);
    if (!spif_linked_list_iterator_init(self, subject)) {
        SPIF_DEALLOC(self);
        self = (spif_linked_list_iterator_t) NULL;
    }
    return self;
}

static spif_bool_t
spif_linked_list_iterator_init(spif_linked_list_iterator_t self, spif_linked_list_t subject)
{
    ASSERT_RVAL(!SPIF_ITERATOR_ISNULL(self), FALSE);
    /* ***NOT NEEDED*** spif_obj_init(SPIF_OBJ(self)); */
    spif_obj_set_class(SPIF_OBJ(self), SPIF_CLASS(SPIF_ITERATORCLASS_VAR(linked_list)));
    self->subject = subject;
    if (SPIF_LIST_ISNULL(self->subject)) {
        self->current = (spif_linked_list_item_t) NULL;
    } else {
        self->current = self->subject->head;
    }
    return TRUE;
}

static spif_bool_t
spif_linked_list_iterator_done(spif_linked_list_iterator_t self)
{
    ASSERT_RVAL(!SPIF_ITERATOR_ISNULL(self), FALSE);
    /* Do not destroy these objects.  The list owns them! */
    self->subject = (spif_linked_list_t) NULL;
    self->current = (spif_linked_list_item_t) NULL;
    return TRUE;
}

static spif_bool_t
spif_linked_list_iterator_del(spif_linked_list_iterator_t self)
{
    ASSERT_RVAL(!SPIF_ITERATOR_ISNULL(self), FALSE);
    spif_linked_list_iterator_done(self);
    SPIF_DEALLOC(self);
    return TRUE;
}

static spif_str_t
spif_linked_list_iterator_show(spif_linked_list_iterator_t self, spif_charptr_t name, spif_str_t buff, size_t indent)
{
    spif_char_t tmp[4096];

    if (SPIF_ITERATOR_ISNULL(self)) {
        SPIF_OBJ_SHOW_NULL(iterator, name, buff, indent, tmp);
        return buff;
    }

    memset(tmp, ' ', indent);
    snprintf((char *) tmp + indent, sizeof(tmp) - indent,
             "(spif_linked_list_iterator_t) %s:  %10p {\n",
             name, (spif_ptr_t) self);
    if (SPIF_STR_ISNULL(buff)) {
        buff = spif_str_new_from_ptr(tmp);
    } else {
        spif_str_append_from_ptr(buff, tmp);
    }

    buff = spif_linked_list_show(self->subject, (spif_charptr_t) "subject", buff, indent + 2);
    buff = spif_linked_list_item_show(self->current, (spif_charptr_t) "current", buff, indent + 2);

    snprintf((char *) tmp + indent, sizeof(tmp) - indent, "}\n");
    spif_str_append_from_ptr(buff, tmp);
    return buff;
}

static spif_cmp_t
spif_linked_list_iterator_comp(spif_linked_list_iterator_t self, spif_linked_list_iterator_t other)
{
    SPIF_OBJ_COMP_CHECK_NULL(self, other);
    SPIF_OBJ_COMP_CHECK_NULL(self->subject, other->subject);
    return spif_linked_list_comp(self->subject, other->subject);
}

static spif_linked_list_iterator_t
spif_linked_list_iterator_dup(spif_linked_list_iterator_t self)
{
    spif_linked_list_iterator_t tmp;

    ASSERT_RVAL(!SPIF_ITERATOR_ISNULL(self), (spif_linked_list_iterator_t) NULL);
    tmp = spif_linked_list_iterator_new(self->subject);
    tmp->current = self->current;
    return tmp;
}

static spif_classname_t
spif_linked_list_iterator_type(spif_linked_list_iterator_t self)
{
    ASSERT_RVAL(!SPIF_ITERATOR_ISNULL(self), (spif_classname_t) NULL);
    return SPIF_OBJ_CLASSNAME(self);
}

static spif_bool_t
spif_linked_list_iterator_has_next(spif_linked_list_iterator_t self)
{
    spif_linked_list_t subject;

    ASSERT_RVAL(!SPIF_ITERATOR_ISNULL(self), FALSE);
    subject = self->subject;
    REQUIRE_RVAL(!SPIF_LIST_ISNULL(subject), FALSE);
    if (self->current) {
        return TRUE;
    } else {
        return FALSE;
    }
}

static spif_obj_t
spif_linked_list_iterator_next(spif_linked_list_iterator_t self)
{
    spif_obj_t tmp;

    ASSERT_RVAL(!SPIF_ITERATOR_ISNULL(self), (spif_obj_t) NULL);
    REQUIRE_RVAL(!SPIF_LIST_ISNULL(self->subject), (spif_obj_t) NULL);
    REQUIRE_RVAL(!SPIF_LINKED_LIST_ITEM_ISNULL(self->current), (spif_obj_t) NULL);
    tmp = self->current->data;
    self->current = self->current->next;
    return tmp;
}

SPIF_DEFINE_PROPERTY_FUNC(linked_list_iterator, linked_list, subject)
SPIF_DEFINE_PROPERTY_FUNC_NONOBJ(linked_list_iterator, linked_list_item, current)
