/* llist.c - A linked list with a container object (unlike GList)
 *
 * Copyright (C) 1998 Oskar Liljeblad
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/types.h>		/* POSIX */
#include <stdint.h>		/* Gnulib/POSIX */
#include <stdbool.h>		/* Gnulib/POSIX */
#include <stdlib.h>		/* Gnulib/C89 */
#include <gettext.h>		/* Gnulib/Gettext */
#define _(s) gettext(s)
#include "xalloc.h"		/* Gnulib */
#include "llist.h"		/* common */
#include "error.h"		/* common */

typedef struct _LListIteratorPriv LListIteratorPriv;

struct _LNode {
	void *data;
	LNode *next;
	LNode *previous;
};

struct _LList {
	LNode *first;
	LNode *last;
	uint32_t size;
};

struct _LListIteratorPriv {
    bool (*has_next)(LListIterator *it);
    void *(*next)(LListIterator *it);
    void (*remove)(LListIterator *it);
    LList *list;
    LNode *entry;
};

static inline void llist_add_last_entry(LList *list, LNode *entry);
static inline void llist_remove_entry(LList *list, LNode *entry);
static inline LNode *llist_get_entry(LList *list, uint32_t index);
static bool llist_iterator_has_next(LListIterator *it);
static void *llist_iterator_next(LListIterator *it);
static void llist_iterator_remove(LListIterator *it);
#if 0
static void llist_iterator_restart(Iterator *it);
static void *llist_iterator_previous(Iterator *it);
static void llist_iterator_add(Iterator *it, void *value);
#endif

LList *
llist_new(void)
{
	LList *list = xmalloc(sizeof(LList));
	
	list->first = NULL;
	list->last = NULL;
	list->size = 0;

	return list;
}

void
llist_free(LList *list)
{
	LNode *entry = list->first;

	while (entry != NULL) {
		LNode *next = entry->next;
		free(entry);
		entry = next;
	}

	free(list);
}

void *
llist_get_first(LList *list)
{
	return (list->size == 0 ? NULL : list->first->data);
}

void *
llist_get_last(LList *list)
{
	return (list->size == 0 ? NULL : list->last->data);
}

void *
llist_remove_first(LList *list)
{
	LNode *entry;
	void *data;

	if (list->size == 0)
		return NULL;

	list->size--;
	entry = list->first;
	data = entry->data;

	if (entry->next != NULL)
		entry->next->previous = NULL;
	else
		list->last = NULL;
	list->first = entry->next;
	free(entry);

	return data;
}

void *
llist_remove_last(LList *list)
{
	LNode *entry;
	void *data;

	if (list->size == 0)
		return NULL;

	list->size--;
	entry = list->last;
	data = entry->data;

	if (entry->previous != NULL)
		entry->previous->next = NULL;
	else
		list->first = NULL;
	list->last = entry->previous;
	free(entry);

	return data;
}

void
llist_add_first(LList *list, void *data)
{
	LNode *entry = xmalloc(sizeof(LNode));
	entry->data = data;

	if (list->size == 0) {
		entry->next = NULL;
		entry->previous = NULL;
		list->first = list->last = entry;
	} else {
		entry->next = list->first;
		entry->previous = NULL;
		list->first->previous = entry;
		list->first = entry;
	}
	list->size++;
}

void
llist_add_last(LList *list, void *data)
{
	LNode *entry = xmalloc(sizeof(LNode));
	entry->data = data;
	llist_add_last_entry(list, entry);
}

static inline void
llist_add_last_entry(LList *list, LNode *entry)
{
	if (list->size == 0) {
		entry->next = NULL;
		entry->previous = NULL;
		list->first = list->last = entry;
	} else {
		entry->next = NULL;
		entry->previous = list->last;
		list->last->next = entry;
		list->last = entry;
	}
	list->size++;
}

bool
llist_remove(LList *list, void *data)
{
	LNode *entry;

	for (entry = list->first; entry != NULL; entry = entry->next) {
		if (entry->data == data) {
			llist_remove_entry(list, entry);
			return true;
		}
	}

	return false;
}

static inline void
llist_remove_entry(LList *list, LNode *entry)
{
	if (list->size == 1) {
		list->first = list->last = NULL;
	} else if (entry == list->first) {
		list->first = entry->next;
		entry->next->previous = NULL;
	} else if (entry == list->last) {
		list->last = entry->previous;
		entry->previous->next = NULL;
	} else {
		entry->next->previous = entry->previous;
		entry->previous->next = entry->next;
	}
	list->size--;
	free(entry);
}

static inline LNode *
llist_get_entry(LList *list, uint32_t index)
{
	LNode *entry;

	if (index < list->size/2) {
		entry = list->first;
		while (index-- > 0)
			entry = entry->next;
	} else {
		entry = list->last;
		while (++index < list->size)
			entry = entry->previous;
	}

	return entry;
}

bool
llist_contains(LList *list, void *data)
{
	LNode *entry;

	for (entry = list->first; entry != NULL; entry = entry->next) {
		if (entry->data == data)
			return true;
	}

	return false;
}

uint32_t
llist_size(LList *list)
{
	return list->size;
}

void
llist_clear(LList *list)
{
	LNode *entry = list->first;

	while (entry != NULL) {
		LNode *next = entry->next;
		free(entry);
		entry = next;
	}

	list->first = list->last = NULL;
	list->size = 0;
}

void *
llist_get(LList *list, uint32_t index)
{
	LNode *entry;

	if (index >= list->size)
		return NULL;

	entry = llist_get_entry(list, index);
	return entry->data;
}

void *
llist_set(LList *list, uint32_t index, void *data)
{
	LNode *entry;
	void *old_data;

	if (index >= list->size)
		return NULL;

	entry = llist_get_entry(list, index);
	old_data = entry->data;
	entry->data = old_data;
	return old_data;
}

void
llist_add_all(LList *list, LList *list2)
{
    uint32_t c;
    for (c = 0; c < list2->size; c++)
    	llist_add(list, llist_get_entry(list2, c)->data);
}

void
llist_add_at(LList *list, uint32_t index, void *data)
{
	LNode *entry;
	
	if (index > list->size)
		return;

	entry = xmalloc(sizeof(LNode));
	entry->data = data;

	if (index < list->size) {
		LNode *after = llist_get_entry(list, index);
		entry->next = after;
		entry->previous = after->previous;
		if (after->previous == NULL)
			list->first = entry;
		else
			after->previous->next = entry;
		after->previous = entry;
		list->size++;
	} else {
		llist_add_last_entry(list, entry);
	}
}

void *
llist_remove_at(LList *list, uint32_t index)
{
	LNode *entry;
	void *data;

	if (index >= list->size)
		return NULL;

	entry = llist_get_entry(list, index);
	data = entry->data;
	llist_remove_entry(list, entry);
	return data;
}

int32_t
llist_index_of(LList *list, void *data)
{
	int32_t index = 0;
	LNode *entry;

	for (entry = list->first; entry != NULL; entry = entry->next) {
		if (entry->data == data)
			return index;
		index++;
	}

	return -1;
}

int32_t
llist_last_index_of(LList *list, void *data)
{
	int32_t index = list->size - 1;
	LNode *entry;

	for (entry = list->last; entry != NULL; entry = entry->previous) {
		if (entry->data == data)
			return index;
		index--;
	}

	return -1;
}

LList *
llist_clone(LList *list)
{
	LList *copy = xmalloc(sizeof(LList));
	LNode *entry;
	LNode *previous = NULL;

	copy->size = list->size;

	for (entry = list->first; entry != NULL; entry = entry->next) {
		LNode *new_entry = xmalloc(sizeof(LNode));
		new_entry->data = entry->data;
		new_entry->previous = previous;
		if (previous == NULL)
			copy->first = new_entry;
		else
			previous->next = new_entry;
		previous = new_entry;
	}

	copy->last = previous;
	if (previous == NULL)
		list->first = NULL;
	else
		previous->next = NULL;

	return copy;
}

void **
llist_to_array(LList *list)
{
	void **array = xmalloc(list->size * sizeof(void *));
	LNode *entry = list->first;
	uint32_t index = 0;

	for (entry = list->first; entry != NULL; entry = entry->next)
		array[index++] = entry->data;

	return array;
}

void **
llist_to_null_terminated_array(LList *list)
{
	void **array = xmalloc((list->size+1) * sizeof(void *));
	LNode *entry = list->first;
	uint32_t index = 0;

	for (entry = list->first; entry != NULL; entry = entry->next)
		array[index++] = entry->data;
	array[index] = NULL;

	return array;
}

bool
llist_is_empty(LList *list)
{
	return list->size == 0;
}

void
llist_iterate(LList *list, void (*iterator_func)(void *))
{
	LNode *entry;
	for (entry = list->first; entry != NULL; entry = entry->next)
		iterator_func(entry->data);
}

void
llist_iterator(LList *list, LListIterator *it)
{
    LListIteratorPriv *itp = (LListIteratorPriv *) it;
    itp->list = list;
    itp->entry = list->first;
    itp->has_next = llist_iterator_has_next;
    itp->next = llist_iterator_next;
    itp->remove = llist_iterator_remove;
}

static bool
llist_iterator_has_next(LListIterator *it)
{
    LListIteratorPriv *itp = (LListIteratorPriv *) it;
    return itp->entry != NULL;
}

static void *
llist_iterator_next(LListIterator *it)
{
    LListIteratorPriv *itp = (LListIteratorPriv *) it;
    void *data = itp->entry->data;
    itp->entry = itp->entry->next;
    return data;
}

static void
llist_iterator_remove(LListIterator *it)
{
    LListIteratorPriv *itp = (LListIteratorPriv *) it;

    if (itp->list->first == itp->entry)
	internal_error(_("Called iterator_remove before first iterator_next"));
    //FIXME: we should probably check that this function isn't called
    // write between next entry.. this is probably done by lastReturned

    if (itp->entry == NULL) {
	llist_remove_entry(itp->list, itp->list->last);
    } else {
	llist_remove_entry(itp->list, itp->entry->previous);
    }
}

#if 0
static void *
llist_iterator_previous(LListIterator *it)
{
    LListIteratorPriv *itp = (LListIteratorPriv *) it;
    void *data;

    /* FIXME: not sure if this is right.
     * This will happen if the last next() returned the last
     * element in the list.
     */
    if (itp->entry == NULL)
	itp->entry = itp->list->last;
    else
	itp->entry = itp->entry->previous;
    data = itp->entry->data;
    return data;
}

static void
llist_iterator_restart(Iterator *it)
{
	LListIterator *listit = (LListIterator *) it;
	listit->entry = listit->list->first;
}

static void
llist_iterator_add(Iterator *it, void *value)
{
	LListIterator *listit = (LListIterator *) it;

	if (listit->entry == NULL) {
		llist_add_last(listit->list, value);
	} else {
		LNode *entry = xmalloc(sizeof(LNode));
		entry->previous = listit->entry->previous;
		entry->next = listit->entry;
		entry->data = value;
		if (entry->next != NULL)
			entry->next->previous = entry;
		else
			listit->list->last = entry;
		if (entry->previous != NULL)
			entry->previous->next = entry;
		else
			listit->list->first = entry;
		listit->list->size++;
		/*listit->entry = entry->next;		-- not necessary */
	}
}
#endif

void
llist_reverse(LList *list)
{
	LNode *head = list->first;
	LNode *tail = list->last;

	while (head != tail && head->previous != tail) {
		void *data = head->data;
		head->data = tail->data;
		tail->data = data;
		head = head->next;
		tail = tail->previous;
	}
}

LNode *
llist_get_first_node(LList *list)
{
	return list->first;
}

LNode *
llist_get_last_node(LList *list)
{
	return list->last;
}

LNode *
lnode_next(LNode *node)
{
	return node->next;
}

LNode *
lnode_previous(LNode *node)
{
	return node->previous;
}

void 
lnode_remove(LList *list, LNode *node)
{
	llist_remove_entry(list, node);
}

LNode *
lnode_add_after(LList *list, LNode *node, void *data)
{
	if (node->next == NULL) {
		llist_add_last(list, data);
		return list->last;
	} else {
		LNode *entry = xmalloc(sizeof(LNode));
		entry->previous = node;
		entry->next = node->next;
		entry->data = data;
		if (entry->next != NULL)
			entry->next->previous = entry;
		else
			list->last = entry;
		if (entry->previous != NULL)
			entry->previous->next = entry;
		else
			list->first = entry;
		list->size++;
		return entry;
	}
}

LNode *
lnode_add_before(LList *list, LNode *node, void *data)
{
	if (node->previous == NULL) {
		llist_add_first(list, data);
		return list->first;
	} else {
		LNode *entry = xmalloc(sizeof(LNode));
		entry->previous = node->previous;
		entry->next = node;
		entry->data = data;
		if (entry->next != NULL)
			entry->next->previous = entry;
		else
			list->last = entry;
		if (entry->previous != NULL)
			entry->previous->next = entry;
		else
			list->first = entry;
		list->size++;
		return entry;
	}
}

bool
lnode_is_first(LNode *node)
{
	return node->previous == NULL;
}

bool
lnode_is_last(LNode *node)
{
	return node->next == NULL;
}

void *
lnode_data(LNode *node)
{
	return node->data;
}
